缘起
最近复习设计模式
拜读谭勇德的<<设计模式就该这样学>>
本系列笔记拟采用golang练习之
单例模式
单例模式(Singleton Pattern)指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点,属于创建型设计模式。
_
三种常见单例模式
- 饿汉式单例
- 类/模块初始化时立即创建的静态全局单例
- 双重检查单例
- 提供获取单例的方法
- 在该方法中通过双重检查静态实例是否为null, 以延迟初始化全局单例
- 首次检查不加同步锁, 二次检查加同步锁, 以保证单例只创建一次
- 容器式单例
- 存在全局唯一的Bean容器
- 通过Bean的名称读取或设置与该名称绑定的bean单例
- 通过读写锁控制并发安全
- 常用于需要持有大量单例的场景, 如IOC容器
单元测试
singleton_test.go, 依次调用了三种单例的方法
package patternsimport "testing"import (sg "learning/gooop/creational_patterns/singleton")func Test_Singleton(t *testing.T) {fnTestSingleton := func(it sg.IDemoSingleton) {it.Hello()}fnTestSingleton(sg.HungrySingleton)fnTestSingleton(sg.GetDualCheckSingleton())ok, it := sg.DefaultBeanContaibner.GetBean("IDemoSingleton")if ok {fnTestSingleton(it.(sg.IDemoSingleton))}}
测试输出
$ go test -v singleton_test.go=== RUN Test_SingletontHungrySingleton.HellotDualCheckSingleton.HellotContainedSingleton.Hello--- PASS: Test_Singleton (0.00s)PASSok command-line-arguments 0.002s
IDemoSingleton.go
定义单例的接口
package singletontype IDemoSingleton interface {Hello()}
饿汉式单例
HungrySingleton.go演示如何实现一个饿汉式单例
package singletonimport ("fmt")type tHungrySingleton struct {}func newHungrySingleton() *tHungrySingleton {return &tHungrySingleton{}}func (me *tHungrySingleton) Hello() {fmt.Printf("tHungrySingleton.Hello\n")}var HungrySingleton IDemoSingleton = newHungrySingleton()
双重检查单例
DualCheckSingleton.go演示如何创建一个双重检查单例
package singletonimport ("fmt""sync")type tDualCheckSingleton struct {}func newDualCheckSingleton() *tDualCheckSingleton {return &tDualCheckSingleton{}}func (me *tDualCheckSingleton) Hello() {fmt.Printf("tDualCheckSingleton.Hello\n")}var gDualCheckSingleton IDemoSingleton = nilvar gSingletonLock = new(sync.Mutex)func GetDualCheckSingleton() IDemoSingleton {if gDualCheckSingleton == nil {gSingletonLock.Lock()defer gSingletonLock.Unlock()if gDualCheckSingleton == nil {gDualCheckSingleton = newDualCheckSingleton()}}return gDualCheckSingleton}
容器式单例
ContainedSingleton.go演示如何通过Bean容器持有大量Bean单例. Bean容器本身是一个饿汉式单例.
package singletonimport ("errors""fmt""sync")type IBeanContainer interface {GetBean(string) (bool, interface{})SetBean(string, interface{}) error}type tBeanContainer struct {mBeans map[string]interface{}mRWMutex *sync.RWMutex}func newBeanContainer() *tBeanContainer {return &tBeanContainer{mBeans: make(map[string]interface{}, 16),mRWMutex: new(sync.RWMutex),}}func (me *tBeanContainer) GetBean(name string) (bool, interface{}) {me.mRWMutex.RLock()defer me.mRWMutex.RUnlock()it, ok := me.mBeans[name]if ok {return true, it} else {return false, nil}}func (me *tBeanContainer) SetBean(name string, it interface{}) error {me.mRWMutex.Lock()defer me.mRWMutex.Unlock()if _,ok := me.mBeans[name];ok {return errors.New(fmt.Sprintf("bean with name %s already exists", name))}me.mBeans[name] = itreturn nil}type tContainedSingleton struct {}func (me *tContainedSingleton) Hello() {fmt.Printf("tContainedSingleton.Hello\n")}func newContainedSingleton() IDemoSingleton {return &tContainedSingleton{}}var DefaultBeanContaibner = newBeanContainer()func init() {DefaultBeanContaibner.SetBean("IDemoSingleton", newContainedSingleton())}
单例模式小结
单例模式的优点
(1)单例模式可以保证内存里只有一个实例,减少了内存的开销。
(2)可以避免对资源的多重占用。
(3)单例模式设置全局访问点,可以优化和共享资源的访问。
单例模式的缺点
(1)单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
(2)在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
(3)单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
