Go的接口语法
2.1、基本语法
在 Golang 中,interface 是一组 method 的集合,是 duck-type programming 的一种体现。不关心属性(数据),只关心行为(方法)。具体使用中你可以自定义自己的 struct,并提供特定的 interface 里面的 method 就可以把它当成 interface 来使用。
if something looks like a duck, swims like a duck and quacks like a duck then it’s probably a duck.
每个接口由数个方法组成,接口的定义格式如下:
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
其中:
-
接口名:使用
type
将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er
,如有写操作的接口叫Writer
,有字符串功能的接口叫Stringer
等。接口名最好要能突出该接口的类型含义。 -
方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
-
参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。
2.2、实现接口的条件
一个对象只要全部实现了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表。
我们来定义一个Animal
接口:
// Animal 接口
type Animal interface {
sleep()
}
定义Dog
和Cat
两个结构体:
type Dog struct {
name string
}
type Cat struct {
name string
}
因为Animal
接口里只有一个sleep
方法,所以我们只需要给Dog
和Cat
类分别实现sleep
方法就可以实现Sayer
接口了。
// Dog实现了Animal接口
func (d Dog) sleep() {
fmt.Printf("%s正在侧卧着睡觉\n", d.name)
}
// Cat实现了Animal接口
func (c Cat) sleep() {
fmt.Printf("%s正在卷成团睡觉\n", c.name)
}
接口的实现就是这么简单,只要实现了接口中的所有方法,就实现了这个接口。
2.3、接口类型变量
那实现了接口有什么用呢?
接口类型变量能够存储所有实现了该接口的实例。 例如上面的示例中,Animal
类型的变量能够存储Dog
和Cat
类型的变量。
func foo(animal Animal) {
animal.sleep()
}
func main() {
var a Animal
var d = Dog{"川普"}
var c = Cat{"拜登"}
// 案例1
a = d
a.sleep()
a = c
a.sleep()
// 案例2
foo(d)
foo(c)
}
2.4、值和指针接收者实现接口
使用值接收者实现接口和使用指针接收者实现接口有什么区别呢?接下来我们通过一个例子看一下其中的区别。
(1)值接收者实现接口
package main
import (
"fmt"
)
type Animal interface {
sleep()
}
type Dog struct {
name string
}
// Dog实现了Animal接口
func (d Dog) sleep() {
fmt.Printf("%s正在侧卧着睡觉\n", d.name)
}
func main() {
var a Animal
var chuanPu = Dog{"川普"}
a = chuanPu // a可以接收Dog类型
chuanPu.sleep() // 将Dog类型chuanPu拷贝给接收者方法sleep的d,然后执行sleep方法
a = &chuanPu // a可以接受*Dog类型
a.sleep() //将*Dog类型chuanPu取值操作后拷贝给接收者方法sleep的d,然后执行sleep方法
}
从上面的代码中我们可以发现,使用值接收者实现接口之后,不管是Dog结构体对象还是结构体指针对象都可以赋值给该接口变量。因为Go语言中有对指针类型变量求值的语法糖,Dog指针a内部会自动求值川普
结构体对象然后拷贝赋值。
(2)指针接收者实现接口
同样的代码我们再来测试一下使用指针接收者有什么区别:
package main
import (
"fmt"
)
type Animal interface {
sleep()
}
type Dog struct {
name string
}
// Dog实现了Animal接口
func (d *Dog) sleep() {
fmt.Printf("%s正在侧卧着睡觉\n", d.name)
}
func main() {
var a Animal
var chuanPu = Dog{"川普"}
// a = chuanPu // a不可以接收Dog类型
a = &chuanPu // a只可以接收*Dog类型
a.sleep()
}
此时实现Animal的接口的是*Dog
类型,所以不能给a
传入Dog
类型的chuanPu
,此时a只能存储*Dog
类型的值,即&chuanPu
。
2.5、类型与接口的关系
(1)一个类型实现多个接口
一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。 例如,狗可以跑,也可以叫。我们就分别定义Runner接口和Sayer接口。
// Sayer 接口
type Sayer interface {
say()
}
// Runner 接口
type Runner interface {
run()
}
Dog既可以 实现Sleep接口,也可以实现Run接口。
package main
import (
"fmt"
)
// Sayer 接口
type Sayer interface {
say()
}
// Runner 接口
type Runner interface {
run()
}
type Dog struct {
name string
}
// 实现Sayer接口
func (d Dog) say() {
fmt.Printf("%s汪汪汪叫\n", d.name)
}
// 实现Runner接口
func (d Dog) run() {
fmt.Printf("%s吐舌头跑\n", d.name)
}
func main() {
var s Sayer
var r Runner
var d = Dog{name: "旺财"}
s = d
s.say()
r = d
r.run()
}
(2)多个类型实现同一接口
Go语言中不同的类型还可以实现同一接口 首先我们定义一个Runner
接口,它要求必须有一个run
方法。
// Runner 接口
type Runner interface {
run()
}
例如狗可以跑,汽车也可以跑,可以使用如下代码实现这个关系:
type Car struct {
brand string
}
type Dog struct {
name string
}
// Runner 接口
type Runner interface {
run()
}
// Dog实现Runner接口
func (d Dog) run() {
fmt.Printf("%s正在吐舌头跑\n", d.name)
}
// Car实现Runner接口
func (c Car) run() {
fmt.Printf("%s正在飞速行驶\n", c.brand)
}
这个时候我们在代码中就可以把狗和汽车当成一个会动的物体来处理了,不再需要关注它们具 体是什么,只需要调用它们的move
方法就可以了。
func main() {
var r Runner
var d = Dog{name: "旺财"}
var c = Car{brand: "奔驰"}
r = d
r.run()
r = c
r.run()
}
2.6、类型嵌套
一个类型(struct)必须实现了接口中的所有方法才能称为实现了该接口。
// Animal 接口
type Animal interface {
sleep()
run()
}
type Dog struct {
name string
}
// Dog实现了run方法和sleep方法,即实现了Animal接口
func (d Dog) run() {
fmt.Printf("%s正在吐舌头跑\n", d.name)
}
/*func (d Dog) sleep() {
fmt.Printf("%s正在侧翻睡\n", d.name)
}*/
func main() {
var r Animal
var d = Dog{name: "旺财"}
r = d
r.run()
}
一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。
package main
import (
"fmt"
)
// WashingMachine 洗衣机
type WashingMachine interface {
wash()
dry()
}
// 甩干器
type Dryer struct{
brand string
}
// 实现WashingMachine接口的dry()方法
func (d Dryer) dry() {
fmt.Println("甩干衣服")
}
// 海尔洗衣机
type Haier struct {
name string
Dryer //嵌入甩干器
}
// 实现WashingMachine接口的wash()方法
func (h Haier) wash() {
fmt.Println("洗衣服")
}
func main() {
var wm WashingMachine
wm = Haier{
name: "海尔洗衣机",
Dryer:Dryer{
brand:"西门子",
},
}
wm.wash()
wm.dry()
}
2.7、接口嵌套
接口与接口间可以通过嵌套创造出新的接口。
package main
import (
"fmt"
)
// Animal 接口
/*type Animal interface {
sleep()
run()
}*/
// Sleep接口
type Sleep interface {
sleep()
}
// Runner接口
type Runner interface {
run()
}
// Animal接口
type Animal interface {
Sleep
Runner
}
type Dog struct {
name string
}
// Dog实现了run方法和sleep方法,即实现了Animal接口
func (d Dog) run() {
fmt.Printf("%s正在吐舌头跑\n", d.name)
}
func (d Dog) sleep() {
fmt.Printf("%s正在侧翻睡\n", d.name)
}
func main() {
var r Animal
var d = Dog{name: "旺财"}
r = d
r.run()
r.sleep()
}