接口在Go语言有着至关重要的地位。如果说goroutine和channel 是支撑起Go语言的并发模型的基石,让Go语言在如今集群化与多核化的时代成为一道极为亮丽的风景,那么接口是Go语言整个类型系统的基石,让Go语言在基础编程哲学的探索上达到前所未有的高度。
Go语言在编程哲学上是变革派,而不是改良派。这不是因为Go语言有goroutine和channel,而更重要的是因为Go语言的类型系统,更是因为Go语言的接口。 Go语言的编程哲学因为有接口而趋近完美。
侵入式接口
在Go语言出现之前,接口主要作为不同组件之间的契约存在。对契约的实现是强制的,你必须声明你的确实现了该接口。侵入式接口的主要表现在于 实现类需要明确声明自己实现了某个接口。
非侵入式接口
在Go语言中,一个类只需要实现了接口要求的所有函数,我们就说这个类实现了该接口。
Go语言的非侵入式接口,看似只是做了很小的文法调整,实则影响深远。
其一, Go语言的标准库,再也不需要绘制类库的继承树图。在Go中,类的继承树并无意义,你只需要知道这个类实现了哪些 方法,每个方法是啥含义就足够了。
其二,实现类的时候,只需要关心自己应该提供哪些方法,不用再纠结接口需要拆得多细才合理。接口由使用方按需定义, 而不用事前规划。
其三,不用为了实现一个接口而导入一个包,因为多引用一个外部的包,就意味着更多的耦合。接口由使用方按自身需求来 定义,使用方无需关心是否有其他模块定义过类似的接口。
接口赋值
接口赋值在Go语言中分为如下两种情况:
将对象实例赋值给接口,这要求该对象实例实现了接口要求的所有方法;
将一个接口赋值给另一个接口。在Go语言中,只要两个接口拥有相同的方法列表(次序不同不要紧),那么它们就是等同的,可以相互赋值。接口赋值并不要求两个接口必须等价。如果接口A的方法列表是接口B的方法列表的子集,那么接口B可以赋值给接口A。
接口查询
这个if语句检查file1接口指向的对象实例是否实现了two.IStream接口,如果实现了,则执行特定的代码。
接口查询是否成功,要在运行期才能够确定。它不像接口赋值,编译器只需要通过静态类型检查即可判断赋值是否可行。
查询接口所指向的对象是否为某个类型的这种用法可以认为只是接口查询的一个特例。接口是对一组类型的公共特性的抽象
类型查询
在Go语言中,还可以更加直截了当地询问接口指向的对象实例的类型,例如:
就像现实生活中物种多得数不清一样,语言中的类型也多得数不清,所以类型查询并不经常使用。它更多是个补充,需要配合接口查询使用。
利用反射也可以进行类型查询。
接口组合
Go语言同样支持接口组合。我们已经介绍过Go语言包中io.Reader接口和io.Writer接口,接下来我们再介绍同样来自于io包的另一个接口io.ReadWriter:<br />![](https://cdn.nlark.com/yuque/0/2018/png/200878/1544783302886-9abc1ad5-01fb-4b29-88e7-a84737026fde.png#width=827)
这个接口组合了Reader和Writer两个接口,它完全等同于如下写法:
因为这两种写法的表意完全相同: ReadWriter接口既能做Reader接口的所有事情,又能做Writer接口的所有事情。在Go语言包中,还有众多类似的组合接口,比如ReadWriteCloser、ReadWriteSeeker、 ReadSeeker和WriteCloser等。
可以认为接口组合是类型匿名组合的一个特定场景,只不过接口只包含方法,而不包含任何成员变量。
Any类型
由于Go语言中任何对象实例都满足空接口interface{},所以interface{}看起来像是可以指向任何对象的Any类型,如下:
当函数可以接受任意的对象实例时,我们会将其声明为interface{},最典型的例子是标准库fmt中PrintXXX系列的函数,例如:
总体来说, interface{}类似于COM中的IUnknown,我们刚开始对其一无所知,但可以通过接口查询和类型查询逐步了解它。