Go 的方法集与接口断言
方法集
引子
首先来看一段代码:
package main
import "fmt"
func main() {
var v IpmHelloByValue
CallSayHello(v) // Ok,Output: Hello,I'm value
CallSayHello(&v) // Ok,Output: Hello,I'm value
var p IpmHelloByPointer
CallSayHello(p) // Not Ok,compile failed: IpmHelloByPointer does not implement IHello (SayHello method has pointer receiver)
CallSayHello(&p) // OK, Output: Hello,I'm pointer
}
type IHello interface {
SayHello()
}
type IpmHelloByPointer struct {
}
func (p *IpmHelloByPointer) SayHello() {
fmt.Println("Hello,I'm pointer")
}
type IpmHelloByValue struct {
}
func (v IpmHelloByValue) SayHello() {
fmt.Println("Hello,I'm value")
}
func CallSayHello(h IHello) {
h.SayHello()
}
为何 CallSayHello(p)
会编译失败,这就涉及到方法集了。
介绍
[方法集(method set)][https://golang.org/ref/spec#Method_sets]:定义了一组关联到给定类型的值或者指针的方法。在定义方法时所使用的接收者(receiver)的类型(值/指针),决定了该方法是关联到值还是关联到指针。
类型的方法集决定了该类型所实现的接口,以及当使用该类型作为 receiver 时,所能调用的
即:
- T类型的值,只能调用接收者类型为值的方法。
- 指向T类型的指针,既能调用接收者类型为指针的方法,也能调用接收者类型为值的方法。
例子
举个例子,为一个结构体声明两个方法,其中一个方法的 receiver 是 value,另一个方法的 receiver 是 pointer。
type MyStruct struct {
}
// receiver 是一个 value
func (m MyStruct) ValueReceiver() {
fmt.Println("ValueReceiver")
}
// receiver 是一个 pointer
func (m *MyStruct) PointerReceiver() {
fmt.Println("PointerReceiver")
}
为这个 struct 创建两个示例,一个的类型是 value,另一个的类型是 pointer。
func main() {
var m MyStruct // 方法集中只有 ValueReceiver()
var pm *MyStruct // 方法集中既有 PointerReceiver(), 也有 ValueReceiver()
}
接下来创建两个 interface 以及使用这两个 interface 的函数
type IValue interface {
ValueReceiver()
}
type IPointer interface {
PointerReceiver()
}
func CallValue(v IValue) {
v.ValueReceiver()
}
func CallPointer(p IPointer) {
p.PointerReceiver()
}
分别将 m 和 pm 传入这两个函数会发生什么?
func main() {
var m MyStruct // 方法集中只有 ValueReceiver()
var pm *MyStruct // 方法集中既有 PointerReceiver(), 也有 ValueReceiver()
CallValue(m) // OK
// 因为 m 的方法集中并没有 PointerReceiver(),所以编译器说它没有实现 IPointer 接口
CallPointer(m) // Compile failed:Type does not implement 'IPointer' as 'PointerReceiver' method has a pointer receiver
CallValue(pm) // OK
CallPointer(pm) // OK
}
一个例外?
package main
import "fmt"
func main() {
var m MyStruct
m.ValueReceiver() // OK,Output: ValueReceiver
m.PointerReceiver() // OK,Output: PointerReceiver 这里为什么可以调用 PointerReceiver()?
pm := &m
pm.ValueReceiver() // OK,Output: ValueReceiver
pm.PointerReceiver() // OK,Output: PointerReceiver
}
type MyStruct struct {
}
// receiver 是一个 value
func (m MyStruct) ValueReceiver() {
fmt.Println("ValueReceiver")
}
// receiver 是一个 pointer
func (m *MyStruct) PointerReceiver() {
fmt.Println("PointerReceiver")
}
重点在第8行,按照之前所说的,m
的方法集中并没有PointerReceiver()
这个方法,为何这段代码可以编译成功?
这是因为编译器在后面做了工作。
在
m.PointerReceiver()
这句代码中,编译器对它做了一个隐式的 dereference 操作,偷偷的将它变成了
(&m).PointerReceiver()
所以最终还是通过一个 pointer 作为 receiver 去调用的 PointerReceiver
。
但是当变量无法取得地址时,编译器就无能为力了,比如这种:
MyStruct{}.ValueReceiver() // OK
MyStruct{}.PointerReceiver() // Not OK
(&MyStruct{}).PointerReceiver() // OK
因为编译器无法取得一个临时变量的地址。
接口断言
简介
接口断言可以判断一个 struct 是否实现了某个接口
通过
// 注意 _ 和 interfaceName 之间不要有 ','
var _ interfaceName = ImplementType
可以实现编译期的接口断言。
其中ImplementType
既可以是一个 value
,也可以是一个 pointer
,如果是 value
类型,需要用 nil
来初始化。
例子
还是之前的例子:
type MyStruct struct {
}
// receiver 是一个 value
func (m MyStruct) ValueReceiver() {
fmt.Println("ValueReceiver")
}
// receiver 是一个 pointer
func (m *MyStruct) PointerReceiver() {
fmt.Println("PointerReceiver")
}
type IValue interface {
ValueReceiver()
}
type IPointer interface {
PointerReceiver()
}
加上接口断言:
var _ IValue = (*MyStruct)(nil) // OK
var _ IPointer = (*MyStruct)(nil) // OK
var _ IValue = MyStruct{} // OK
var _ IPointer = MyStruct{} // Not OK: Type does not implement 'IPointer'
我是笨比
看起来这个接口断言好高大上呀,仔细一琢磨,它的形式不就是 go 中声明变量的方式么?
var 变量名字 类型 = 表达式
这儿只不过是把变量名字用 _
代替了而已,意思是告诉编译器我不在乎这个变量的值。
总结:我是笨比(是什么迷惑住了我的双眼?)
参考
《Go语言实战》
《The go programming language》