Go语言编写简洁接口的最佳实践
由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!
接口允许我们在处理特定行为时将不同类型的对象视为同一种类型。它们是 Go 程序员工具箱中的核心工具,但新手开发者常常使用不当,导致代码难以阅读且充满 bug。
接口是方法签名的集合,它们在 Go 语言中实现了某种形式的多态性。
点击分享到 Twitter
接口概述
让我们以标准库为例,看看如何编写简洁的 Go 代码。错误处理接口很简单:
type error interface {
Error() string
}
根据定义,错误接口封装了任何定义了`Error()`方法、不接受任何参数且返回字符串的类型。例如,我们定义一个表示网络问题的结构体:
type networkProblem struct {
message string
code int
}
然后我们定义一个Error()方法:
func (np networkProblem) Error() string {
return fmt.Sprintf("network error! message: %s, code: %v", np.message, np.code)
}
现在,我们可以在任何接受错误的地方使用networkProblem结构体的实例。
func handleErr(err error) {
fmt.Println(err.Error())
}
np := networkProblem{
message: "we received a problem",
code: 404,
}
handleErr(np)
// prints "network error! message: we received a problem, code: 404"
保持界面小巧
如果你只能从这篇文章中记住一条建议,那就记住:保持接口简洁!接口的目的是定义准确表示某个实体所需的最小行为。
以下是一个较大的界面示例,它仍然是定义最小行为的一个很好的例子:
type File interface {
io.Closer
io.Reader
io.Seeker
Readdir(count int) ([]os.FileInfo, error)
Stat() (os.FileInfo, error)
}
http.File – https://golang.org/pkg/net/http/#pkg-overview
任何满足接口行为的类型都可以被 HTTP 包视为文件。这很方便,因为 HTTP 包不需要知道它处理的是磁盘上的文件、网络缓冲区还是简单的[]byte。
接口不应该了解满足类型的信息
接口应该定义其他类型必须满足哪些条件才能被归类为该接口的成员。它们不应该知道在设计时恰好满足该接口的任何类型。
例如,假设我们正在构建一个接口来描述定义一辆汽车所需的组件。
type car interface {
GetColor() string
GetSpeed() int
IsFiretruck() bool
}
`GetColor()`和`GetSpeed()`方法完全合理,它们的作用域仅限于车辆。`IsFiretruck ()`则是一种反模式。我们强制所有车辆都必须声明自己是否是消防车。为了让这种模式有意义,我们需要一长串可能的子类型列表。`IsPickup ()`、`IsSedan()`、`IsTank()` ……这没完没了!
相反,开发者应该利用类型断言的原生功能,在给定汽车接口实例时推导出底层类型。或者,如果需要子接口,可以这样定义:
type firetruck interface {
car
HoseLength() int
}
它继承了car 类所需的方法,并增加了一个额外的所需方法。
接口不是类
接口不是类,它们更精简。
接口没有构造函数或析构函数,因此不需要创建或销毁数据。
接口本质上不是层级式的,尽管有一些语法糖可以创建恰好是其他接口的超集的接口。
接口定义了函数签名,但不定义底层行为。这意味着,对于结构体方法而言,创建接口通常并不能使代码更简洁(DRY 原则,避免重复代码)。如果五个类型都满足 error 接口,那么它们都需要各自实现一个Error()函数。
感谢阅读
如有任何问题或意见,请在推特上联系我@wagslane 。
Dev.to 上的 Lane:wagslane
这篇文章《在 Go 中编写简洁接口的最佳实践》最初发表于Qvault。
文章来源:https://dev.to/wagslane/best-practices-for-writing-clean-interfaces-in-go-5c2j
