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)
        }
}
posted @ 2020-02-01 17:15  wAt3her  阅读(133)  评论(0编辑  收藏  举报