设计模式

设计模式在go的开源项目和实战开发的总结

Posted by Liangjf on September 1, 2020

设计模式在go的开源项目和实战开发的总结

以下设计模式和开发设计技巧是在工作, 学习和阅读开源项目源码时的一些总结, 会定时更新, 增加新的总结.

函数选项模式

  • grpc-go,go-micro,etcd……N多开源项目使用了此模式,因为go没有默认参数,并且函数选项模式可以优雅的实现了默认参数结构体,并且通过函数对象来对参数赋值。一般是Options参数结构、Option函数对象、Service业务对象(持有Options, Init, Run)

goroutine管理者模式(sync.WaitGroup)

  • 通过sync.WaitGroup来封装一个结构XXX,暴露Do,内部创建一个goroutine来调用传入的函数,并且隐藏Add和defer Done,在最终通过XXX调用Wait,就不会造成goroutine泄露和统一管理goroutine,方便做一些统计信息(见于nsq中nsqd源码)

管道(pipe)

  • 通过不同的chan,构成goroutine链式结构,模拟流水线工作,不同goroutine负责不同的工作,达到并发处理的目的。也成菊花链, 哈哈~~~
  • 用来控制goroutine的并发数,防止一下子创建太多goroutine,耗尽资源。一般是很高并发时使用。其实更好的是用专业的限流中间件,比如Hystrix, juju/ratelimit

装饰模式

  • http请求的handle的middleware函数装饰真正的handle,实现拦截鉴权,打log等功能(见于http中间件)
  • 自定义tcp协议,通过frame结构封装net.Conn,实现对conn的协议的装包和拆包(见于rpc)

建造者模式

  • go-micro实现自定义发现注册中心时需要实现Reslove接口,传入Build函数,把复杂的对象构造由用户定义,内部直接使用(见于go-micro的服务注册发现插件)

适配模式

  • 常见于框架的升级,新接口/新函数为了向后兼容
  • 接入第三方接口时,字段结构和自身业务可能不匹配,会自定义接口来封装适配自己的业务字段结构
  • 内部和外部数据流交互时,可以通过适配模式来定义函数进行匹配转换
  • 借用go的鸭子接口类型,非侵入式接口特点,可以实现的比较优雅

单例模式

  • 常用于全局只需要唯一实例的情况:数据库对象,redis对象,配置对象config,日志对象log, 全局序列号生成器等等(用go特色的sync.Once实现)

工厂模式

  • 在go中,Newxxx就是简单工厂模式了,可见,在go中是提倡使用此模式的
  • 工厂模式,可以在orm框架可见, 用于底层数据库类型的创建和切换, 底层只需要调用标准库的sql.DB就行了

原型模式

  • 在go中比较少用了,直接用标准库的对象池

代理模式

  • 延迟处理和在实际操作前后进行钩子处理,比如web框架中有beego,提供prepare用于请求handle前先调用做一些鉴权,打log等处理。gin的中间件和Next实现代理handle。
  • orm操作也可以触发如insertBefore/insertAfter等钩子,这样的对象就是代理
  • 代理模式也常见于一些中间件,比如网关,有反向代理和正向代理,并且可以做一些鉴权,流量管控,灰度测试,流量分发等

观察者模式

  • web开发比较少见,gui开发或者游戏开发比较常见。
  • 可以应用在订单调度通知,任务状态变化, 需要广播给相关组件或者任务
  • 但在go web开发中,单进程使用观察者模式可能少见,一般是大型系统使用消息队列来实现消息/事件的广播和发布订阅

组合模式

  • 随处可见,基础到标准库的io.Reader io.Writer接口被io库,buffer库的组合
  • go中提倡小接口,大组合
  • go设计思想,没有继承,而是组成

策略模式

  • 常用模式,封装一系列算法使得算法可以互相替换。多用于对某个业务的不同算法实现,通过依赖注入的方式选择对应的具体实现。其实在go中,感觉这个模式没有那么清晰了,因为好的设计本来就应用依赖于接口,而不是实现,并且应该通过依赖注入,而不是对象内部持有实现
  • 在大话设计模式中,例子是多种打折计算算法。定义算法接口,不同打折计算算法实现接口,定义一个计算类(业务使用)包含接口字段,通过依赖注入来选择想使用的打折算法

桥接模式

  • 典型的不依赖具体,而依赖抽象(分离抽象和实现)的总结模式,go提倡的也是面向接口开发。

命令模式

  • 把具体命令封装到对象中使用
  • 工作池,任务队列中应用,打事件类型,数据和处理函数打包构造成一个对象,扔入工作池或者任务队列,等worker消费。达到解耦合并发处理的目的