创建型 - 1. 单例模式

66 阅读2分钟

1. 单例模式的定义

单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。

2. 单例模式的用处

  • 从业务概念上,有些数据在系统中只应该保存一份,就比较适合设计为单例类。
  • 使用单例解决资源访问冲突的问题。

3. 单例的实现

3.1 懒汉式

type SingleInstance struct{}

var instance *SingleInstance

func GetInstanceLazily() *SingleInstance {
   if instance == nil {
      instance = &SingleInstance{}
   }
   return instance
}

3.2 饥汉式


type SingleInstance struct{}

var instance *SingleInstance

func init() {
   instance = &SingleInstance{}
}

func GetInstance() *SingleInstance {
   return instance
}

3.3 双重检测

type SingleInstance struct{}

var (
   instance *SingleInstance
   mutex    sync.Mutex
)

func GetInstanceSafety() *SingleInstance {
   if instance == nil {
      mutex.Lock()
      if instance == nil {
         instance = &SingleInstance{}
      }
      mutex.Unlock()
   }
   return instance
}


func TestGetInstanceSafety(t *testing.T) {
   goroutineNum := 100

   receiveChan := make(chan *SingleInstance, goroutineNum)

   for i := 0; i < goroutineNum; i++ {
      go func() {
         receiveChan <- GetInstanceSafety()
      }()
   }

   for i := 0; i < goroutineNum; i++ {
      _ = <-receiveChan
   }
}

4. 单例模式存在的问题

4.1 单例对 OOP 特性的支持不友好

将某个类设计成到单例类,也就意味着放弃了继承和多态这两个强有力的面向对象特性,也就相当于损失了可以应对未来需求变化的扩展性。写成单例模式之后,这个类就没办法被继承了(因为直接把构造方法变成private了),那么多态、继承这两个特性都没法用了。

4.2 单例会隐藏类之间的依赖关系

通过构造函数、参数传递等方式声明的类之间的依赖关系,我们通过查看函数的定义,就能很容易识别出来。但是,单例类不需要显示创建、不需要依赖参数传递,在函数中直接调用就可以了。

4.3 单例对代码的扩展性不好

4.4 单例对代码的可测试性不友好

如果单例类依赖比较重的外部资源,比如 DB,我们在写单元测试的时候,希望能通过 mock 的方式将它替换掉。而单例类这种硬编码式的使用方式,导致无法实现 mock 替换。

4.5 单例不支持有参数的构造函数

5. 单例模式的替代方案

  • 静态方法,保证全局唯一;
  • 将单例生成的对象,作为参数传递给函数(也可以通过构造函数传递给类的成员变量),可以解决单例隐藏类之间依赖关系的问题;