缘起

最近复习设计模式
拜读谭勇德的<<设计模式就该这样学>>
本系列笔记拟采用golang练习之

单例模式

单例模式(Singleton Pattern)指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点,属于创建型设计模式。
_

三种常见单例模式

  • 饿汉式单例
    • 类/模块初始化时立即创建的静态全局单例
  • 双重检查单例
    • 提供获取单例的方法
    • 在该方法中通过双重检查静态实例是否为null, 以延迟初始化全局单例
    • 首次检查不加同步锁, 二次检查加同步锁, 以保证单例只创建一次
  • 容器式单例
    • 存在全局唯一的Bean容器
    • 通过Bean的名称读取或设置与该名称绑定的bean单例
    • 通过读写锁控制并发安全
    • 常用于需要持有大量单例的场景, 如IOC容器

单元测试

singleton_test.go, 依次调用了三种单例的方法

  1. package patterns
  2. import "testing"
  3. import (sg "learning/gooop/creational_patterns/singleton")
  4. func Test_Singleton(t *testing.T) {
  5. fnTestSingleton := func(it sg.IDemoSingleton) {
  6. it.Hello()
  7. }
  8. fnTestSingleton(sg.HungrySingleton)
  9. fnTestSingleton(sg.GetDualCheckSingleton())
  10. ok, it := sg.DefaultBeanContaibner.GetBean("IDemoSingleton")
  11. if ok {
  12. fnTestSingleton(it.(sg.IDemoSingleton))
  13. }
  14. }

测试输出

  1. $ go test -v singleton_test.go
  2. === RUN Test_Singleton
  3. tHungrySingleton.Hello
  4. tDualCheckSingleton.Hello
  5. tContainedSingleton.Hello
  6. --- PASS: Test_Singleton (0.00s)
  7. PASS
  8. ok command-line-arguments 0.002s

IDemoSingleton.go

定义单例的接口

  1. package singleton
  2. type IDemoSingleton interface {
  3. Hello()
  4. }

饿汉式单例

HungrySingleton.go演示如何实现一个饿汉式单例

  1. package singleton
  2. import (
  3. "fmt"
  4. )
  5. type tHungrySingleton struct {}
  6. func newHungrySingleton() *tHungrySingleton {
  7. return &tHungrySingleton{}
  8. }
  9. func (me *tHungrySingleton) Hello() {
  10. fmt.Printf("tHungrySingleton.Hello\n")
  11. }
  12. var HungrySingleton IDemoSingleton = newHungrySingleton()

双重检查单例

DualCheckSingleton.go演示如何创建一个双重检查单例

  1. package singleton
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. type tDualCheckSingleton struct {}
  7. func newDualCheckSingleton() *tDualCheckSingleton {
  8. return &tDualCheckSingleton{}
  9. }
  10. func (me *tDualCheckSingleton) Hello() {
  11. fmt.Printf("tDualCheckSingleton.Hello\n")
  12. }
  13. var gDualCheckSingleton IDemoSingleton = nil
  14. var gSingletonLock = new(sync.Mutex)
  15. func GetDualCheckSingleton() IDemoSingleton {
  16. if gDualCheckSingleton == nil {
  17. gSingletonLock.Lock()
  18. defer gSingletonLock.Unlock()
  19. if gDualCheckSingleton == nil {
  20. gDualCheckSingleton = newDualCheckSingleton()
  21. }
  22. }
  23. return gDualCheckSingleton
  24. }

容器式单例

ContainedSingleton.go演示如何通过Bean容器持有大量Bean单例. Bean容器本身是一个饿汉式单例.

  1. package singleton
  2. import (
  3. "errors"
  4. "fmt"
  5. "sync"
  6. )
  7. type IBeanContainer interface {
  8. GetBean(string) (bool, interface{})
  9. SetBean(string, interface{}) error
  10. }
  11. type tBeanContainer struct {
  12. mBeans map[string]interface{}
  13. mRWMutex *sync.RWMutex
  14. }
  15. func newBeanContainer() *tBeanContainer {
  16. return &tBeanContainer{
  17. mBeans: make(map[string]interface{}, 16),
  18. mRWMutex: new(sync.RWMutex),
  19. }
  20. }
  21. func (me *tBeanContainer) GetBean(name string) (bool, interface{}) {
  22. me.mRWMutex.RLock()
  23. defer me.mRWMutex.RUnlock()
  24. it, ok := me.mBeans[name]
  25. if ok {
  26. return true, it
  27. } else {
  28. return false, nil
  29. }
  30. }
  31. func (me *tBeanContainer) SetBean(name string, it interface{}) error {
  32. me.mRWMutex.Lock()
  33. defer me.mRWMutex.Unlock()
  34. if _,ok := me.mBeans[name];ok {
  35. return errors.New(fmt.Sprintf("bean with name %s already exists", name))
  36. }
  37. me.mBeans[name] = it
  38. return nil
  39. }
  40. type tContainedSingleton struct {}
  41. func (me *tContainedSingleton) Hello() {
  42. fmt.Printf("tContainedSingleton.Hello\n")
  43. }
  44. func newContainedSingleton() IDemoSingleton {
  45. return &tContainedSingleton{}
  46. }
  47. var DefaultBeanContaibner = newBeanContainer()
  48. func init() {
  49. DefaultBeanContaibner.SetBean("IDemoSingleton", newContainedSingleton())
  50. }

单例模式小结

单例模式的优点
(1)单例模式可以保证内存里只有一个实例,减少了内存的开销。
(2)可以避免对资源的多重占用。
(3)单例模式设置全局访问点,可以优化和共享资源的访问。
单例模式的缺点
(1)单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
(2)在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
(3)单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。