设计模式
创建型模式
单例模式
Once.Do
被设计成严格保证传入的函数只执行一次,且当 Once.Do
返回时,保证抢占 Once 执行权成功的 goroutine 传入 Do 的函数已经执行完成。
type Once struct {
done uint32
m Mutex
}
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
源代码中使用 atomic.LoadUint32(&o.done) == 0
和o.done == 0
结合互斥锁实现了双重检测锁,将同时执行的 goroutine 阻塞在 Mutex 处直到抢夺成功的函数执行完成。
工厂模式
- 简单工厂:由于 Go 本身是没有构造函数的,一般而言我们采用 NewName 的方式创建对象/接口,当它返回的是接口的时候,其实就是简单工厂模式;
- 工厂方法:当对象的创建逻辑比较复杂,需要做各种初始化操作时使用,如阿里云接口调用凭证;
- 抽象工厂:创建一系列相关或相互依赖的对象,而无需指定其具体类;
- DI 容器:Go 生态中 wire 是代码生成实现依赖注入与管理;
建造者模式
当类中的属性比较多,或者存在必选或非必选的属性时,即类的创建参数复杂时,使用建造者模式。 在 Go 中可以使用 Functional Options 的方式进行复杂参数类的创建。
原型模式
原型模式定义一个通用克隆接口,该接口让你能够克隆对象,同时又无需将代码和对象所属类耦合。通常情况下,这样的接口中仅包含一个 Copy 方法,可以使用序列化与反序列化的方式深拷贝。
结构型模式
适配器模式
适配器可以对已有对象的接口进行修改,确保至少有两个类的接口不兼容:
- 一个无法修改(通常是第三方、遗留系统或者存在众多已有依赖的类)的功能性服务类。
- 一个或多个将受益于使用服务类的客户端类。
例如运维系统对阿里云和 AWS 提供的 SDK 进行统一封装,将两个接口统一。
代理模式
代理模式强调不改变原有接口的情况下引入附加类实现功能,通常用于做非需求性功能开发,如监控、统计、鉴权、限流、事务等。
- 动态代理:通过代码生成的方式完成;
- 静态代理:实现与原有类一模一样的接口。
装饰模式
不用继承而是使用高阶函数给原始类添加功能。
外观模式
为子系统提供一组统一的接口,定义一组高层接口让子系统更易用,封装底层实现,隐藏复杂性。
例如 Linux 提供的系统调用,或者门户网站直接提供验证码登录/注册等。
组合模式
现实业务中有着树形结构,将一组对象和组合对象按树组合,统一递归处理,简化逻辑。
享元模式
复用对象,节省内存,前提是享元对象是不可变对象,与对象池不同,对象池是为了节省创建和销毁的时间,享元模式是为了节省空间,整个生命周期被所有共享者使用。
行为型模式
观察者模式
在对象之间定义一对多的依赖,当一个对象状态改变时,所有依赖的对象都会收到通知。
模版方法
在父类中定义一个算法框架(业务逻辑),允许子类在不修改结构的情况重写算法的特定步骤。
策略模式
定义一族算法类,将每个算法封装起来,让它们可以相互替换,模版方法是基于继承,策略模式基于组合。
责任链模式
参考框架过滤器或者中间件。echo
状态模式
参考有限状态机。rrweb-replay
迭代器模式
将复杂的容器对象的遍历操作拆分出来,开发者不需要了解如何遍历,是需要使用容器提供的迭代器即可。参考 Python 的迭代器。
访问者模式
允许一个或者多个操作应用到一组对象上,解耦操作和对象本身,氛围 SingleDispatch 和 DoubleDispatch,参考 K8s 的 kubectl 实现。
备忘录模式
在不违背封装原则的前提下,捕获对象内部的一个状态,在其之外保存这个状态以便后续恢复。例如备份,防丢失,撤销等。
命令模式
将请求(命令)封装为一个对象,这样就可以使用不同的请求参数化其他对象,在 Go 中我们可以定义函数类型去实现:
// Command 命令
type Command func() error
// StartCommandFunc 返回一个 Command 命令
// 是因为正常情况下不会是这么简单的函数
// 一般都会有一些参数
func StartCommandFunc() Command {
return func() error {
fmt.Println("game start")
return nil
}
}
// ArchiveCommandFunc ArchiveCommandFunc
func ArchiveCommandFunc() Command {
return func() error {
fmt.Println("game archive")
return nil
}
}
中介模式
用一个单独的对象来维护一组对象的交互,避免对象之间直接交互。