Go语言设计模式(一)单例模式和设计原则

73 阅读5分钟

设计模式六大原则

1.开闭原则:

开闭原则是指对扩展开放,对修改关闭.在程序需要进行拓展时,不能修改所有的代码,实现一个热插拔的效果.从而使程序扩展性更好,易于维护和升级.

2.里氏代换原则:

任何基类可以出现的地方,子类一定可以出现,是继承复用的基石.只有当子类可以替换基类且软件的组件功能不受影响.基类才能真正被复用.使子类能够在基类的基础上增加新的行为.是对开闭原则的补充.开闭原则的步骤是抽象化,而基类和子类的继承关系是抽象化的具体实现.

3.依赖倒转原则:

是开闭原则的基础.具体内容是针对接口编程.依赖于抽象,而不依赖于实体.

4.接口隔离原则:

是指使用多个隔离接口比使用单个接口隔离更好.还有另一个意义是降低类之间的耦合度.

5.迪米特法则:

又称为最少知道原则,是指一个实体应当尽量少的与其他实体之间发生相互作用,使系统功能模块相对独立.

6.合成复用原则:

指通过关联关系(包括组合关系和聚合关系)在一个新对象中使用一些已有的对象,使它们成为新对象的一部分.新对象通过委托已有对象的方法实现复制.

7.单例模式:

单例模式的类提供了一种访问其唯一对象的方法,该对象可以直接被访问,无须实例化.单例模式保证了一个类的对象只存在一个.同时维护了一个对象的全局访问点.

7.1使用场景:

如果程序中的某个类对于所有客户端都只有一个可用实例.

想要严格控制全局变量.

开发网站的计数器.

开发程序的日志记录.共享的日志文件一直处于打开状态.所以只能有一个实例进行操作.

web应用程序读取配置文件,因为配置文件是共享资源.

设计数据库连建池.

设计线程池.

7.2单例实现:
7.2.1懒汉式单例模式:

在创建对象时不直接创建对象,在加载配置文件时才创建对象.


// 单例类.
type singleton struct {
	Value int
}

// 私有变量.
var instance *singleton

// 获取单例对象.
func getInstanceSingleton() *singleton {
	if instance == nil {
		instance := new(singleton)
	}
	return instance
}

在并发条件下,协程并不安全可能会创建多个对象.

7.2.2Sync.Mutex懒汉式:

import "sync"

// 单例类.
type singleton struct {
	Value int
}

// 私有变量.
var instance *singleton

// 锁对象.
var mutex sync.Mutex

// 获取单例对象.
func getInstanceSingleton() *singleton {
	mutex.Lock()
	defer mutex.Unlock()
	if instance == nil {
		instance := new(singleton)
	}
	return instance
}

保证了协程并发安全,但是每次都要加锁解锁,性能上不够高效.

7.2.3饿汉式单例模式:

在创建对象时,不判断创建的对象是否为空,直接创建对象.饿汉单例是并发安全的.唯一的缺点是导入包的时候会创建对象.并且创建的对象会持续存储在内存中.


import "fmt"

type singleton2 struct {
}

var instance2 *singleton2

func init() {
	if instance2 == nil {
		instance2 = new(singleton2)
		fmt.Println("创建了单个实例")
	}
}

// 提供实例函数.
func getInstance() *singleton2 {
	return instance2
}

7.2.4双重检查单例模式:


import "sync"

// 单例类.
type singleton struct {
	Value int
}

// 私有变量.
var instance *singleton

// 锁对象.
var mutex sync.Mutex

// 获取单例对象.对象为空时,进行加锁.在获取对象就不需要加速了.
func getInstanceSingleton() *singleton {

	if instance == nil {
		mutex.Lock()
		instance := new(singleton)
		mutex.Unlock()
	}
	return instance
}

7.2.5sync.Once单例模式:

是Go标准库提供的函数,只执行一次实现.通常用于单例模式,如初始化配置 保持数据库连接等.作用和init函数类似.但有区别.init函数会在其所在的包首次被加载时执行,如果被加载的包不立即被使用,既浪费了内存空间又延长了加载时间.

sync.Once可以在代码的任意位置被初始化和调用,在并发场景中是并发安全的.


import (
	"fmt"
	"sync"
)

var one sync.Once

var instance3 *singleton3

type singleton3 struct {
}

func getSingleton3() *singleton3 {
	one.Do(func() {
		instance3 = new(singleton3)
		fmt.Println("创建单个实例")
	})
	return instance3
}
7.3实践:

import (
	"fmt"
	_ "gomodule/pubsub"
	"sync"
)

var lock = &sync.Mutex{}

type singletonTest struct {
}

var instance *singletonTest

// 获取实例.
func getSingletonTest() *singletonTest {
	if instance == nil {
		lock.Lock()
		defer lock.Unlock()
		if instance == nil {
			fmt.Println("创建单个实例")
			instance = new(singletonTest)
		} else {
			fmt.Println("已创建单个实例")
		}
	} else {
		fmt.Println("已创建单个实例")
	}
	return instance
}
func main() {
	for i := 0; i < 10; i++ {
		go getSingletonTest()
	}
	fmt.Scanln()
}

*** 执行结果:***

7.4单例模式优点:

单例模式可以扩展为工厂模式.

由于在系统内存中只有一个对象,因此需要频繁创建和销毁对象的系统,可以提高性能.

7.5单例模式缺点:

单例模式不是抽象的,可扩展性低.

滥用单例模式会产生一些负面问题.为了节省资源,如果使用单例模式设计数据库对象,可能会导致共享连接池对象没有被释放.

知行合一,心外物.

如果大家喜欢我的分享的话,可以关注我的微信公众号

念何架构之路