Go 语言中设计最精妙的部分是 interface。
有人甚至将 interface 翻译为“界面”而非“接口”,可见 Go 语言中的接口独树一帜,与 C++ 颇不一样。
一
据说,Go语言的诞生,是三个有很强个性的设计师共同完成的。Go语言的定位,就象三维坐标系中的一个点,在强类型、动态和并发这三个特性维度上,分别代表了Ken、Robert和Rob三人的创造思维的投影。
与 C++、Java 一样,Go 的变量类型是安全高效的强静态类型,“强类型”这一点容易理解;“并发”是 Go 的一大特性以后再说;何来“动态”呢?在 Python、Ruby 这类动态语言中有动态类型,对象可以根据提供的方法被处理,而忽略它们的实际类型,Go 也有这种功力吗?
《Go 语言中的面向对象》一文中讲过,Go 没有明显的继承,而是用 Has-A 替代 Is-A 的实现的。
如果一只鸟走起来像鸭子,叫起来像鸭子,那它就是鸭子 。
|
|
遵循这个理念,Go 使用接口规避了传统面向对象的继承(似乎传统继承有不少缺点,关于继承和组合有颇多讨论),使得这种静态语言表现出非常灵活的动态性。
二
具体来看,接口是一组 method 签名的组合,我们通过接口来定义对象的一组行动。比较方便的是,任何提供了接口方法实现代码的类型都隐式地实现了该接口,而不用显式地声明。同时,任何类型都实现了空接口(interface{}),因为空接口不包含任何 method。
|
|
顺着这个思路,如果一个函数把interface{}作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回interface{},那么也就可以返回任意类型的值。
当然,非空接口也可作为函数参数,试看一例:
|
|
fmt.Println 可以直接输出 Bob,这是因为 Bob 实现了 String() 也就实现了接口 fmt.Stringer
|
|
而 fmt.Println 的函数参数正是 Stringer 接口!
三
不仅如此,Go 的接口还有一个吸引人的地方,它支持匿名字段。就像 struct 的匿名字段一样,带有匿名字段的接口竟包含了另一个接口,那么这个接口同时包含了另一个接口的方法!看源码会发现有大量这样的例子,比如
|
|
io 包下面的 io.ReadWriter 包含了 io 包里的 Reader 和 Writer 两个接口。我们发现,这种嵌入接口的机制使得语言又灵活了不少。
在灵活的同时,Go 还能保证减少编译出错,因为我们可以反向知道接口变量里实际保存了哪些类型,这便是 Go 的 Comma-ok 断言和 switch 测试。
|
|
并且,Go 语言有反射机制,可以检查程序在运行时的状态。这简直是在说,一个聪明灵活的人,还非常善于反省。
|
|