Golang 接口笔记
接口是 CS 中非常重要的一个概念,可以让我们把系统进行划分,不同层次之间可以做到互不相通。上层的模块和下层的模块之间只依赖一个接口,做到互不侵犯。
Go 接口是隐式的接口,对于某个类而言,其不用显示的声明实现了某个接口,只要实现了接口声明的方法,那么就默认这个类实现了这个接口。
Go 里面判断接口是否被某个类实现,只用看这个类的方法集是否是这个接口方法集的超集。对于任何类型而言,其都是空方法集的超集,那么任何类型都实现了空接口。
Go 是静态类型语言,所有的变量类型都是在编译时期确定下来的。变量实际上是通过一对 pair (type, value) 进行表示的,此时的 type 是对于 runtime 时候而言,变量经过不同的赋值之后,其 hold 的type会发生改变,hold 的value也会发生改变。
指针和接口
对于某个类实现有 value receiver 也有 pointer receiver 两种方法。因为方法实现的不同,可能会出现没有办法向接口赋值的情况。
type Foo interface {
foo()
}
type Bar struct {}
func (b *Bar) foo() {}
--- Allowed ---------
b := Bar{}
b.foo()
--- Not allowed -----
var foo Foo = Bar{}
可以看到,第二种赋值则不被允许。因为对于实现的 foo() 方法而言,其 receiver 是一个指向 Bar 类型的指针。所以我们可以说是 *Bar 类型实现了接口而不是 Bar 类型。所以,如果采用 var foo Foo = &Bar{}
的赋值方法,才允许赋值。同时 Bar{} 是个字面量,其不能被寻址,所以,自然无法采用指针传递。
然而,如果方法采用的是 value receiver 方法,而使用指针进行初始化变量,但是仍然可以通过编译。因为这个时候,Go 编译器会自动进行指针的解引用,从而直接得到对应的结构体。
所以,如果如果使用指针实现接口的话,只有指针变量才可以算实现了该接口,如果使用结构体实现接口的话,那么指针类型和结构体类型都算实现了该接口。
接口和多态
Go 语言本身是没有多态的,但是可以利用接口进行多态。如果不同类型的类都实现了某个接口,那么针对一个具体的接口类型的变量,根据其 hold 的 type 不同,调用的 function 相同,但却调用的不同类型的 function。从而实现了动态的变化。
一个典型的多态的例子。(出自 Go101)
package main
import "fmt"
type Filter interface {
About() string
Process([]int) []int
}
// UniqueFilter用来删除重复的数字。
type UniqueFilter struct{}
func (UniqueFilter) About() string {
return "删除重复的数字"
}
func (UniqueFilter) Process(inputs []int) []int {
outs := make([]int, 0, len(inputs))
pusheds := make(map[int]bool)
for _, n := range inputs {
if !pusheds[n] {
pusheds[n] = true
outs = append(outs, n)
}
}
return outs
}
// MultipleFilter用来只保留某个整数的倍数数字。
type MultipleFilter int
func (mf MultipleFilter) About() string {
return fmt.Sprintf("保留%v的倍数", mf)
}
func (mf MultipleFilter) Process(inputs []int) []int {
var outs = make([]int, 0, len(inputs))
for _, n := range inputs {
if n % int(mf) == 0 {
outs = append(outs, n)
}
}
return outs
}
// 在多态特性的帮助下,只需要一个filteAndPrint函数。
func filteAndPrint(fltr Filter, unfiltered []int) []int {
// 在fltr参数上调用方法其实是调用fltr的动态值的方法。
filtered := fltr.Process(unfiltered)
fmt.Println(fltr.About() + ":\n\t", filtered)
return filtered
}
func main() {
numbers := []int{12, 7, 21, 12, 12, 26, 25, 21, 30}
fmt.Println("过滤之前:\n\t", numbers)
// 三个非接口值被包裹在一个Filter切片
// 的三个接口元素中。
filters := []Filter{
UniqueFilter{},
MultipleFilter(2),
MultipleFilter(3),
}
// 每个切片元素将被赋值给类型为Filter的
// 循环变量fltr。每个元素中的动态值也将
// 被同时复制并被包裹在循环变量fltr中。
for _, fltr := range filters {
numbers = filteAndPrint(fltr, numbers)
}
}