Go - Pattern
前置原则
在实际真正需要的时候才对程序进行
抽象
- 不要为了抽象而抽象
- 不要为了使用接口而使用接口
解耦或者抽象是有成本的
- 造成
运行效率的下降 - 影响代码的
可读性
组合
- 如果
C++和Java是关于类型层次结构和类型分类的语言,那么Go是关于组合的语言 组合是 Go 的重要设计哲学之一,而正交性则为组合哲学的落地提供了方便的条件
正交
- 在计算机技术中,正交性用于表示某种
不相依赖性或者解耦性 - 编程语言中的
语法元素和语言特性也存在正交的情况,通过将这些正交的特性组合起来,可以实现更为高级的特性
Go 在语言设计层面提供的正交的语法元素
| Key | Value |
|---|---|
类型定义是正交的 |
无类型体系,没有父子类的概念 |
方法和类型是正交的 |
方法的本质只是将 receiver 参数作为第一个参数的函数而已 |
接口与其它语言元素是正交的 |
接口与它的实现者之间没有显式关联 |
方式
垂直组合
用在将多个类型通过
类型嵌入的方式实现新类型的定义
- 传统的
OOP编程语言大多是通过继承的方式构建出类型体系 - Go 没有类型体系的概念,Go 通过
类型的组合而不是继承让单一类型承载更多的功能 -垂直组合 - 通过垂直组合定义的新类型与被嵌入的类型之间没有所谓的
父子关系- 没有
Type Casting - 被嵌入的类型也不知道
外部类型的存在
- 没有
- 调用方法时,方法的匹配取决于
方法名,而非类型 垂直组合更多应用于类型定义,本质是一种类型组合,属于类型之间的耦合方式
通过嵌入
接口构建接口
1 | // 实现接口行为聚合 |
通过嵌入
接口构建结构体
1 | // 用于快速构建满足某一接口的结构体类型,常用于单元测试 |
通过嵌入
结构体构建结构体
1 | type A struct { |
- 在
结构体中嵌入接口或者其它结构体,都是委派模式(delegate)的一种应用 - 对新结构体类型的方法调用,可能会被
委派给该结构体内部嵌入的结构体实例
水平组合
依赖接口,而非依赖实现
1 | func Save(w io.Writer, data []byte) error { |
1 | func TestSave(t *testing.T) { |
模式
基本模式
接受
接口类型参数的函数或者方法
1 | type YourInterfaceType interface { |
创建模式
接受接口,返回结构体 -
accept interfaces, return structs
NewXXX- 大多数包含接口类型字段的结构体的实例化,都可以使用创建模式实现
Case 1
1 | type Cond struct { |
1 | type Locker interface { |
Case 2
1 | type Logger struct { |
1 | type Writer interface { |
包装器模式
返回值类型与参数类型相同
1 | func YourWrapperFunc(param YourInterfaceType) YourInterfaceType |
实现对输入参数的类型的包装
在不改变被包装类型的定义的情况下,返回具备新功能特性、实现相同接口类型的新类型
1 | func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} } |
1 | func main() { |
支持
链式调用
1 | type CapitalizedReader struct { |
适配器模式
核心为
适配器函数类型(Adaptor Function Type),它是一个工具类型
将一个满足特定
函数签名的普通函数,显式转换成自身类型的实例,转换后的实例同时也是某个接口类型的实现者
1 | // greetings 的类型为 func(http.ResponseWriter, *http.Request) |
1 | type HandlerFunc func(ResponseWriter, *Request) |
1 | func ListenAndServe(addr string, handler Handler) error { |
适配器函数类型(http.HandlerFunc)将一个
普通函数转型为实现了 http.Handler 接口的类型的实例
中间件模式
在 Go Web 中,中间件常指实现了
http.Handler接口的http.HandlerFunc类型实例
中间件模式结合了
包装器模式和适配器模式
1 | func validateAuth(s string) error { |
所谓中间件,本质为一个
包装函数,最里面利用了适配器函数类型(http.HandlerFunc)
1 | $ curl 127.1:7777 |
空接口
尽量避免使用
空接口作为函数参数类型
- 空接口
不提供任何信息 - 虽然 Go
无需显式 implement 接口,但必须声明接口- 可以让种类繁多的类型与接口匹配,包括无法编辑的
库代码 - 兼顾
安全性和灵活性,而安全性由 Go编译器来保证(需要接口类型的定义)
- 可以让种类繁多的类型与接口匹配,包括无法编辑的
1 | type Reader interface { |
- Go 编译器通过
解析接口定义,得到接口的名字以及方法集合- 在为该接口类型参数
赋值时,编译器会根据这些信息对实参进行检查
- 在为该接口类型参数
- 如果
函数或者方法的参数类型为空接口(interface{})- 此时并没有为
编译器提供关于传入实参数据的任何信息 - 从而失去
静态类型语言类型安全检查的保护屏障- 可能会到运行时才能发现错误
- 此时并没有为
- 因此,尽量
抽象出带有一定行为契约的接口,并将其作为函数或者方法的参数类型 - 使用空接口作为函数或者方法的参数类型的场景
- 面对
未知类型的数据,借用了具有『泛型能力』的interface{} - Go 泛型落地后,
interface{}可以被慢慢替代,正如 Java 中很少直接使用 Object 一样
- 面对
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.













