在 Go 语言中, 接口 就是方法签名(Method Signature)的集合。在面向对象的领域里,接口定义一个对象的行为,接口只指定了对象应该做什么,至于如何实现这个行为,则由对象本身去确定。当一个类型定义了接口中的所有方法,我们称它实现了该接口。接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法。
接口的定义
使用 type
关键字可以定义接口:
type interface_name interface {
method()
}
接口的实现
创建类型或者结构体,并为其绑定接口定义的方法,接收者为该类型或结构体,方法名为接口中定义的方法名,这样就说该类型或者结构体实现了该接口。例如:
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
接口实现多态
使用接口可以实现多态,例如下面的程序,定义了名为 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() // 汪汪汪...
}
接口的内部表示
可以把接口看作内部的一个元组 (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梦}
喵喵喵...
空接口
空接口 是特殊形式的接口类型,没有定义任何方法的接口就称为空接口,可以说所有类型都至少实现了空接口,空接口表示为 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)
}
类型断言
类型断言用于提取接口的底层值(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
类型选择
类型选择用于将接口的具体类型与 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
实现多个接口
类型或者结构体可以实现多个接口,例如:
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 中没有继承机制,但可以通过接口的嵌套实现类似功能。例如:
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.