0
点赞
收藏
分享

微信扫一扫

Go 语言系列23:接口

是她丫 2022-11-25 阅读 68

在 Go 语言中, 接口 就是方法签名(Method Signature)的集合。在面向对象的领域里,接口定义一个对象的行为,接口只指定了对象应该做什么,至于如何实现这个行为,则由对象本身去确定。当一个类型定义了接口中的所有方法,我们称它实现了该接口。接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法。


Go 语言系列23:接口_接受者

接口的定义

Go 语言系列23:接口_赋值_02


使用 ​​type​​ 关键字可以定义接口:

type interface_name interface {
method()
}

Go 语言系列23:接口_接受者

接口的实现

Go 语言系列23:接口_赋值_02


创建类型或者结构体,并为其绑定接口定义的方法,接收者为该类型或结构体,方法名为接口中定义的方法名,这样就说该类型或者结构体实现了该接口。例如:

package main

import "fmt"

type Usb interface {
link()
}

type Computer struct {
name string
}

func (computer Computer) link() {
fmt.Println("电脑 USB 接口连接到 U 盘")
}

func main() {
myComputer := Computer{"菜籽的电脑"}
myComputer.link()
}

上面的程序定义了一个名为 ​​Usb​​​ 的接口,接口中有未实现的方法 ​​link()​​​ ,这里还定义了名为 ​​Computer​​​ 的结构体,其绑定了方法 ​​link()​​​ ,也就隐式实现了 ​​Usb​​​ 接口,实现的内容是打印 ​​电脑 USB 接口连接到 U 盘​​ 语句,运行该程序输出如下:

电脑 USB 接口连接到 U 盘

上面的例子使用了值接受者实现接口,下面的例子使用了指针接受者实现接口。

package main

import "fmt"

type Describer interface {
Describe()
}

type Person struct {
name string
age int
}

func (person Person) Describe() {
fmt.Printf("%s is %d years old\n", person.name, person.age)
}

type Date struct {
year int
month int
day int
}

func (date *Date) Describe() {
fmt.Printf("Today is %d-%d-%d\n", date.year, date.month, date.day)
}

func main() {
var d1 Describer
var d2 Describer

person1 := Person{"John", 30}
d1 = person1
d1.Describe()

person2 := Person{"Mary", 25}
d1 = &person2
d1.Describe()

date := Date{2022, 1, 1}
// d2 = date // error
d2 = &date
d2.Describe()
}

该程序定义了结构体 ​​Person​​​ ,使用其作为值接受者实现 ​​Describer​​​ 接口。​​person1​​​ 的类型为 ​​Person​​​ , ​​person1​​​ 赋值给 ​​d1​​​ ,由于 ​​Person​​​ 实现了接口变量 ​​d1​​​ 所以会有输出。而接下来 ​​d1​​​ 又被赋值为 ​​&person2​​​ ,同样有输出。接下来的结构体 ​​Date​​​ 使用指针接受者实现 ​​Describer​​​ 接口。​​date​​​ 的类型为 ​​Date​​​ , ​​d2​​​ 被赋值为 ​​&date​​​ ,所以会有输出。但如果把 ​​d2​​​ 赋值为 ​​date​​​ 会报错,对于使用指针接受者的方法,用一个指针或者一个可取得地址的值来调用都是合法的。但接口中存储的具体值(Concrete Value)并不能取到地址,因此对于编译器无法自动获取 ​​date​​ 的地址,于是程序报错。运行该程序输出如下:

John is 30 years old
Mary is 25 years old
Today is 2022-1-1

Go 语言系列23:接口_接受者

接口实现多态

Go 语言系列23:接口_赋值_02


使用接口可以实现多态,例如下面的程序,定义了名为 ​​Animal​​​ 的接口,接口中有方法 ​​bark()​​​ ,也就是说动物会发出不同的叫声。程序中还定义了结构体 ​​Cat​​​ 和 ​​Dog​​​ ,分别实现了 ​​Animal​​​ 接口,猫的叫声为 ​​喵喵喵...​​​ 而狗的叫声为 ​​汪汪汪...​​ ,利用的接口实现了不同的功能,这就是多态。

package main

import "fmt"

type Animal interface {
bark()
}

type Cat struct {
name string
}

type Dog struct {
name string
}

func (cat Cat) bark() {
fmt.Println("喵喵喵...")
}

func (dog Dog) bark() {
fmt.Println("汪汪汪...")
}

func main() {
myCat := Cat{"哆啦A梦"}
myDog := Dog{"史努比"}
myCat.bark() // 喵喵喵...
myDog.bark() // 汪汪汪...
}

Go 语言系列23:接口_接受者

接口的内部表示

Go 语言系列23:接口_赋值_02


可以把接口看作内部的一个元组 ​​(type, value)​​​。​​type​​​ 是接口底层的具体类型(Concrete Type),而 ​​value​​ 是具体类型的值。

package main

import "fmt"

type Animal interface {
bark()
}

type Cat struct {
name string
}

func (cat Cat) bark() {
fmt.Println("喵喵喵...")
}

func describe(animal Animal) {
fmt.Printf("Interface type: %T\nInterface value: %v\n", animal, animal)
}

func main() {
var animal Animal
myCat := Cat{"哆啦A梦"}
animal = myCat
describe(animal)
animal.bark()
}

在上面的程序中,定义了 ​​Animal​​​ 接口,其中有 ​​bark()​​​ 方法,结构体 ​​Cat​​​ 实现了该接口。使用 ​​animal = myCat​​​ 语句我们把 ​​myCat​​​ ( ​​Cat​​​ 类型)赋值给了 ​​animal​​​ ( ​​Animal​​​ 类型),现在打印出 ​​animal​​​ 的具体类型为 ​​Cat​​​ ,而 ​​Cat​​​ 的值为 ​​{哆啦A梦}​​ 。运行该程序输出如下:

Interface type: main.Cat
Interface value: {哆啦A梦}
喵喵喵...

Go 语言系列23:接口_接受者

空接口

Go 语言系列23:接口_赋值_02


空接口 是特殊形式的接口类型,没有定义任何方法的接口就称为空接口,可以说所有类型都至少实现了空接口,空接口表示为 ​​interface{}​​ 。例如,我们之前的写过的空接口参数函数,可以接受任何类型的参数:

package main

import "fmt"

func describe(int interface {}) {
fmt.Printf("Type: %T, value: %v\n", int, int)
}

func main() {
str := "Let's go"
describe(str)
num := 3.14
describe(num)
}

上面的程序中我们定义了函数 ​​describe​​ 使用空接口作为参数,所以可以给这个函数传递任何类型的参数,运行该程序输出如下:

Type: string, value: Let's go
Type: float64, value: 3.14

通过上面的例子不难发现接口都有两个属性,一个是值,而另一个是类型。对于空接口来说,这两个属性都为 ​​nil​​ :

package main

import "fmt"

func main() {
var in interface{}
fmt.Printf("Type: %T, Value: %v", in, in)
// Type: <nil>, Value: <nil>
}

除了上面讲到的使用空接口作为函数参数的用法,空接口还有以下两种用法。

直接使用 ​​interface{}​​ 作为类型声明一个实例,这个实例就能承载任何类型的值:

package main

import "fmt"

func main() {
var in interface{}

in = "Let's go"
fmt.Println(in) // Let's go

in = 3.14
fmt.Println(in) // 3.14
}

我们也可以定义一个接收任何类型的 ​​array​​​ 、 ​​slice​​​ 、 ​​map​​​ 、 ​​strcut​​ 。例如:

package main

import "fmt"

func main() {
x := make([]interface{}, 3)
x[0] = "Let's go"
x[1] = 3.14
x[2] = []int{1, 2, 3}
for _, value := range x {
fmt.Println(value)
}
}

运行该程序输出如下:

Let's go
3.14
[1 2 3]

空接口可以承载任何值,但是空接口类型的对象是不能赋值给另一个固定类型对象的。

package main

func main() {
var num = 1
var in interface{} = num
var str string = in // error
}

当空接口承载数组和切片后,该对象无法再进行切片。

package main

import "fmt"

func main() {
var slice = []int{1, 2, 3}

var in interface{} = slice

var newSlice = in[1:2] // error
fmt.Println(newSlice)
}

Go 语言系列23:接口_接受者

类型断言

Go 语言系列23:接口_赋值_02


类型断言用于提取接口的底层值(Underlying Value)。使用 ​​interface.(Type)​​​ 可以获取接口的底层值,其中接口 ​​interface​​​ 的具体类型是 ​​Type​​ 。

package main

import "fmt"

func assert(in interface{}) {
value, ok := in.(int)
fmt.Println(value, ok)
}

func main() {
var x interface{} = 3
assert(x)
var y interface{} = "Let's go"
assert(y)
}

运行上面的程序输出如下:

3 true
0 false

Go 语言系列23:接口_接受者

类型选择

Go 语言系列23:接口_赋值_02


类型选择用于将接口的具体类型与 ​​case​​​ 语句所指定的类型进行比较。它其实就是一个 ​​switch​​​ 语句,但在 ​​switch​​​ 后面跟的是 ​​in.(type)​​​ ,并且每个 ​​case​​ 后面跟的是类型。

package main

import "fmt"

func getTypeValue(in interface{}) {
switch in.(type) {
case int:
fmt.Printf("Type: int, Value: %d\n", in.(int))
case string:
fmt.Printf("Type: string, Value: %s\n", in.(string))
default:
fmt.Printf("Unknown type\n")
}
}

func main() {
getTypeValue(3)
getTypeValue("abc")
getTypeValue(true)
}

运行上面的程序输出如下:

Type: int, Value: 3
Type: string, Value: abc
Unknown type

Go 语言系列23:接口_接受者

实现多个接口

Go 语言系列23:接口_赋值_02


类型或者结构体可以实现多个接口,例如:

package main

import "fmt"

type Describer interface {
Describe()
}

type Animal interface {
bark()
}

type Cat struct {
name string
}

func (cat Cat) Describe() {
fmt.Printf("Cat name: %s\n", cat.name)
}

func (cat Cat) bark() {
fmt.Printf("喵喵喵...\n")
}

func main() {
myCat := Cat{"哆啦A梦"}
myCat.Describe()
myCat.bark()
}

上面的程序定义了两个接口,结构体 ​​Cat​​ 分别实现了这两个接口,运行该程序输出如下:

Cat name: 哆啦A梦
喵喵喵...

Go 语言系列23:接口_接受者

接口的嵌套

Go 语言系列23:接口_赋值_02


虽然在 Go 中没有继承机制,但可以通过接口的嵌套实现类似功能。例如:

package main

import "fmt"

type Animal interface {
Describer
Bark
}

type Describer interface {
Describe()
}

type Bark interface {
bark()
}

type Cat struct {
name string
}

func (cat Cat) Describe() {
fmt.Printf("Cat name: %s\n", cat.name)
}

func (cat Cat) bark() {
fmt.Printf("喵喵喵...\n")
}

func main() {
myCat := Cat{"哆啦A梦"}
myCat.Describe()
myCat.bark()
}

​Cat​​​ 结构体实现了 ​​Animal​​ 接口。运行该程序输出如下:

Cat name: 哆啦A梦
喵喵喵...

参考文献:

[1] Alan A. A. Donovan; Brian W. Kernighan, Go 程序设计语言, Translated by 李道兵, 高博, 庞向才, 金鑫鑫 and 林齐斌, 机械工业出版社, 2017.




举报

相关推荐

0 条评论