初心是记录和总结,自己学习Go语言的历程。如果能帮助到你,这是我的荣幸。
接口
它的定义是这样的:接口把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口,接口也是一种数据类型。
- 将共性的方法定义在一起:注意是定义哦,并没有去实现方法。
- 其他类型实现这些方法:这表示是通过方法的形式,通过接收者指定具体的类型,并且实现接口中定义的方法
- 第1点的操作称为:定义接口;第2点的操作称为:实现接口
重点在于这个共性
我们通过例子来了解为什么需要接口,而甚至网上有文称Go语言是面向接口式编程,这个还需我们一探究竟,至少目前先来理解Go语言接口的基本使用。
例子:养宠物
这里我想了一个例子:
老程啊,他喜欢养宠物,猫猫狗狗的,他每日的日常工作就是需要去喂这些动物,猫和狗需要吃掉喂的东西。
我们通过编程语言的方式去描述这个例子:
1.涉及到三个结构体:人、猫、狗:
package main
import "fmt"
// 定义一个人
type Person struct {
name string
dog Dog
cat Cat
}
// 定义狗
type Dog struct {
name string
age int
}
// 定义猫
type Cat struct {
name string
age int
}
func main() {
person := Person{name: "老程", dog: Dog{name: "小黑", age: 3}, cat: Cat{name: "发财", age: 5}}
fmt.Println(person) //{老程 {小黑 3} {发财 5}}
}
第一个问题就出现了,可以发现Dog
和Cat
这个两个结构体中,都存在name
和age
这两个属性属于重复性的内容了,有过面向对象的思想,我们能不能构建一个父类包含这两个字段,然后Dog
和Cat
作为子类继承后得到相应字段呢?引出例子第一个需要我们学习的内容:
嵌套结构体
结构体A
中可以嵌套结构体B
,此时A
会继承B
中所有定义的字段。注意:B
是父类!
我们改写代码,定义一个父类Animal
,让Dog
和Cat
继承它,改动后代码如下:
package main
import "fmt"
type Person struct {
name string
dog Dog
cat Cat
}
// 定义了一个父类:Animal
type Animal struct {
name string
age int
}
// Dog继承了Animal
type Dog struct {
//name string
//age int
Animal
}
// Cat继承了Animal
type Cat struct {
//name string
//age int
Animal
}
func main() {
//person := Person{name: "老程", dog: Dog{name: "小黑", age: 3}, cat: Cat{name: "发财", age: 5}}
//fmt.Println(person) //{老程 {小黑 3} {发财 5}}
person := Person{name: "老程", dog: Dog{Animal{name: "小黑", age: 3}}, cat: Cat{Animal{name: "发财", age: 5}}}
fmt.Println(person) //{老程 {{小黑 3}} {{发财 5}}}
fmt.Println(person.dog.name) //小黑
}
2.人需要去喂动物
我们会想到使用方法去完成对喂动物的定义,方法的核心代码如下:
第一步:我们会尝试编写一个给Person
的方法,但是这个时候传入的参数我们就开始犯愁了,好像目前并没有一个类型能表示两个类型,而方法名是唯一的,所以我们会写两个方法,分别对应Cat
和Dog
func (*Person) feedCat(cat Cat) {
fmt.Println(cat.name)
fmt.Println("吃鱼")
}
func (*Person) feedDog(dog Dog) {
fmt.Println(dog.name)
fmt.Println("吃骨头")
}
在Main函数中调用
person.feedDog(person.dog)
person.feedCat(person.cat)
第二个问题也出现了,我需要一个存储任意类型值的一个参数,就像是Object一样。这也就是接口的空接口的特性,它就可以存储任意类型的值。我们改写代码:
空接口
- 空接口的定义是
var x interface{}
,作为形参时var可以省略,x可以存任何类型的值。 - 取出
x
真实存的值,通过x.(type)
的用法,type里写上想取出值的类型,会返回v,ok
,其中v
表示实际值,ok
表示是否存在,如果不存在v
为空,ok
为false
。
func (*Person) feed(Animal interface{}) {
switch v := Animal.(type) {
case Dog:
fmt.Println(v.name)
fmt.Println("吃骨头")
case Cat:
fmt.Println(v.name)
fmt.Println("吃鱼")
}
}
在Main函数中调用
person.feed(person.dog)
person.feed(person.cat)
可能你还不理解空接口为什么可以这样用,我们接下往下学习。观察现在的代码,我们会发现第三个问题,我们发现动物吃东西的逻辑,我们现在是用打印函数解决的,实际中应该去真的执行什么代码,至少是关于类型的方法,我们会给Cat
和Dog
加上对应的方法:
func (Cat) eat() {
fmt.Println("吃鱼")
}
func (Dog) eat() {
fmt.Println("吃骨头")
}
加完方法后,改写上述feed
方法的时候,我们很容易会发现,Animal interface{}
我们使用的是空接口这个类型去接收任意参数的,当使用断言Animal.(type)
的时候,才清楚要去具体操作什么类型的数据,所以在调用方法的时候变成了这样,都是使用v.eat()
的方式去执行相应的语句:
func (*Person) feed(Animal interface{}) {
switch v := Animal.(type) {
case Dog:
fmt.Println(v.name)
//fmt.Println("吃骨头")
v.eat()
case Cat:
fmt.Println(v.name)
//fmt.Println("吃鱼")
v.eat()
}
}
我们会发现,是因为我们定义的是空接口,所以程序不知道具体需要操作哪些类型,在程序中还需要通过断言的方式去判断类型,这并不利于程序的扩展,我们会希望有这样的现象发生:feed
方法应该自动识别我传入参数的类型,或者至少我不用再去判断类型再执行方法,因为在这个方法中我执行的都是共性方法,只是根据传入的实际类型去变动而已。
我们的接口已经呼之欲出了!再来回顾一下接口两个注意事项
- 将共性的方法定义在一起:注意是定义哦,并没有去实现方法。
- 其他类型实现这些方法:这表示是通过方法的形式,通过接收者指定具体的类型,并且实现接口中定义的方法
我们先来实现第一点,根据共性方法,定义接口:
定义接口
很明显,在这个例子中两个动物的eat
方法是共性的,只是随着接收者不同而不同。
type Eater interface {
eat()
}
使用type
和 interface
两个关键字定义了名字为Eater
的接口,而eat()
就是我们说共性方法。
第二点,我们已经在之前发现的第三个问题中写好了。我们知道空接口可以存任意类型,这很容易理解是因为空接口没有定义任何方法,没有固定的类去实现它,那么它当然可以存任意类型。
那么现在这个定义好方法的接口,并且由具体的类去实现了,它是不是只存这种类型的数据呢,这种具体指向存储类型的接口,是不是不用类型断言推导呢?现在见证奇迹的时候到了!
//person.feed(person.dog)
//person.feed(person.cat)
// 定义 Eater 接口
var x Eater
// 使用 Eater 接口去存实现接口的类型
x = person.dog
//传入接口类型
person.feed(x)
x = person.cat
person.feed(x)
在feed
将形参改掉,如下:
func (*Person) feed(Animal Eater) {
Animal.eat()
}
对比一下原feed方法那种空接口断言的方式,是不是直呼惊奇!!代码清爽了很多。
最后附上完整代码感受一下接口的魅力,它对程序代码的扩展提供了很大的帮助,因为动物如果增多,我们是不需要再改动feed
方法的。
package main
import "fmt"
type Person struct {
name string
dog Dog
cat Cat
}
type Animal struct {
name string
age int
}
type Dog struct {
Animal
}
type Cat struct {
Animal
}
// 定义接口
type Eater interface {
eat()
}
func main() {
person := Person{name: "老程", dog: Dog{Animal{name: "小黑", age: 3}}, cat: Cat{Animal{name: "发财", age: 5}}}
var x Eater
x = person.dog
person.feed(x)
x = person.cat
person.feed(x)
}
// 实现接口
func (Cat) eat() {
fmt.Println("吃鱼")
}
func (Dog) eat() {
fmt.Println("吃骨头")
}
// 通过接口去调用
func (*Person) feed(Animal Eater) {
Animal.eat()
}