设计模式

创建型模式

单例模式

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) == 0o.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
	}
}

中介模式

用一个单独的对象来维护一组对象的交互,避免对象之间直接交互。