接口定义
type 接口名 interface {
方法列表
}
接口类型声明中的这些方法所代表的就是该接口的方法集合。一个接口的方法集合就是它的全部特征
实现接口
对于任何数据类型,只要它的方法集合中完全包含了一个接口的全部特征(即全部的方法),那么它就一定是这个接口的实现类型。
怎样判定一个数据类型的某一个方法实现的就是某个接口类型中的某个方法呢?
两个充分必要条件,一个是“两个方法的签名需要完全一致”,另一个是“两个方法的名称要一模一样”。
duck typing
是一种无侵入式的接口实现方式。
这是程序设计中的一种类型推断风格,这种风格适用于动态语言(比如PHP、Python、Ruby、Typescript、Perl、Objective-C、Lua、Julia、JavaScript、Java、Groovy、C#等)和某些静态语言(比如Golang,一般来说,静态类型语言在编译时便已确定了变量的类型,但是Golang的实现是:在编译时推断变量的类型),支持"鸭子类型"的语言的解释器/编译器将会在解析(Parse)或编译时,推断对象的类型。
动态类型&动态值&静态类型
package main
import "fmt"
type Pet interface {
SetName(name string)
Name() string
Category() string
}
type Dog struct {
name string // 名字。
}
func (dog *Dog) SetName(name string) {
dog.name = name
}
func (dog Dog) Name() string {
return dog.name
}
func (dog Dog) Category() string {
return "dog"
}
func main() {
// 示例1。
dog := Dog{"little pig"}
_, ok := interface{}(dog).(Pet)
fmt.Printf("Dog implements interface Pet: %v\n", ok)
_, ok = interface{}(&dog).(Pet)
fmt.Printf("*Dog implements interface Pet: %v\n", ok)
fmt.Println()
// 示例2。
var pet Pet = &dog
fmt.Printf("This pet is a %s, the name is %q.\n",
pet.Category(), pet.Name())
}
对于一个接口类型的变量来说,我们赋给它的值可以被叫做它的实际值(也称动态值),而该值的类型可以被叫做这个变量的实际类型(也称动态类型)
对于变量pet来讲,它的动态类型是*Dog,动态值就是&dog ,它的静态类型就是Pet,并且永远是Pet,但是它的动态类型却会随着我们赋给它的动态值而变化。
为接口变量赋值
当我们为一个接口变量赋值时会发生什么?
接口类型值的存储方式和结构
接口类型本身是无法被值化的。在我们赋予它实际的值之前,它的值一定会是nil,这也是它的零值。
当我们给一个接口变量赋值的时候,该变量的动态类型会与它的动态值一起被存储在一个专用的数据结构中。我们就把这个专用的数据结构叫做iface吧,在 Go 语言的runtime包中它其实就叫这个名字。iface的实例会包含两个指针,一个是指向类型信息的指针,另一个是指向动态值的指针。这里的类型信息是由另一个专用数据结构的实例承载的,其中包含了动态值的类型,以及使它实现了接口的方法和调用它们的途径,等等。总之,接口变量被赋予动态值的时候,存储的是包含了这个动态值的副本的一个结构更加复杂的值。
接口变量赋值为真正的nil
在 Go 语言中,我们把由字面量nil表示的值叫做无类型的nil。这是真正的nil,因为它的类型也是nil的。
只要我们把一个有类型的nil赋给接口变量,那么这个变量的值就一定不会是那个真正的nil。
怎样才能让一个接口变量的值真正为nil呢?要么只声明它但不做初始化,要么直接把字面量nil赋给它。
接口的组合
接口类型间的嵌入也被称为接口的组合。接口类型间的嵌入比结构体字段的嵌入要更简单一些,因为它不会涉及方法间的“屏蔽”。只要组合的接口之间有同名的方法就会产生冲突,从而无法通过编译,即使同名方法的签名彼此不同也会是如此。因此,接口的组合根本不可能导致“屏蔽”现象的出现。
这是因为相比于包含很多方法的大接口而言,小接口可以更加专注地表达某一种能力或某一类特征,同时也更容易被组合在一起。
Go 语言标准库代码包io中的ReadWriteCloser接口和ReadWriter接口就是这样的例子,它们都是由若干个小接口组合而成的。以io.ReadWriteCloser接口为例,它是由io.Reader、io.Writer和io.Closer这三个接口组成的。
这三个接口都只包含了一个方法,是典型的小接口。它们中的每一个都只代表了一种能力,分别是读出、写入和关闭。我们编写这几个小接口的实现类型通常都会很容易。并且,一旦我们同时实现了它们,就等于实现了它们的组合接口io.ReadWriteCloser。即使我们只实现了io.Reader和io.Writer,那么也等同于实现了io.ReadWriter接口,因为后者就是前两个接口组成的。可以看到,这几个io包中的接口共同组成了一个接口矩阵。它们既相互关联又独立存在