单例形式

聊聊在出产环境中运用Docker的最佳实践 – 掘金 ()

单例形式是一种创立型规划形式, 让你能够确保一个类只要一个实例, 并供给一个拜访该实例的大局节点。

2023 跟我一起学设计模式:单例模式

问题

单例形式一起处理了两个问题, 所以违反了单一责任准则

  1. 确保一个类只要一个实例。 为什么会有人想要操控一个类所拥有的实例数量? 最常见的原因是操控某些共享资源 (例如数据库或文件) 的拜访权限。

    它的运作办法是这样的: 假如你创立了一个目标, 一起过一瞬间后你决定再创立一个新目标, 此刻你会取得之前已创立的目标, 而不是一个新目标。

    注意, 普通结构函数无法完成上述行为, 由于结构函数的规划决定了它有必要总是回来一个新目标。

2023 跟我一起学设计模式:单例模式

客户端乃至或许没有意识到它们一直都在运用同一个目标。

  1. 为该实例供给一个大局拜访节点。 还记得你 (好吧, 其实是我自己) 用过的那些存储重要目标的大局变量吗? 它们在运用上非常方便, 但一起也非常不安全, 由于任何代码都有或许覆盖掉那些变量的内容, 从而引发程序崩溃。

    和大局变量一样, 单例形式也答应在程序的任何地方拜访特定目标。 可是它能够维护该实例不被其他代码覆盖。

    还有一点: 你不会希望处理同一个问题的代码涣散在程序遍地的。 因而更好的办法是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。

现在, 单例形式已经变得非常流行, 以至于人们会将只处理上文描绘中任意一个问题的东西称为单例

处理方案

一切单例的完成都包含以下两个相同的步骤:

  • 将默认结构函数设为私有, 防止其他目标运用单例类的 new运算符。
  • 新建一个静态构建办法作为结构函数。 该函数会 “偷偷” 调用私有结构函数来创立目标, 并将其保存在一个静态成员变量中。 尔后一切关于该函数的调用都将回来这一缓存目标。

假如你的代码能够拜访单例类, 那它就能调用单例类的静态办法。 不管何时调用该办法, 它总是会回来相同的目标。

真实世界类比

政府是单例形式的一个很好的示例。 一个国家只要一个官方政府。 不管组成政府的每个人的身份是什么, “某政府” 这一称谓总是鉴别那些掌权者的大局拜访节点。

单例形式结构

2023 跟我一起学设计模式:单例模式

  1. 单例 (Singleton) 类声明了一个名为 getInstance获取实例的静态办法来回来其所属类的一个相同实例。

    单例的结构函数有必要对客户端 (Client) 代码隐藏。 调用 获取实例办法有必要是获取单例目标的仅有办法。

伪代码

在本例中, 数据库衔接类便是一个单例。 该类不供给公有结构函数, 因而获取该目标的仅有办法是调用 获取实例办法。 该办法将缓存初次生成的目标, 并为一切后续调用回来该目标。

// 数据库类会对`getInstance(获取实例)`办法进行界说以让客户端在程序遍地
// 都能拜访相同的数据库衔接实例。
class Database is
    // 保存单例实例的成员变量有必要被声明为静态类型。
    private static field instance: Database
    // 单例的结构函数有必要永远是私有类型,以防止运用`new`运算符直接调用构
    // 造办法。
    private constructor Database() is
        // 部分初始化代码(例如到数据库服务器的实践衔接)。
        // ……
    // 用于操控对单例实例的拜访权限的静态办法。
    public static method getInstance() is
        if (Database.instance == null) then
            acquireThreadLock() and then
                // 确保在该线程等待解锁时,其他线程没有初始化该实例。
                if (Database.instance == null) then
                    Database.instance = new Database()
        return Database.instance
    // 最终,任何单例都有必要界说一些可在其实例上履行的业务逻辑。
    public method query(sql) is
        // 比方运用的一切数据库查询恳求都需求经过该办法进行。因而,你能够
        // 在这里增加限流或缓冲逻辑。
        // ……
class Application is
    method main() is
        Database foo = Database.getInstance()
        foo.query("SELECT ……")
        // ……
        Database bar = Database.getInstance()
        bar.query("SELECT ……")
        // 变量 `bar` 和 `foo` 中将包含同一个目标。

单例形式适合运用场景

假如程序中的某个类关于一切客户端只要一个可用的实例, 能够运用单例形式。

单例形式制止经过除特别构建办法以外的任何办法来创立自身类的目标。 该办法能够创立一个新目标, 但假如该目标已经被创立, 则回来已有的目标。

假如你需求更加严格地操控大局变量, 能够运用单例形式。

单例形式与大局变量不同, 它确保类只存在一个实例。 除了单例类自己以外, 无法经过任何办法替换缓存的实例。

请注意, 你能够随时调整约束并设定生成单例实例的数量, 只需修改 获取实例办法, 即 getInstance 中的代码即可完成。

完成办法

  1. 在类中增加一个私有静态成员变量用于保存单例实例。
  2. 声明一个公有静态构建办法用于获取单例实例。
  3. 在静态办法中完成”推迟初始化”。 该办法会在初次被调用时创立一个新目标, 并将其存储在静态成员变量中。 尔后该办法每次被调用时都回来该实例。
  4. 将类的结构函数设为私有。 类的静态办法仍能调用结构函数, 可是其他目标不能调用。
  5. 检查客户端代码, 将对单例的结构函数的调用替换为对其静态构建办法的调用。

单例形式优缺点

  • 你能够确保一个类只要一个实例。

  • 你取得了一个指向该实例的大局拜访节点。

  • 仅在初次恳求单例目标时对其进行初始化。

  • 违反了单一责任准则。 该形式一起处理了两个问题。

  • 单例形式或许掩盖不良规划, 比方程序各组件之间相互了解过多等。

  • 该形式在多线程环境下需求进行特别处理, 防止多个线程屡次创立单例目标。

  • 单例的客户端代码单元测验或许会比较困难, 由于许多测验结构以基于承继的办法创立模仿目标。 由于单例类的结构函数是私有的, 而且绝大部分语言无法重写静态办法, 所以你需求想出细心考虑模仿单例的办法。 要么干脆不编写测验代码, 或者不运用单例形式。

golang代码示例

Go 单例形式解说和代码示例 – 掘金 ()

最近文章

聊聊在出产环境中运用Docker的最佳实践 – 掘金 ()