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.