核心篇主要涵盖接口类型语法与 Go 原生提供的三个并发原语(Goroutine、channel 与 select),之所以将它们放在核心语法的位置,是因为它们不仅代表了 Go 语言在编程语言领域的创新,更是影响 Go 应用骨架(Application Skeleton)设计的重要元素。
在接下的三讲中,我们将系统学习 Go 语言的接口类型,围绕接口类型的基础知识与接口定义的惯例、接口类型的内部表示以及接口的应用模式这三方面内容进行讲解。在这一讲中,我们先来学习一下接口类型的基础知识部分。
认识接口类型
在前面的学习中,我们曾不止一次接触过接口类型,对接口类型也有了一些粗浅的了解。我们知道,接口类型是由 type 和 interface 关键字定义的一组方法集合,其中,方法集合唯一确定了这个接口类型所表示的接口。下面是一个典型的接口类型 MyInterface 的定义:
type MyInterface interface {
M1(int) error
M2(io.Writer, ...string)
}
接口类型一旦被定义后,它就和其他 Go 类型一样可以用于声明变量,比如:
var err error // err是一个error接口类型的实例变量
var r io.Reader // r是一个io.Reader接口类型的实例变量
这些类型为接口类型的变量被称为接口类型变量,如果没有被显式赋予初值,接口类型变量的默认值为 nil。如果要为接口类型变量显式赋予初值,我们就要为接口类型变量选择合法的右值。
Go 规定:如果一个类型 T 的方法集合是某接口类型 I 的方法集合的等价集合或超集,我们就说类型 T 实现了接口类型 I,那么类型 T 的变量就可以作为合法的右值赋值给接口类型 I 的变量。
如果一个变量的类型是空接口类型,由于空接口类型的方法集合为空,这就意味着任何类型都实现了空接口的方法集合,所以我们可以将任何类型的值作为右值,赋值给空接口类型的变量。
空接口类型的这一可接受任意类型变量值作为右值的特性,让他成为 Go 加入泛型语法之前唯一一种具有“泛型”能力的语法元素,包括 Go 标准库在内的一些通用数据结构与算法的实现,都使用了空类型interface{}作为数据元素的类型,这样我们就无需为每种支持的元素类型单独做一份代码拷贝了。
Go 语言还支持接口类型变量赋值的“逆操作”,也就是通过接口类型变量“还原”它的右值的类型与值信息,这个过程被称为“类型断言(Type Assertion)”。类型断言通常使用下面的语法形式:
v, ok := i.(T)
尽量定义“小接口”
接口类型的背后,是通过把类型的行为抽象成契约,建立双方共同遵守的约定,这种契约将双方的耦合降到了最低的程度。和生活工作中的契约有繁有简,签署方式多样一样,代码间的契约也有多有少,有大有小,而且达成契约的方式也有所不同。 而 Go 选择了去繁就简的形式,这主要体现在以下两点上:
隐式契约,无需签署,自动生效
Go 语言中接口类型与它的实现者之间的关系是隐式的,不需要像其他语言(比如 Java)那样要求实现者显式放置“implements”进行修饰,实现者只需要实现接口方法集合中的全部方法便算是遵守了契约,并立即生效了。
更倾向于“小契约”
这点也不难理解。你想,如果契约太繁杂了就会束缚了手脚,缺少了灵活性,抑制了表现力。所以 Go 选择了使用“小契约”,表现在代码上就是尽量定义小接口,即方法个数在 1~3 个之间的接口。Go 语言之父 Rob Pike 曾说过的“接口越大,抽象程度越弱”,这也是 Go 社区倾向定义小接口的另外一种表述。
Go 对小接口的青睐在它的标准库中体现得淋漓尽致,这里我给出了标准库中一些我们日常开发中常用的接口的定义:
// $GOROOT/src/builtin/builtin.go
type error interface {
Error() string
}
// $GOROOT/src/io/io.go
type Reader interface {
Read(p []byte) (n int, err error)
}
// $GOROOT/src/net/http/server.go
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(int)
}
小接口有哪些优势?
第一点:接口越小,抽象程度越高
第二点:小接口易于实现和测试
第三点:小接口表示的“契约”职责单一,易于复用组合
定义小接口,你可以遵循的几点
首先,别管接口大小,先抽象出接口。
第二,将大接口拆分为小接口。
最后,我们要注意接口的单一契约职责。
此文章为3月Day19学习笔记,内容来源于极客时间《Tony Bai · Go 语言第一课》。