Go 语言入门指南:聊聊接口的使用 | 青训营

142 阅读4分钟

上一篇青训营文章聊到了Go语言中的接口,从Go运行时谈到了接口的nil误区,这次来聊一聊Go中接口具体的使用。

在使用接口之前,要知道一条前置原则:在实际真正需要的时候才对程序进行抽象。也就是 不要为了抽象而抽象

比如,假如只就实现一个简单的加法函数,就没必要再去定义一个接口,再去实现。

type Adder interface {
	Add(a ,b int) int
}

func Add(adder Adder,a,b int) int {
	return adder.Add(a,b)
}

如上面的代码,这样会造成代码冗余,过度设计。接口虽然可以实现 解耦,但它也会引起额外副作用,会造成运行效率的下降,也会影响代码的可读性。

Go语言中的组合设计哲学

Go语言之父Rob Pike曾说过: 如果C++和Java是关于类型层次结构和类型分类的语言,那么Go则是关于组合的语言。接口作为Go语言提供的具有天然正交性的语法元素,在Go程序的静态结构搭建与耦合设计中扮演着至关重要的角色,主要有两种组合方式。

垂直组合

Go语言通过类型嵌入实现垂直组合

通过嵌入接口构建接口

通过在接口定义中嵌入其他接口类型,实现接口行为聚合,组成大接口

如ReadWriter接口类型:

type ReadWriter interface {
    Reader
    Writer
}

通过嵌入接口构建结构体类型

type MyReader struct {
	io.Reader // underlying reader
	N int64   // max bytes remaining
}

通过嵌入结构体类型构建新结构体类型

在结构体中嵌入接口类型名和在结构体中嵌入其他结构体

水平组合

当我们通过垂直组合将一个个类型建立完毕后,应用骨架就已经搭建好了,接下来就需要一个连接骨架的关节,把骨架拼成一个完整可运行的应用。接口可以将各个类型水平组合连接在一起。通过接口,整个应用程序不再是一个个孤立的一部分,而是一幅完整的、有灵活性和扩展性的静态骨架结构。

接口的几种应用模式

基本模式

接受接口类型参数的函数或方法是水平组合的基本语法

func func(param IfaceType)

函数参数中的接口类型作为“关节”,支持将位于多个包中的多个类型与func函数连接到一起,共同实现函数功能。接口类型和它的实现者之间还满足了:依赖抽象、里氏替换原则、接口隔离等代码设计原则。

创建模式

type Cond struct {
    ... ...
    L Locker
}

func NewCond(l Locker) *Cond {
    return &Cond{L: l}
}

在这里,sync标准库cond。通过接收Locker接口,返回Cond接口体,实现创建一个Cond实例。大多数包含接口类型字段的结构体的实例化,都可以使用创建模式实现。

包装器模式

在基本模式的基础上,当返回值的类型与参数类型相同时,就是包装器模式,如标准库io库里的使用:

func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} }

type LimitedReader struct {
    R Reader // underlying reader
    N int64  // max bytes remaining
}

func (l *LimitedReader) Read(p []byte) (n int, err error) {
    // ... ...
}

适配器模式

适配器函数类型是一个辅助水平组合实现的“工具”类型, 它是一个类型,它可以将一个满足特定函数签名的普通函数,显式转换成自身类型的实例,转换后的实例同时也是某个接口类型的实现者。

如http库里的HandlerFunc:

func greeting(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "hello world")
}

func main() {
    http.ListenAndServe(":8080", http.HandlerFunc(greeting))
}

// HandlerFunc适配器
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

这里通过http.HandlerFunc这个适配器函数类型,将普通函数greeting快速转化为满足http.Handler接口的类型。

中间件

中间件就是包装模式和适配器模式结合的产物。

小结

主要介绍了接口的前置原则,两种组合以及几种模式。