天天看点

创建型设计模式:单例模式

作者:风趣生命的起源

意图

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并为该实例提供了全局访问点。

创建型设计模式:单例模式

问题

单例模式同时解决了两个问题,违反了单一职责原则:

1、确保一个类只有一个实例。为什么要控制一个类有多少个实例?最常见的原因是控制对某个共享资源的访问,例如数据库或文件。

工作原理如下:假设你创建了一个对象,但过了一段时间后决定创建一个新的对象。而不是获得一个新的对象,你会得到已经创建的那个对象。

请注意,使用常规的构造函数无法实现这种行为,因为构造函数的调用始终应该返回一个新的对象。

创建型设计模式:单例模式

客户端甚至可能没有意识到他们一直在使用同一个对象。

2、提供一个全局访问点来访问该实例。记得那些用来存储一些重要对象的全局变量吗?虽然它们非常方便,但也非常不安全,因为任何代码都有可能覆盖这些变量的内容并导致应用程序崩溃。

就像全局变量一样,Singleton 模式允许您从程序的任何地方访问某个对象。然而,它还保护该实例不被其他代码覆盖。

这个问题还有另一面:您不希望用于解决问题#1的代码散布在整个程序中。最好将其放在一个类中,特别是如果您的其余代码已经依赖于它。

如今,Singleton 模式变得非常流行,以至于人们可能会称某些东西为 Singleton,即使它只解决了列出的问题之一。

解决方法

所有 Singleton 的实现都有以下两个共同步骤:

1. 将默认构造函数设为私有,以防其他对象使用 new 运算符来创建 Singleton 类的实例。

2. 创建一个静态创建方法,作为构造函数的替代。在内部,该方法调用私有构造函数来创建一个对象,并将其保存在静态字段中。后续对该方法的所有调用都会返回缓存的对象。

如果你的代码可以访问 Singleton 类,那么它就能调用 Singleton 的静态方法。因此,每次调用该方法时,都会返回相同的对象。

真实世界类比

政府是 Singleton 模式的一个很好的例子。一个国家只能有一个官方政府。无论组成政府的个人身份如何变化,"X政府"这个标题都是一个全局访问点,用于识别负责管理的人群。

结构

创建型设计模式:单例模式

Singleton类声明了静态方法getInstance,该方法返回其自身类的同一个实例。

Singleton的构造函数应该对客户端代码隐藏起来。调用getInstance方法应该是获取Singleton对象的唯一方式。

伪代码

在这个例子中,数据库连接类充当了一个Singleton。该类没有公共构造函数,因此获取其对象的唯一方式是调用getInstance方法。该方法缓存了第一次创建的对象,并在后续的调用中返回它。

// The Database class defines the `getInstance` method that lets
// clients access the same instance of a database connection
// throughout the program.
class Database is
    // The field for storing the singleton instance should be
    // declared static.
    private static field instance: Database

    // The singleton's constructor should always be private to
    // prevent direct construction calls with the `new`
    // operator.
    private constructor Database() is
        // Some initialization code, such as the actual
        // connection to a database server.
        // ...

    // The static method that controls access to the singleton
    // instance.
    public static method getInstance() is
        if (Database.instance == null) then
            acquireThreadLock() and then
                // Ensure that the instance hasn't yet been
                // initialized by another thread while this one
                // has been waiting for the lock's release.
                if (Database.instance == null) then
                    Database.instance = new Database()
        return Database.instance

    // Finally, any singleton should define some business logic
    // which can be executed on its instance.
    public method query(sql) is
        // For instance, all database queries of an app go
        // through this method. Therefore, you can place
        // throttling or caching logic here.
        // ...

class Application is
    method main() is
        Database foo = Database.getInstance()
        foo.query("SELECT ...")
        // ...
        Database bar = Database.getInstance()
        bar.query("SELECT ...")
        // The variable `bar` will contain the same object as
        // the variable `foo`.           

适用性

1、当你的程序中的一个类只应该有一个实例供所有客户端使用时,可以使用单例模式,例如,多个程序部分共享的单个数据库对象。

单例模式禁用了除了特殊创建方法之外的所有创建对象的方式。该方法要么创建一个新对象,要么返回一个已经存在的对象。

2、当你需要更严格地控制全局变量时,可以使用单例模式。

与全局变量不同,单例模式保证了一个类只有一个实例。除了单例类本身,没有任何东西能替代缓存的实例。

需要注意的是,你可以随时调整这个限制,允许创建任意数量的单例实例。唯一需要更改的代码是getInstance方法的实现。

如何实现

  1. 在类中添加一个私有的静态字段来存储单例实例。
  2. 声明一个公共的静态创建方法来获取单例实例。
  3. 在静态方法内部实现"延迟初始化"。第一次调用时创建一个新对象并将其放入静态字段中。在后续的调用中,该方法应始终返回该实例。
  4. 将类的构造函数设为私有。类的静态方法仍然可以调用构造函数,但其他对象不能。
  5. 检查客户端代码,并将所有直接调用单例构造函数的地方替换为调用其静态创建方法的地方。

Python示例

class SingletonMeta(type):
    """
    The Singleton class can be implemented in different ways in Python. Some
    possible methods include: base class, decorator, metaclass. We will use the
    metaclass because it is best suited for this purpose.
    """

    _instances = {}

    def __call__(cls, *args, **kwargs):
        """
        Possible changes to the value of the `__init__` argument do not affect
        the returned instance.
        """
        if cls not in cls._instances:
            instance = super().__call__(*args, **kwargs)
            cls._instances[cls] = instance
        return cls._instances[cls]


class Singleton(metaclass=SingletonMeta):
    def some_business_logic(self):
        """
        Finally, any singleton should define some business logic, which can be
        executed on its instance.
        """

        # ...


if __name__ == "__main__":
    # The client code.

    s1 = Singleton()
    s2 = Singleton()

    if id(s1) == id(s2):
        print("Singleton works, both variables contain the same instance.")
    else:
        print("Singleton failed, variables contain different instances.")           

继续阅读