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》

https://blog.csdn.net/random_w/article/details/106279550

04-28 02:47