创建型模式-单例模式

什么是单利模式

单例是一种创建型设计模式, 让你能够保证一个类只有一个实例,并提供一个访问该  实例的全局节点

  • 单例模式,一个类只有一个实例
  • 为该实例提供一个全局访问节点

  1. 单例(Singleton)类声明了一个名为 getInstance 获取实 例 的静态方法来返回其所属类的一个相同实例。单例的构造函数必须对客户端(Client) 代码隐藏。 调用 获取实例 方法必须是获取单例对象的唯一方式。

Example

简单单例

package main

import (
"fmt"
"sync"
)

var lock = &sync.Mutex{}

type single struct {
}

var singleInstance *single

func 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
}

利用init函数

  • 我们可以在 init函数中创建单例实例。 这仅适用于实例的早期初始化工作已经确定时。 init函数仅会在包中的每个文件里调用一次, 所以我们可以确定其只会创建一个实例

sync.Once

package main

import (
"fmt"
"sync"
)

var once sync.Once

type single struct {
}

var singleInstance *single

func getInstance() *single {
if singleInstance == nil {
once.Do(
func() {
fmt.Println("Creating single instance now.")
singleInstance = &single{}
})
} else {
fmt.Println("Single instance already created.")
}

return singleInstance
}

客户端

package main

import (
"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()
}

适用场景

  1. 如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式
  • 单例模式禁止通过除特殊构建方法以外的任何方式来创建自 身类的对象。该方法可以创建一个新对象,但如果该对象已 经被创建,则返回已有的对象。
  1. 如果你需要更加严格地控制全局变量,可以使用单例模式
  • 单例模式与全局变量不同,它保证类只存在一个实例。除了 单例类自己以外,无法通过任何方式替换缓存的实例

优缺点

优点

  • 可以保证一个类只有一个实例
  • 获得了一个指向该实例的全局访问节点
  • 仅在首次请求单例对象时对其进行初始化

缺点

  • 违反了_单一职责原则_。该模式同时解决了两个问题、

  • 单例模式可能掩盖不良设计,比如程序各组件之间相互了解过多等

  • 该模式在多线程环境下需要进行特殊处理,避免多个线程多 次创建单例对象

  • 单例的客户端代码单元测试可能会比较困难,因为许多测试 框架以基于继承的方式创建模拟对象。由于单例类的构造函 数是私有的,而且绝大部分语言无法重写静态方法,所以你 需要想出仔细考虑模拟单例的方法。要么干脆不编写测试代 码,或者不使用单例模式

与其他模式的关系

  • 外观类通常可以转换为单例类,因为在大部分情况下一个外 观对象就足够了
  • 如果你能将对象的所有共享状态简化为一个享元对象,那么 享元就和单例类似了。但这两个模式有两个根本性的不同
    • 只会有一个单例实体,但是享元类可以有多个实体,各实 体的内在状态也可以不同
    • 单例对象可以是可变的。享元对象是不可变的
  • 抽象工厂、生成器和原型都可以用单例来实现