单例模式实现
最后,我们必须实现单例模式。正如我们前面提到的,通常做法是写一个静态方法和实例来检索单例模式实例。
在 Go 中,没有 static
这个关键字,但是我们可以通过使用包的范围来达到同样的效果。
首先,我们创建一个结构体,其中包含我们想要保证的对象 在程序执行过程中成为单例的对象。
package singleton type Singleton struct { count int } var instance *Singleton func init() { instance = &Singleton{} } func GetInstance() *Singleton { if instance == nil { instance = new(Singleton) } return instance } func (s *Singleton) AddOne() int { s.count++ return s.count }
我们来分析一下这段代码的差别,在 Java 或 C++ 语言中,变量实例会在程序开始时被初始化为 NULL
。但在 Go 中,你可以将结构的指针初始化为 nil
,但不能将一个结构初始化为 nil
(相当于其他语言的 NULL
)。
所以 var instance *singleton*
这一语句定义了一个指向结构的指针为 nil
,而变量称为 instance
。
我们创建了一个 GetInstance
方法,检查实例是否已经被初始化(instance == nil
),并在已经分配的空间中创建一个实例 instance = new(singleton)
。
Addone()
方法将获取变量实例的计数,并逐个加 1,然后返回当前计数器的值。
再一次运行单元测试代码:
$ go test -v -run=GetInstance . === RUN TestGetInstance --- PASS: TestGetInstance (0.00s) PASS ok github.com/yuzhoustayhungry/GoDesignPattern/singleton 0.297s
单例模式优缺点
优点:
- 你可以保证一个类只有一个实例。
- 你获得了一个指向该实例的全局访问节点。
- 仅在首次请求单例对象时对其进行初始化。
缺点:
- 违反了单一职责原则。 该模式同时解决了两个问题。
- 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。
- 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。
- 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。
总结
我们有很多只需要一个对象的情况 ,比如:线程池、缓存、对话框、处理偏好设置和注册表的对象、日志对象、充当打印机、显卡等设备的驱动程序的对象。
对于这些对象,如果实例化超过一个对象,就容易出现问题(程序的行为异常、资源使用过量或者不一致的结果。)