12-接口
Go 中的接口 interface
是一种类型,一种抽象的类型。
interface
是一组方法的集合,是 dack-type programming
的一种体现。
接口做的事情就像是定义一个协议(规则),只要一台机器具有洗衣服和甩干的功能,我就称其为洗衣机。
接口不关心属性(数据),只关心行为(方法)。
只要一个结构体 X 实现了接口 A 中所有的方法,就称这个结构体 X 为接口 A 的实现类,称结构体 X 实现了接口 A。还可称结构体 X 是 A 类型。
为了保护你的Go语言职业生涯,请牢记接口(interface)是一种类型。
定义接口
基本语法如下:
type identifier interface {
methodName1([param])([return])
methodName2([param])([return])
methodName3([param])([return])
...
}
identifier
:接口名,命名时按照规范应该加上 er
,如 有字符串功能的 Stringer
、有读取功能的 Reader
。
methodName
:方法名
eg:
type USBer interface {
start()
end()
}
实现接口
实现接口:
// 定义一个接口
type USBer interface {
start()
end()
}
// 定义一个结构体
type Mouse struct {
name string
}
// 实现两个接口中的方法,接收者为结构体 Mouse,这样 Mouse 就是一个实现类
func (m Mouse) start() {
fmt.Println(m.name, "starts working.")
}
func (m Mouse) end() {
fmt.Println(m.name, "end working.")
}
结构体 Mouse
实现了 USBer
的所有方法 start()
和 end()
,所以 Mouse
就是 USBer
的一个实现类。
使用接口
实现了接口的结构体(实现类)和没实现接口的结构体,区别在于
当一个函数需要传递一个接口类型的参数时,可以传递一个接口变量进去,也可以传一个实现类对象进去。
func test(usb USBer) { // 需要传递一个接口类型的参数
usb.start()
usb.end()
}
func main() {
m1 := Mouse{"罗技"}
test(m1) // 传递一个实现类对象
var u1 USBer = m1
test(u1) // 传递一个接口变量
}
注意,
接口变量使用之前,需要先赋一个实现类对象。
接口变量可以调用接口中的方法,但不可以调用实现类的属性和方法
package main
import "fmt"
// 定义一个接口
type USBer interface {
start()
end()
}
// 定义一个结构体
type Mouse struct {
name string
}
// 实现两个接口中的方法,接收者为结构体 Mouse,这样 Mouse 就是一个实现类
func (m Mouse) start() {
fmt.Println(m.name, "starts working.")
}
func (m Mouse) end() {
fmt.Println(m.name, "end working.")
}
// 定义结构体 Mouse 自己的方法
func (m Mouse) selfMethod() {
fmt.Println(m.name, "selfMethod")
}
// 定义一个测试方法,需要接收一个 USB 接口变量。可以传递 USB 变量,也可以传递 USB 实现类的对象
func test(usb USBer) { // 需要传递一个接口类型的参数
usb.start()
usb.end()
}
func main() {
m1 := Mouse{"罗技"}
// Mouse是一个实现类,可以传递实现类对象给 test()
test(m1)
// 接口变量使用之前要先赋一个实现类对象
var u1 USBer = m1
// 传递一个接口变量给 test()
test(u1)
m1.start() // 罗技 starts working.
m1.end() // 罗技 end working.
m1.selfMethod() // 罗技 selfMethod
u1.start() // 罗技 starts working.
u1.end() // 罗技 end working.
// u1.selfMethod() // 错误,接口变量不可以调用实现类的方法
// u1.name // 错误,接口变量不可以调用实现类的属性
}
空接口
不包含任何方法的接口,称为空接口。
我们可以认为 任意类型都实现了空接口
interface {}
type T interface {}
eg:
type T interface{}
type person struct {
name string
}
var t1 T = person{"Boii"}
var t2 T = "str"
var t3 T = 100
var t4 T = []int{1, 2, 3}
fmt.Println(t1) // {Boii}
fmt.Println(t2) // str
fmt.Println(t3) // 100
fmt.Println(t4) // [1 2 3]
空接口的作用在于 任意类型。
当我们想要接受一个任意类型的参数,或者定义一个接收任意类型的容器,都可以使用空接口来代替。func test (a interface{}) { ... } test("string") test(123) test([]int{1, 2, 3, 4})
a := []interface{}{1, 3, "str", true} fmt.Println(a) // {1 3 str true} type T interface{} b := []T{1, 3, "str", true} fmt.Println(b) // {1 3 str true}
接口嵌套
接口中不仅可以定义方法签名,还可以定义其他的接口。这种方式我们称为接口嵌套。
// 接口A
type A interface {
method1()
}
// 接口B
type B interface {
method2()
}
// 接口C,嵌套了接口 A 和 B
type C interface {
A
B
method3()
}
// 结构体
type person struct {
name string
}
func (p person) method1() { // 使 person 成为 接口A 的实现类
...
}
func (p person) method2() { // 使 person 成为 接口B 的实现类
...
}
func (p person) method3() { // 使 person 成为 接口C 的实现类,前提是实现了前面两个
...
}
嵌套了接口,不仅要实现接口本身的方法,还要实现接口中的嵌套接口的方法,才能认为实现了最外侧的接口。
如上面例子,person 要成为 C 的实现类,不仅要实现 C 的 method3()
方法,还得实现 A 的 method1()
和 B 的method2()
。
func main() {
p := person{"Boii"}
p.method1()
p.method2()
p.method3()
var a1 A = p
a1.method1()
// a1.method2() // !报错
// a1.method3() // !报错
var b1 B = p
b1.method2()
// b1.method1() // !报错
// b1.method3() // !报错
var c1 C = p
c1.method1() // 正确
c1.method2() // 正确
c1.method3() // 正确
var a2 A = c1 // 接口C变量看成是接口A类型,这是可以的
a2.method1() // 但是只能调用接口A的 method1()
// a2.method2() // !报错
// a2.method3() // !报错
var c2 C = a1 // !报错,接口A变量看成是接口C类型,这是错误的
}
接口断言
设 定义了一个接口A,还有两个结构体 X、Y,两个结构体都实现了接口A,当一个函数或方法想要接收 X 或 Y 两种类型时,可以将形参设置为 接口A 类型,这样传递的时候不管是 X 的对象还是 Y 的对象都可以传递进来。
【Q】:那如果在方法或函数里,我需要确定到底是 X 还是 Y 怎么办?
type A interface {
aMethod()
}
type X struct {
name string
age int
}
type Y struct {
name string
sex string
}
// 实现接口A
func (x X) aMethod() {
fmt.Println("I am ", x)
}
func (y Y) aMethod() {
fmt.Println("I am", y)
}
// 定义一个接收 X 和 Y 对象的函数
func test(a A) {
a.aMethod()
}
【A】:这时候我们需要用到接口 类型断言
来确定传进来的到底是 x 还是 y。
- 方式1:
instance := 接口对象.(实际类型)
,这种不安全,会引发 panic()instance, ok := 接口对象.(实际类型)
,这种就安全。接口对象是实际类型时,ok 为 true
- 方式2:switch
switch instance := 接口对象.(type) { case 实际类型1: ... case 实际类型2: ... ... }
于是在 test()
函数中我们可以这样写:
// 定义一个接收 X 和 Y 对象的函数
func test (a A) {
if ins, ok := a.(X); ok { // 使用 if 断言
fmt.Println(ins.age)
} else if ins, ok := a.(Y); ok {
fmt.Println(ins.sex)
}
}
或者这样写:
// 定义一个接收 X 和 Y 对象的函数
func test(a A) {
switch ins := a.(type) { // 使用 switch 判断
case X:
fmt.Println(ins.age)
case Y:
fmt.Println(ins.sex)
}
}
值类型实现 VS 指针类型实现
我们先定义一个接口,和两个结构体
然后分别使用 值类型 和 指针类型 实现接口
type Aer interface {
method()
}
type X struct {}
type Y struct {}
// 值类型实现
func (x X) method() {}
// 指针类型实现
func (y *Y) method() {}
此时实现接口的是 X
类型 和 *Y
类型。
接着我们来使用一下:
func main() {
x := X{}
y := Y{}
var a1 Aer = x // a1 可以接收 x 类型
var a2 Aer = &x // a2 可以接收 *X 类型
var a3 Aer = y // !报错,a3 不可以接收 y 类型
var a4 Aer = &y // a4 可以接收 *y 类型
}
在使用值类型实现接口之后,不管是 X 结构体类型
还是 *X 结构体指针类型
,都可以赋值给该接口变量。
因为在 Go 语言中有对指针类型变量求值的语法糖,X 指针 &x
内部会自动求值。
var a2 Aer = &x
会变成 var a2 Aer = *(&x)
。
而 Y 是用指针类型实现的,所以只能传递指针 &y
给 Aer 接口变量。