1 意图
单例模式可以保证一个类只有一个实例,提供一个访问该实例的全局节点。
2 问题
单例模式同时解决了两个问题,所以违反了单一职责原则。
- 保证一个类只有一个实例。
为什么要控制一个类的实例数量?最常见的原因是控制某些共享资源(如数据库或者文件)的访问权限。
其运行方式是:如果创建了一个对象,稍后决定再创建一个对象时,会得到先前创建的对象,而不是一个新的对象。
注意:普通构造函数无法实现上述行为,构造函数的设计决定了它必须总是返回一个新的对象。

- 为类的单一实例提供一个全局访问节点。
全局变量使用十分方便,但是也非常不安全:任何代码都有可能覆盖掉全局变量的值,引发程序崩溃。
单例模式允许程序在任何地方访问特定对象,同时可以保护该实例不被其他代码覆盖。
此外,不希望解决同一个问题的代码分散在程序各处。更好的方式是将其放在同一个类中,特别是其他代码已经依赖这个类时更应该如此。3 解决方案
单例模式包含两个步骤:
- 将默认构造函数设置为私有,防止其他对象使用
new运算符创建实例 新建一个静态构建方法作为构造函数。该函数会调用私有构造函数来创建对象,将其保存在一个静态成员变量中。此后所有对该函数的调用都将返回这个缓存对象。
4 结构

单例(Singleton)类声明了一个名为getInstace()的静态方法来返回所属类的一个相同实例。单例构造函数必须对客户端代码隐藏,调用getInstance()是获取单例对象的唯一方式。5 伪代码
``java // 数据库类会对getInstance(获取实例)`方法进行定义以让客户端在程序各处 // 都能访问相同的数据库连接实例。 class Database is // 保存单例实例的成员变量必须被声明为静态类型。 private static field instance: Database// 单例的构造函数必须永远是私有类型,以防止使用
new运算符直接调用构 // 造方法。 private constructor Database() is// 部分初始化代码(例如到数据库服务器的实际连接)。// ...
// 用于控制对单例实例的访问权限的静态方法。 public static method getInstance() is
if (Database.instance == null) thenacquireThreadLock() and then// 确保在该线程等待解锁时,其他线程没有初始化该实例。if (Database.instance == null) thenDatabase.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 中将包含同一个对象。
<a name="febb7d8d"></a># 6 应用场景- **如果某个类对所有客户端只需要有一个可用的实例,可以使用单例模式。**<br />单例模式禁止通过特殊构建方法以外的任何方式来创建类的对象。该特殊方法可以创建新对象,如果该对象已经创建,则返回已有的对象。- **如果需要更加严格地控制全局变量,可以使用单例模式。**<br />单例模式与全局变量不同,它保证类只存在一个实例。除了类自身外,无法通过任何方式替换缓存的实例。<br />可以随时调整限制,设置生成实例的数量,只需要修改`getInstance()`方法。<a name="4170e445"></a># 7 实现方式1. 在类中添加私有静态成员变量用于保存单例实例。2. 声明一个公有静态构建方法用于获取单例实例。3. 在静态方法中实现“延迟初始化”。该方法会在首次被调用时创建一个对象,将其存储在静态成员变量中。此后该方法每次被调用时都返回该实例。4. 将类的构造函数设置为私有。类的静态方法可以调用构造函数,其他对象不能调用。5. 检查客户端代码,将对单例类构造函数的调用,替换成对其静态构建方法的调用。<a name="87338c0b"></a># 8 优缺点- 优点- 可以保证一个类只有一个实例。- 有一个获取该实例的全局访问节点。- 仅在首次请求单例对象时进行初始化。- 缺点- 违反了单一职责原则,一个模式解决了两个问题(保证单个实例,获取该实例)。- 可能会掩盖不良设计,如程序组件之间相互了解过多。- 多线程环境下需要进行特殊处理,避免多个线程多次创建单例对象。- 客户端代码的单元测试可能会比较困难,因为许多测试框架以基于继承的方式创建模拟对象。单例类的构造方法是私有的,而且绝大部分语言无法重写静态方法,所以需要考虑模拟单例的方法。要么不编写测试代码,要么不使用单例模式。<a name="2b0a734f"></a># 9 与其他模式的关系- [外观模式](https://refactoringguru.cn/design-patterns/facade)通常可以转换为[单例模式](https://refactoringguru.cn/design-patterns/singleton),因为大部分情况下一个外观对象就足够了。- 如果可以将所有共享状态简化为一个享元对象,则[享元模式](https://refactoringguru.cn/design-patterns/flyweight)和单例模拟类似了。但两个模式有根本的不同:- 单例类只有一个单例实体,但是享元类可以有多个实体,各实体的内在状态也可以不同。- 单例对象是可变的,享元对象是不可变的。- [抽象工厂模式](https://refactoringguru.cn/design-patterns/abstract-factory)、[生成器模式](https://refactoringguru.cn/design-patterns/builder)、[原型模式](https://refactoringguru.cn/design-patterns/prototype)都可以用单例模式实现。<a name="8b93ebe6"></a># 10 代码示例<a name="a8920174"></a>## 10.1 单例类```goackage mainimport ("fmt""sync")var lock = &sync.Mutex{}type single struct {}var singleInstance *singlefunc getInstance() *single {if singleInstance == nil {lock.Lock()defer lock.Unlock()if singleInstance == nil {fmt.Println("Creating single instance now.")singleInstance = &single{}} else {fmt.Println("Single instance already created.")}} else {fmt.Println("Single instance already created.")}return singleInstance}
10.2 客户端
package mainimport ("fmt")func main() {for i := 0; i < 30; i++ {go getInstance()}// Scanln is similar to Scan, but stops scanning at a newline and// after the final item there must be a newline or EOF.fmt.Scanln()}
