Go进阶之方法集合接口实现

19 阅读8分钟

自定义类型的方法和接口都是Go语言中的概念.并且他们之间存在千丝万缕的关系.示例:

```
package main

type Interface interface {
    M1()
    M2()
}

type T struct{}

func (t T) M1() {}

func (t *T) M2() {}

func main() {
    var t T
    var pt *T
    var i Interface
    i = t
    i = pt
}
```

image.png

上边的例子没有通过编译器检查.编译器给出的信息是:不能使用变量t给接口类型变量

i赋值.因为t没有实现Interface接口方法集合中的M2()方法.下面会慢慢解除疑惑.

1.方法集合:

上一篇文章说过receiver类型除了考量是否需要对类型实例进行修改 类型实例复制

导致的性能损耗之外.还有一个因素就是类型是否需要实现某个接口类型.

Go语言的一个创新是.自定义类型与接口之间实现关系是松耦合的.如果某个自定义类

型T的方法集合是某个接口类型的方法集合的超集.那么就说类型T实现了该接口.并且

该类型T的变量可以被赋值给该接口类型的变量.即方法集合决定接口实现.

方法集合是Go语言中一个重要的概念.在为接口类型变量赋值 使用结构体嵌入 接口

嵌入 类型别名和方法表达式等时都会用到方法集合.像胶水一样自定义类型与接口隐

式的粘结在一起.

要判断一个自定义类型是否实现了某接口类型.首先要识别出自定义类型的方法集合

和接口类型的方法集合.有时候并不明显.可以通过一个工具函数方便的输出自定义类

型或接口的方法集合.

工具函数:

```
func DumpMethodSet(i interface{}) {
    v := reflect.TypeOf(i)
    elemTyp := v.Elem()
    n := elemTyp.NumMethod()
    if n == 0 {
       fmt.Printf("%s 方法个数为0", elemTyp)
       return
    }
    fmt.Printf("%s 集合\n", elemTyp)
    for i := 0; i < n; i++ {
       fmt.Println("-", elemTyp.Method(i).Name)
    }
    fmt.Printf("\n")
}
```

把开头的代码调用如上工具类:

```
package main

import (
    "fmt"
    "reflect"
)

type Interface interface {
    M1()
    M2()
}

type T struct{}

func (t T) M1() {}

func (t *T) M2() {}

func main() {
    var t T
    var pt *T
    DumpMethodSet(&t)
    DumpMethodSet(&pt)
    DumpMethodSet((*Interface)(nil))
}

func DumpMethodSet(i interface{}) {
    v := reflect.TypeOf(i)
    elemTyp := v.Elem()
    n := elemTyp.NumMethod()
    if n == 0 {
       fmt.Printf("%s 方法个数为0", elemTyp)
       return
    }
    fmt.Printf("%s 集合\n", elemTyp)
    for i := 0; i < n; i++ {
       fmt.Println("-", elemTyp.Method(i).Name)
    }
    fmt.Printf("\n")
}
```

执行结果:

image.png

从执行结果中可以看出.var t T的方法集合中并不是Interface的超集.所以无法进行

赋值.

2.类型嵌入与方法集合:

Go语言的设计哲学之一是偏好组合.Go语言支持用组合的思想来实现一些面向对象

领域经典的机制.比如继承.

与接口类型和结构体类型相关的类型嵌入有三种组合.在接口类型中嵌入接口类型. 在

结构体类型中嵌入接口类型. 在结构体类型中嵌入结构体类型.

2.1在接口类型中嵌入接口类型:

按Go语言惯例.接口类型中仅包含少量方法.并且常常仅有一个方法.通过在接口类型

中嵌入其他接口类型实现接口的组合.比如io包中的ReadWriter

ReadWriteCloser 等接口类型就是通过嵌入Reader Writer Closer三个基本接口

类型形成的.源码如下:

type Reader interface {
    Read(p []byte) (n int, err error)
}


type Writer interface {
    Write(p []byte) (n int, err error)
}


type Closer interface {
    Close() error
}

上面为三个基本接口.

type ReadWriter interface {
    Reader
    Writer
}


type ReadCloser interface {
    Reader
    Closer
}


type WriteCloser interface {
    Writer
    Closer
}


type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

再来看看通过嵌入接口类型后新接口类型的方法集合,以Go标准库中的几个接口为例

子.示例如下:

```
func main() {
    DumpMethodSet((*io.Writer)(nil))
    DumpMethodSet((*io.Reader)(nil))
    DumpMethodSet((*io.Closer)(nil))
    DumpMethodSet((*io.ReadWriter)(nil))
    DumpMethodSet((*io.ReadWriteCloser)(nil))
}
```

执行结果:

```
func main() {
    DumpMethodSet((*io.Writer)(nil))
    DumpMethodSet((*io.Reader)(nil))
    DumpMethodSet((*io.Closer)(nil))
    DumpMethodSet((*io.ReadWriter)(nil))
    DumpMethodSet((*io.ReadWriteCloser)(nil))
}
```

image.png 通过输出结果可知.通过嵌入接口类型而创建的新接口类型的方法集合包括了被嵌入

接口类型的方法集合.

在Go1.14之前版本有一个约束.被嵌入接口类型的方法集合不能有交集.同时被嵌入

的接口类型的方法集合中的方法不能与新接口中其他方法同名.

type Interface1 interface {
	M1()
}
type Interface2 interface {
	M1()
	M2()
}

type Interface3 interface {
	Interface1
	Interface2
}

type Interface4 interface {
	Interface2
	M2()
}

Go1.14版本开始去掉了该约束.

2.2在结构体类型中嵌入接口类型:

在结构体类型中嵌入接口类型后.该结构体类型的方法集合中将包含被嵌入接口类型

的方法集合.

示例:

package main

import (
	"fmt"
	"reflect"
)

type Interface interface {
	M1()
	M2()
}

type T struct {
	Interface
}

func (T) M3() {

}

func main() {
	DumpMethodSet((*Interface)(nil))

	var t T
	var pt *T

	DumpMethodSet(&t)
	DumpMethodSet(&pt)
}

运行结果:

image.png

输出的结果与预期一致.如果当结构体类型中嵌入多个接口类型且这些接口类型的方

法集合存在交集时.结果就会出现不一样.

Go选择方法次序:

1).优先选择结构体自身实现的方法.

2).如果结构体自身并未实现.那么将查找结构体中的嵌入接口类型的方法集合中是否

有方法.如果有.则提升为结构体方法.

示例如下:

```
package main

import (
    "fmt"
    "reflect"
)

type Interface interface {
    M1()
    M2()
}

type T struct {
    Interface
}

func (T) M1() {
    fmt.Println("struct M1")
}

type S struct{}

func (S) M1() {
    fmt.Println("s M1")
}

func (S) M2() {
    fmt.Println("s M2")
}

func main() {
    var t = T{
       Interface: S{},
    }
    t.M1()
    t.M2()
}
```

输出结果:

image.png

如果结构体嵌入了多个接口类型且这些接口类型的方法集合存在交集.Go编译器将会

报错.除非结构体自己实现了交集中所有的方法.

示例如下:

```
package main

import (
    "fmt"
    "reflect"
)

type Interface interface {
    M1()
    M2()
    M3()
}

type Interface1 interface {
    M1()
    M2()
    M4()
}

type T struct {
    Interface
    Interface1
}

func main() {
    t := new(T)
    t.M1()
    t.M2()
}
```

image.png

可以看到编译器给出错误提示.编译器在t.M1和t.M2出现分歧.不知道该选择哪个.

修证示例:

```
package main

import (
    "fmt"
    "reflect"
)

type Interface interface {
    M1()
    M2()
    M3()
}

type Interface1 interface {
    M1()
    M2()
    M4()
}

type T struct {
    Interface
    Interface1
}
func (t *T) M1() {
    
}

func (t *T) M2() {

}

func main() {
    t := new(T)
    t.M1()
    t.M2()
}
```

结构体类型在嵌入某个接口类型的同时.也实现了这个接口.这一特性在单元测试中特

别有用.(后续单元测试的时候在详情举例).

2.3在结构体类型中嵌入结构体类型:

在结构体中嵌入结构体提供了一种实现继承的手段.外部的结构体类型T可以继承嵌入

的结构体类型的所有方法.无论是T类型的变量实例还是T类型变量实例.都可以调用*

所有继承的方法.

示例:

```
package main

import (
    "fmt"
    "reflect"
)

type T1 struct {
}

type T2 struct {
}

func (t T1) T1M1() {
    fmt.Println("T1M1")
}

func (t T1) T1M2() {
    fmt.Println("T1M2")
}

func (t *T1) T1M3() {
    fmt.Println("T1M3")
}

func (t T2) T2M1() {
    fmt.Println("T2M1")
}

func (t T2) T2M2() {
    fmt.Println("T2M2")
}

func (t *T2) T2M3() {
    fmt.Println("T2M3")
}

type T struct {
    T1
    T2
}

func main() {
    t := T{
       T1: T1{},
       T2: T2{},
    }

    fmt.Println("call method through t")
    t.T1M1()
    t.T1M2()
    t.T1M3()
    t.T2M1()
    t.T2M2()
    t.T2M3()

    fmt.Println("\ncall method through pt")
    pt := &t
    pt.T1M1()
    pt.T1M2()
    pt.T1M3()
    pt.T2M1()
    pt.T2M2()
    pt.T2M3()

    var t1 T1
    var pt1 *T1
    DumpMethodSet(&t1)
    DumpMethodSet(&pt1)

    var t2 T2
    var pt2 *T2
    DumpMethodSet(&t2)
    DumpMethodSet(&pt2)

}

func DumpMethodSet(i interface{}) {
    v := reflect.TypeOf(i)
    elemTyp := v.Elem()
    n := elemTyp.NumMethod()
    if n == 0 {
       fmt.Printf("%s 方法个数为0", elemTyp)
       return
    }
    fmt.Printf("%s 集合\n", elemTyp)
    for i := 0; i < n; i++ {
       fmt.Println("-", elemTyp.Method(i).Name)
    }
    fmt.Printf("\n")
}
```

执行结果:

image.png 从结果可以看出.

*T类型的方法集合=T1的方法集合+T2的方法集合.

***T类型的集合=T1的方法集合+T2的方法集合.

3.defined类型的方法集合.

Go语言支持基于已有的类型创建新类型.示例如下:

type MyInterface T1
type MyInterface2 T2

已有的类型(比如上面的T1 T2)被称为underlying类型.新类型被称为defined类型.

新定义的类型和原类型方法集合会有什么不同.

示例如下:

package main

import (
	"fmt"
	"reflect"
)

type T struct{}

func (t T) M1() {

}

func (t *T) M2() {

}

type Interface interface {
	M1()
	M2()
}

type T1 T
type Interface1 Interface

func main() {
	var t T
	var pt *T
	var t1 T1
	var pt1 *T1
	DumpMethodSet(&t)
	DumpMethodSet(&pt)
	DumpMethodSet(&t1)
	DumpMethodSet(&pt1)
	DumpMethodSet((*Interface)(nil))
	DumpMethodSet((*Interface1)(nil))

}

func DumpMethodSet(i interface{}) {
	v := reflect.TypeOf(i)
	elemTyp := v.Elem()
	n := elemTyp.NumMethod()
	if n == 0 {
		fmt.Printf("%s 方法个数为0", elemTyp)
		return
	}
	fmt.Printf("%s 集合\n", elemTyp)
	for i := 0; i < n; i++ {
		fmt.Println("-", elemTyp.Method(i).Name)
	}
	fmt.Printf("\n")
}

执行结果:

image.png 从结果来看.Go对于分别基于接口类型和自定义非接口类型创建的defined类型给出

了不一样的结果.

基于接口类型创建的defined类型与原接口类型的方法集合是一致的.

而基于自定义非接口类型创建的defiend类型并没有继承原类型的方法集合.新的

dfiend类型的方法集合是空的.

4.类型别名的方法集合:

类型别名与原类型几乎是等价的.但是在方法集合上与原类型是否有区别呢.

示例:

```
package main

import (
    "fmt"
    "reflect"
)

type T struct {
}

func (T) M1() {
    fmt.Println("M1")
}

func (*T) M2() {
    fmt.Println("M2")
}

type Interface interface {
    M1()
    M2()
}

type T1 = T
type Interface1 = Interface

func main() {
    var t T
    var pt *T
    var t1 T1
    var pt1 *T1

    DumpMethodSet(&t)
    DumpMethodSet(&pt)
    DumpMethodSet(&t1)
    DumpMethodSet(&pt1)
    DumpMethodSet((*Interface)(nil))
    DumpMethodSet((*Interface1)(nil))
}

func DumpMethodSet(i interface{}) {
    v := reflect.TypeOf(i)
    elemTyp := v.Elem()
    n := elemTyp.NumMethod()
    if n == 0 {
       fmt.Printf("%s 方法个数为0", elemTyp)
       return
    }
    fmt.Printf("%s 集合\n", elemTyp)
    for i := 0; i < n; i++ {
       fmt.Println("-", elemTyp.Method(i).Name)
    }
    fmt.Printf("\n")
}
```

执行结果:

image.png

从输出结果可以看出.函数甚至都无法识别出类型别名.无论类型别名还是原类型输出

的都是一样的方法集合.由此可以知道.无论别名与原类型拥有完全相同的方法集合.无

论原类型是接口类型还是非接口类型.

让风吹过我耳廓.烦恼都吹破.





如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路