杂谈:正确使用Go接口
杂谈:正确使用Go接口
不要写Java风格的接口
由于Go实现一个接口只需要含有签名完全一致(包括receiver、args和retval)就行了,而不像Java中那样通过 implements 关键字来指定实现的接口类型,所以Go中不要写这样的代码:
package animals
type Animal interface {
Speaks() string
}
// implementation of Animal
type Dog struct{}
func (a Dog) Speaks() string { return "woof" }
package circus
import "animals"
func Perform(a animal.Animal) string { return a.Speaks() }
应该修正:
- 将
Animal改名为Speaker,缩小接口的语义 - 不要
Animal在包中定义animals,而是在使用点——包circus中定义
Postel定律
一般来说,函数接收接口,返回结构体。
func funcName(INTERFACETYPE) CONCRETETYPE
这不仅是通过接口来约束了 funcName 中的行为(不会进行除了接口定义的方法之外的操作,比如访问成员变量),同时也方便测试Mock——只需要传入一个Mock了接口的对象即可。
适度预定义接口
编程的形式相当自由——没有什么硬性规定。你当然可以预先定义一个接口。
预先定义接口通常会导致代码过度设计。但在某些情况下,你显然需要预先定义接口。我能想到几种情况:
- 密封接口(Sealed Interfaces)
- 抽象数据类型(Abstract Data Types)
- 递归接口(Recursive Interfaces)
密封接口(Sealed Interfaces)
密封接口是指具有未导出方法的接口。这意味着包外的用户无法创建实现该接口的类型。
package bar
type Fooer interface {
Foo()
sealed()
}
这样一来只有在 bar 包内才可以使用和创建 Fooer 类型的对象。
抽象数据类型(Abstract Data Types)
就是接口本来的用途。比如sort包:
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
如果你想使用这个sort包,你就必须实现接口的这3个方法
递归接口(Recursive Interfaces)
是一种“自举”的接口:
type Fooer interface {
Foo() Fooer
}
This pattern is useful for creating contexts to operate in.

浙公网安备 33010602011771号