定义
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
需求分析
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
就如上面所提到的打印机,一台打印机一个时刻只有一个进程(实例)在运行,否则打印机会出现错乱。
解决方案
如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。
那么怎么让类自身负责保存它的唯一实例?Go有一个优雅的解决方案就是标准库里的sync.Once
。它是用来判断某个函数是否已经执行过一次,如果没有就执行,如果执行过就不会再执行这个函数。
package singleton
import (
"fmt"
"sync"
)
// 首字母小写,防止外部使用,私自实例化
type printer struct{
Counter int // 计数器,计算是第几次打印
Buf string // 要打印的内容的缓存
}
var (
once sync.Once // 来判断某个函数是否已经执行过一次,如果没有就执行,如果执行过就不会再执行
instance *printer // 实例
)
func (p *printer) MyPrint() {
if len(p.Buf) != 0 { // 只有缓存有东西时才打印内容
p.Counter++
fmt.Println(p.Counter, ".Printer is printing:", p.Buf)
p.Buf = "" // 打印完缓存置空
}
}
func (p *printer) SetContent(s string) {
p.Buf = s
}
func NewPrinter() *printer {
once.Do(func() {
instance = &printer{0, ""}
})
return instance
}
使用方式:
package main
import (
"fmt"
"studygo/pattern/singleton"
)
func main() {
printer := singleton.NewPrinter() // 第一个实例
printer.SetContent("hello")
printer.MyPrint()
printer1 := singleton.NewPrinter() // 第二个实例
printer1.SetContent("Hi")
printer1.MyPrint()
// 由于NewPrinter返回的是指针,所以我们可以简单的看下两个打印机地址是否相同
if printer1 == printer {
fmt.Println("They are the same printer")
}
}
结果:
1 .Printer is printing: hello
2 .Printer is printing: Hi
They are the same printer
从结果来看printer
和printer1
是一样的对象。可以从两个地方验证,结果前面的字母,因为打印机每次打印都会进行计数,如果重新生成一个打印机,那么计数就会从0开始,说明这两个打印机是同一个实例。另外生成的实例对象是一个指针,可以比较指针来判断来个实例的地址是否一样~!
参考文献:https://books.studygolang.com/go-patterns/
撩我?
搜索我的微信公众中号:Kyda