最近会在一些开源项目和源码中见到 struct 嵌套匿名 interface,比如:
1 | type Interface interface { |
本文试图搞清楚这种设计的意义以及我们在实际编码中何时会用上。
在这之前,首先需要搞清楚 interface 的一些小细节。
Details of Interface
本文不对 interface 本身做科普,而是试图介绍一些 interface 中可能错过的小细节。
实际/动态类型和静态类型
1 | type Action interface { |
对于一个接口类型的变量来说,例如上面的变量 person,赋给它的值可以被叫做它的实际值(也称动态值),而该值的类型可以被叫做这个变量的实际类型(也称动态类型)。
比如,把取址表达式 &Person 的结果值赋给了变量 person,值的类型 *Person
就是该变量的动态类型。
动态类型这个叫法是相对于静态类型而言的。对于变量 person 来讲,它的静态类型就是 Action,并且永远是 Action,但是它的动态类型却会随着赋给它的动态值而变化。比如,只有我把一个 *Person
类型的值赋给变量 person 之后,该变量的动态类型才会是 *Person
。如果还有一个 Action 接口的实现类型 *ET
,并且我又把一个此类型的值赋给了 person
,那么它的动态类型就会变为 *ET
。
在给一个接口类型的变量赋予实际的值之前,它的动态类型是不存在的。
Struct with embemed Interface
一个实现了某个 interface 的 struct 需要实现该 interface 下的所有方法:
1 | type Action interface { |
但是如下声明是合法的:
1 | type Action interface { |
Person 确实实现了Action 的方法,从一个更可视化的角度来看,可以看成:
1 | func (p Person) Jump() { |
那么 p.Action.Jump()
究竟指代的位置是什么?其实,只要是任何实现了 Action 接口的任何对象都可以。
另外需要注意的一点是上述例子的打印结果:person:size=[16],value=[&{<nil>}]
,这里说明两点:
- 一个 interface 占据的大小是 16 个字节;
- 这里 person 的打印结果为
&{<nil>}
,即 Action 接口的打印值为 nil,注意这里 nil 仅代表字面量上的 nil,而不代表此处 Action 的值为 nil,接口在被实例化时,会被包装一层复杂的数据结构 iface,iface 的实例会包含两个指针,一个是指向类型信息的指针,另一个是指向动态值的指针。这里的类型信息是由另一个专用数据结构的实例承载的,其中包含了动态值的类型,以及使它实现了接口的方法和调用它们的途径等等。因此,这里的 nil 的真正含义是指 Action 实例化后指向动态值的指针为 nil。
通过下面这个示例来更清楚的演示细节:
1 | type Action interface { |
Use Case
回到最开始的问题,什么时候会用上它们?
Example: Interface Wrapper
从一个角度来说,它可以做到 DRY 原则,另一方面,它同样可以帮助开发者遵守 SOLID 中的依赖倒置原则。如下示例:
1 | type Action interface { |
与 Somebody 结构体相比,Hero 结构体只有一个 Jump 方法和 Somebody 结构体不同,那么它就可以采用这种做法。Hero 可以只实现 Jump 方法而不需要考虑 Run 方法的实现,相当于 Hero 这个数据结构 “继承” 了 Somebody 这个数据结构,同时它“实现”了自己的 Jump 方法。
Example: sort.Reverse
一个经典的例子是 golang 源码库中的 sort.Reverse
。它的使用在乍看之下并不好理解。一个比较简单的排序例子,对一个整数切片进行排序:
1 | lst := []int{4, 5, 2, 8, 1, 9, 3} |
sort.Sort
接受一个实现了 sort.Interface
接口的参数,定义如下:
1 | type Interface interface { |
如果有一个类型想用 sort.Sort
来排序,就必须实现这个接口;对于像 int slice 这样的简单类型,标准库提供了 sort.IntSlice
这样的方便类型,可以接受传进来的切片,并在其上实现 sort.Interface
方法。
1 | // IntSlice attaches the methods of Interface to []int, sorting in increasing order. |
到目前为止,一切都很好。
那么 sort.Reverse
是如何工作的呢?通过巧妙地采用一个嵌入结构中的接口。sort 包中有这样一个类型来帮助完成任务。
1 | type reverse struct { |
注意这里 Less 的实现,逆向通过嵌入的方式实现 sort.Interface
(只要在初始化的时候使用实现了该 struct 的类型就可以了),它“截取”了该接口的一个方法 Less
。然后,它将其委托给嵌入值的 Less
,但将参数的顺序倒置。这个 Less
实际上是将元素进行反向比较,这将使排序工作反向进行。
sort.Reverse
函数简单来说就是:
1 | func Reverse(data sort.Interface) sort.Interface { |
现在可以这样做:
1 | lst := []int{4, 5, 2, 8, 1, 9, 3} |
这里需要理解的关键点是,调用 sort.Reverse
本身并不对任何东西进行排序或反转。它可以被看作是一个高阶函数:它产生一个值,包装给它的接口并调整其函数实现。对 sort.Sort
的调用是排序发生的地方。