[Go] 我在工作中常用的 Golang 设计模式
责任链模式
含义: 指定顺序串行化地执行一系列的任务,前置的任务节点根据执行情况可以选择提前熔断流程或者正常向下执行.
场景: grpc 拦截器 和 gin 的中间件都是责任链模式
实现:
grpc 拦截器,责任链模式实现
原理:Interceptor 中的 handler 参数就指代的是下一个 Interceptor, 如果是最后一个 Interceptor, 那 handle 就是定义的 enhance hanlder。
type Handler func(ctx context.Context, req []string) ([]string, error)
type Interceptor func(ctx context.Context, req []string, handler Handler) ([]string, error)
func ChainInterceptors(interceptors []Interceptor) Interceptor {
if len(interceptors) == 0 {
return nil
}
return func(ctx context.Context, req []string, handler Handler) ([]string, error) {
return interceptors[0](ctx, req, GetNextChainHandler(interceptors, 0, handler))
}
}
func GetNextChainHandler(interceptors []Interceptor, index int, nextHandler Handler) Handler {
if index == len(interceptors)-1 {
return nextHandler
}
return func(ctx context.Context, req []string) ([]string, error) {
return interceptors[index+1](ctx, req, nextHandler)
}
}
下面是使用 grpc 拦截器的代码
// 用法
// enhanced final handler
var handler Handler = func(ctx context.Context, req []string) ([]string, error) {
req = append(req, "enhance final handler")
return req, nil
}
var interceptor1 Interceptor = func(ctx context.Context, req []string, handler Handler) ([]string, error) {
fmt.Println("interceptor1 preprocess...")
req = append(req, "interceptor1_preprocess")
resp, err := handler(ctx, req)
fmt.Println("interceptor1 postprocess")
resp = append(resp, "interceptor1_postprocess")
return resp, err
}
var interceptor2 Interceptor = func(ctx context.Context, req []string, handler Handler) ([]string, error) {
fmt.Println("interceptor2 preprocess...")
req = append(req, "interceptor2_preprocess")
resp, err := handler(ctx, req)
fmt.Println("interceptor2 postprocess")
resp = append(resp, "interceptor2_postprocess")
return resp, err
}
func main() {
chainedInterceptor := ChainInterceptors([]Interceptor{
interceptor1, interceptor2,
})
resp, _ := chainedInterceptor(context.Background(), nil, handler)
fmt.Printf("resp: %+v", resp)
}
gin 中间件 责任链模式实现, 与 grpc 拦截器层层传递不同的是,gin 的中间件是由 gin 本身管理的,需要 .Next
, .Abort
去熔断或者终止。
package main
const MaxHandlersCnt = 100
type Handler func(ctx *Context, req map[string]interface{}) (map[string]interface{}, error)
type HandlerManager struct {
index int
handlers []Handler
}
func NewHandlerManager(handlers ...Handler) *HandlerManager {
return &HandlerManager{
index: -1,
handlers: handlers,
}
}
func (c *Context) Next(req map[string]interface{}) {
c.index++
for c.index < len(c.handlers) && c.index <= MaxHandlersCnt {
c.handlers[c.index](c, req)
c.index++
}
}
func (c *Context) Abort() {
c.index = MaxHandlersCnt
}
单例模式
含义: 结构体在整个进程中存在唯一的示例来供外部反复调用
场景:
- 只允许存在一个实例的类,比如全局统一的监控或者统计模块
- 实例化时很耗费资源的类,比如连接池、第三方库的客户端等
- 负责业务领域分层的类,比如 domain、service、repo 等
实现:
在单例模式的实现上,可以分为饿汉式和懒汉式两种类型:
• 饿汉式:应用一启动,就完成单例的初始化工作
• 懒汉式:手动执行单例的初始化工作
首先是饿汉模式
var s *singleton
type Singleton interface {
Work()
}
type singleton struct{}
func init() {
s = &singleton{}
}
func (s *singleton) Work() {
}
func GetSingletonIns() Singleton {
return s
}
然后是懒汉模式
懒汉模式的第一种做法是锁 + double check.
package main
import "sync"
var (
s Singleton
mux sync.Mutex
)
type Singleton interface {
Work()
}
type singleton struct{}
func newSingleton() Singleton {
return &singleton{}
}
func (s *singleton) Work() {
}
func GetSingletonIns() Singleton {
if s != nil {
return s
}
mux.Lock()
defer mux.Unlock()
// double check
if s != nil {
return s
}
s = newSingleton()
return s
}
懒汉模式的第二种做法是使用 sync.Once, 原理也是锁 + double check, Once 结构体有一个 uint32 类型的 done 字段,表示 once 保护的函数是否已经被执行过。( 通过 atomic.StoreUint32(&o.done, 1) 对 done 字段进行原子操作的修改 )
package main
import "sync"
var (
s Singleton
once sync.Once
)
type Singleton interface {
Work()
}
type singleton struct{}
func newSingleton() Singleton {
return &singleton{}
}
func (s *singleton) Work() {
}
func GetSingletonIns() Singleton {
once.Do(func() {
s = newSingleton()
})
return s
}
工厂模式
含义: 在类和使用方之间添加一个工厂类中间层,实现了代码的防腐和解耦
场景:
- 业务方法和类之间产生过高的耦合度
- 类的定义发生变更,代码中类的构造流程都需要改动
实现:
简单工厂模式: 工厂模式中最简单直观的实现方式,有很好的切面效果,但是类扩展时无法满足开闭原则
工厂方法模式: 一个类对应一个工厂类,工程类实现工厂接口, 存在代码冗余,但可以在类扩展时满足开闭原则
抽象工厂模式: 工厂方法模式针对的是同一类或同等级产品(美式,拿铁,卡布奇诺),而抽象工厂模式针对的是多种类的产品设计(星巴克的拿铁,瑞幸的拿铁...)
首先是是简单工厂:
工厂负责生成咖啡,入口是下面的 CoffeeMaker
函数。
package main
import "fmt"
type Coffee interface {
}
type americanoCoffee struct {
}
type latteCoffee struct {
}
type cappuccinoCoffee struct {
}
func NewAmericanoCoffee() Coffee {
return &americanoCoffee{}
}
func NewLatteCoffee() Coffee {
return &latteCoffee{}
}
func NewCappuccinoCoffee() Coffee {
return &cappuccinoCoffee{}
}
type coffeeMaker func() Coffee
type CoffeeFactory struct {
coffeeList map[string]coffeeMaker
}
func NewCoffeeFactory() *CoffeeFactory {
return &CoffeeFactory{
coffeeList: map[string]coffeeMaker{
"americano": NewAmericanoCoffee,
"latte": NewLatteCoffee,
"cappuccino": NewCappuccinoCoffee,
},
}
}
// 工厂入口
func (f *CoffeeFactory) MakeCoffee(typ string) (Coffee, error) {
coffeeMaker, ok := f.coffeeList[typ]
if !ok {
return nil, fmt.Errorf("coffee type %s is not exist", typ)
}
return coffeeMaker, nil
}
简单工厂有个问题,如果在加一种类型的咖啡,会直接修改 coffeeList, 不遵循开闭原则。 所以有个下面的工厂方法模式,每个具体的咖啡类后都有一个工厂类。这样做的好处是,以后再添加一种类型的咖啡,比如猫屎咖啡,只需要加猫屎咖啡类和其工厂类就可以了。
package main
type Coffee interface {
}
type CoffeeFactory interface {
CreateCoffee() Coffee
}
type americanoCoffee struct {
}
// 美式咖啡的工厂类
type AmericanoFactory struct {
}
func NewAmericanoFactory() CoffeeFactory {
return &AmericanoFactory{}
}
func (a *AmericanoFactory) CreateCoffee() Coffee {
return americanoCoffee{}
}
type latteCoffee struct {
}
// 拿铁的工厂类
type LatteFactory struct {
}
func NewLatteFactory() CoffeeFactory {
return &AmericanoFactory{}
}
func (a *LatteFactory) CreateCoffee() Coffee {
return americanoCoffee{}
}
type cappuccinoCoffee struct {
}
// 卡布奇诺的工厂类
type CappuccinoFactory struct {
}
func NewCappuccinoFactory() CoffeeFactory {
return &CappuccinoFactory{}
}
func (a *CappuccinoFactory) CreateCoffee() Coffee {
return cappuccinoCoffee{}
}
func NewCapCoffee() Coffee {
return &americanoCoffee{}
}
func NewLatteCoffee() Coffee {
return &latteCoffee{}
}
func NewCappuccinoCoffee() Coffee {
return &cappuccinoCoffee{}
}
工厂方法模式针对的是同一类或同等级产品(美式,拿铁,卡布奇诺只有一层咖啡的维度),而抽象工厂模式针对的是多种类的产品设计(星巴克的拿铁,瑞幸的拿铁...)也就是增添了品牌方的维度。
美式咖啡 AmericanoCoffee
,拿铁咖啡 LatteCoffee
是接口,创建拿铁和美式的工厂(CoffeeFactory
)也是接口,不同品牌方(星巴克,瑞幸),对美式 (StarbucksAmericanoCoffee
),拿铁,以及他们的工厂接口(StarbucksCafeFactory
)做实现。
package main
type CoffeeFactory interface {
CreateAmericanoCoffee() AmericanoCoffee
CreateLatteCoffee() LatteCoffee
}
type AmericanoCoffee interface{}
type LatteCoffee interface{}
// 星巴克美式
type StarbucksAmericanoCoffee struct {
}
func NewStarbucksAmericanoCoffee() AmericanoCoffee {
return &StarbucksAmericanoCoffee{}
}
// 星巴克拿铁
type StarbucksLatteCoffee struct {
}
func NewStarbucksLatteCoffee() LatteCoffee {
return &StarbucksLatteCoffee{}
}
// 星巴克品牌实现
type StarbucksCafeFactory struct{}
func (s *StarbucksCafeFactory) CreateAmericanoCoffee() AmericanoCoffee {
return NewStarbucksAmericanoCoffee()
}
func (s *StarbucksCafeFactory) CreateLatteCoffee() LatteCoffee {
return NewStarbucksLatteCoffee()
}
如果再加一个品牌方,只需要增加对美式和拿铁的接口实现(上面代码例子中的 StarbucksAmericanoCoffee
, StarbucksLatteCoffee
) 以及品牌方工厂实现(上面代码例子中的 StarbucksCafeFactory
)。
装饰器模式
含义: 在不改变其原有的结构和功能为对象添加新功能的模式其实就叫做装饰器模式,装饰比继承更加灵活,可以实现装饰者和被装饰者之间松耦合
功能:功能之间耦合太深,并且该功能可能要随时新增或者撤销。
实现:
圆形和矩形都是形状(接口 Shape
), 都可以实现画一个圆形/矩形的功能,在装饰器模式的基础上,在新增一个给圆形/矩形的边框画成绿色。如果不用装饰器,你可以直接在圆形/矩形的 Draw 方法里写该功能,但是耦合度高。要根据自己的业务需求来选择合适的模式。
package main
import "fmt"
type Shape interface {
Draw()
}
type Circle struct {
}
func (c *Circle) Draw() {
fmt.Println("绘制圆形")
}
type Rectangle struct {
}
func (c *Rectangle) Draw() {
fmt.Println("绘制矩形")
}
// 装饰器声明
type DecoratorShape Shape
func NewColorfulShape(s Shape) DecoratorShape {
return s
}
type RedColorFullShape struct {
DecoratorShape
}
func NewRedColorFullShape(s DecoratorShape) DecoratorShape {
return &RedColorFullShape{s}
}
func (r *RedColorFullShape) Draw() {
r.DecoratorShape.Draw()
fmt.Println("圆形/矩形边框画成了红色")
}
观察者模式
场景:
- mq 发布订阅
- etcd watcher 功能
在观察者模式中,核心的角色包含三类:
- Observer:观察者.
- Event:事物的变更事件. 其中 Topic 标识了事物的身份以及变更的类型,Val 是变更详情
- EventBus:事件总线, 负责维护管理观察者,并且在事物发生变更时,将情况同步给每个观察者.
首先定义这三个角色:
type Event struct {
Topic string
Val interface{}
}
type Observer interface {
OnChange(ctx context.Context, e *Event) error
}
type EventBus interface {
Subscribe(topic string, o Observer)
UnSubscribe(topic string, o Observer)
Publish(ctx context.Context, e *Event)
}
实现 Observer
type SimpleObserver struct {
name string
}
func NewSimpleObserver() Observer {
return &SimpleObserver{}
}
func (s *SimpleObserver) OnChange(ctx context.Context, e *Event) error {
fmt.Printf("observer: %s, event key: %s, event val: %v", s.name, e.Topic, e.Val)
return nil
}
实现 BaseEventBus
type BaseEventBus struct {
mux sync.RWMutex
observers map[string]map[Observer]struct{}
}
func NewBaseEventBus() *BaseEventBus {
return &BaseEventBus{
observers: make(map[string]map[Observer]struct{}),
}
}
func (b *BaseEventBus) Subscribe(topic string, o Observer) {
b.mux.Lock()
defer b.mux.Unlock()
_, ok := b.observers[topic]
if !ok {
b.observers[topic] = make(map[Observer]struct{})
}
b.observers[topic][o] = struct{}{}
}
func (b *BaseEventBus) UnSubscribe(topic string, o Observer) {
b.mux.Lock()
defer b.mux.Unlock()
delete(b.observers[topic], o)
}
实现 SyncEventBus
在同步模式下,采用串行遍历的方式对每个观察者进行通知,并对处理流程中遇到的错误进行聚合, 统一处理.
type SyncEventBus struct {
*BaseEventBus
}
func NewSyncEventBus(base *BaseEventBus) EventBus {
return &SyncEventBus{
base,
}
}
// 同步 event bus
func (s *SyncEventBus) Publish(ctx context.Context, e *Event) {
s.mux.RLock()
subscribers := s.observers[e.Topic]
s.mux.RUnlock()
errs := make(map[Observer]error)
for subscriber := range subscribers {
if err := subscriber.OnChange(ctx, e); err != nil {
errs[subscriber] = err
}
}
// 自定义错误处理
// s.handleErr(ctx, errs)
}
实现 AsyncEventBus, 异步通知观察者,错误由后台的错误协程异步管理。
type AsyncEventBus struct {
*BaseEventBus
errC chan *ObserverWithErr
ctx context.Context
stop context.CancelFunc
}
type ObserverWithErr struct {
o Observer
err error
}
func NewAsyncEventBus(base *BaseEventBus) EventBus {
asyncBus := &AsyncEventBus{
BaseEventBus: base,
}
asyncBus.ctx, asyncBus.stop = context.WithCancel(context.Background())
go asyncBus.handleErr()
return asyncBus
}
func (s *AsyncEventBus) Publish(ctx context.Context, e *Event) {
s.mux.RLock()
subscribers := s.observers[e.Topic]
s.mux.RUnlock()
for subscriber := range subscribers {
// shadow 避免并发问题
subscriber := subscriber
go func() {
if err := subscriber.OnChange(s.ctx, e); err != nil {
select {
case <-s.ctx.Done():
case s.errC <- &ObserverWithErr{
err: err,
o: subscriber,
}:
}
}
}()
}
}
func (s *AsyncEventBus) Stop() {
s.stop()
}
func (s *AsyncEventBus) handleErr() {
for {
select {
case <-s.ctx.Done():
return
case respErr := <-s.errC:
// 处理 err
fmt.Printf("observer: %v, err: %v", respErr.o, respErr.err)
}
}
}
构造者模式
含义: 类实例化的过程,初始化类属性。
场景: 很多第三方库当中,大家都见过这样的代码 NewRocketMQ(withHost('localhost'))
这就是构造者模式的思想。
实现:
package main
import "fmt"
type Options struct {
host string
username string
password string
}
type Option func(opts *Options)
func WithHost(host string) Option {
return func(opts *Options) {
opts.host = host
}
}
func WithPassword(password string) Option {
return func(opts *Options) {
opts.password = password
}
}
func WithUsername(username string) Option {
return func(opts *Options) {
opts.username = username
}
}
// 兜底默认值
func repairOptions(opts *Options) {
if opts.username == "" {
opts.username = "root"
}
}
func NewOptions(opts ...Option) *Options {
optionsIns := &Options{}
for _, opt := range opts {
opt(optionsIns)
}
repairOptions(optionsIns)
return optionsIns
}
// 应用 options 构造者模式
func main() {
opt := NewOptions(WithHost("localhost"), WithPassword("654321"))
fmt.Printf("options %+v", opt)
}