8.6. Interfaces(接口)
C++提供了class,类继承和模板,类似的go语言提供了接口支持。go中的接口和C++中的纯虚 基类 (只有虚函数,没有数据成员)类似。在Go语言中,任何实现了接口的函数的类型,都可以 看作是接口的一个实现。 类型在实现某个接口的时候,不需要显式关联该接口的信息。接口的实现 和接口的定义完全分离了。
类型的方法和普通函数定义类似,只是前面多了一个对象接收者receiver。 对象接受者和C++中的this指针类似。
type myType struct { i int }
func (p *myType) get() int { return p.i }
方法get依附于myType类型。myType对象在 函数中对应p。
方法在命名的类型上定义。如果,改变类型的话,那么就是针对新类型的另一个函数了。
如果要在内建类型上定义方法,则需要给内建类型重新指定一个名字,然后在新指定名字的类型上 定义方法。新定义的类型和内建的类型是有区别的。
type myInteger int
func (p myInteger) get() int { return int(p) } // Conversion required.
func f(i int) { }
var v myInteger
// f(v) is invalid.
// f(int(v)) is valid; int(v) has no defined methods.
把方法抽象到接口:
type myInterface interface {
get() int
set(i int)
}
为了让我们前面定义的myType满足接口,需要再增加一个方法:
func (p *myType) set(i int) { p.i = i }
现在,任何以myInterface类型作为参数的函数,都可以用 *myType 类型传递了。
func getAndSet(x myInterface) {}
func f1() {
var p myType
getAndSet(&p)
}
以C++的观点来看,如果把myInterface看作一个纯虚基类,那么实现了 set 和 get方法的 *myType 自动成为 了从myInterface纯虚基类继承的子类了。在Go中,一个类型可以同时 实现多种接口。
使用匿名成员,我们可以模拟C++中类的继承机制。
type myChildType struct { myType; j int }
func (p *myChildType) get() int { p.j++; return p.myType.get() }
这里的myChildType可以看作是myType的子类。
func f2() {
var p myChildType
getAndSet(&p)
}
myChildType类型中是有set方法的。在go中,匿名成员的方法 会默认被提升为类型本身的方法。 因为myChildType含有一个myType 类型的匿名成员,因此也就继承了myType中的set方法, 另一个 get方法则相当于被重载了。
不过这和C++也不是完全等价的。当一个匿名方法被调用的时候,方法对应的类型对象 是匿名成员类型, 并不是当前类型!换言之,匿名成员上的方法并不是C++中的虚函数。 如果你需要模拟虚函数机制, 那么可以使用接口。
一个接口类型的变量可以通过接口的一个内建的特殊方法转换为另一个接口类型变量。 这是由运行时库动态完成的, 和C++中的dynamic_cast有些类似。 但是在Go语言中,两个相互转换的接口类型之间并不需要什么信息。
type myPrintInterface interface {
print()
}
func f3(x myInterface) {
x.(myPrintInterface).print() // type assertion to myPrintInterface
}
向myPrintInterface类型的转换是动态的。它可以工作在底层实现了 print方法的变量上。
因为,这里动态类型转换机制,我们可以用它来模拟实现C++中的模板功能。这里我们需要 定一个最小的接口:
type Any interface { }
该接口可以持有任意类型的数据,但是在使用的时候需要将该接口变量转换为需要的类型。 因为,这里类型转换是动态实现的,因此,没有办法定义像C++中的内联函数。类型的验证 由运行时库完成, 我们可以调用该变量类型支持的所有方法。
type iterator interface {
get() Any
set(v Any)
increment()
equal(arg *iterator) bool
}