go 的方法集和介面斷言

機智的小小帥發表於2021-04-27

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)的型別(值/指標),決定了該方法是關聯到值還是關聯到指標。

Method sets

A type may have a method set associated with it. The method set of an interface type is its interface. The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T). Further rules apply to structs containing embedded fields, as described in the section on struct types. Any other type has an empty method set. In a method set, each method must have a unique non-blank method name.

The method set of a type determines the interfaces that the type implements and the methods that can be called using a receiver of that type.

型別的方法集決定了該型別所實現的介面,以及當使用該型別作為 receiver 時,所能呼叫的

Values Methods Receivers
T (t T)
*T (t T) and (t *T)

即:

  • 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

相關文章