golang接口,interface
package interfaceCase
import "fmt"
// AnimalI 声明接口,定义行为
type AnimalI interface {
// Each 吃
Each()
// Drink 喝
Drink()
// Sleep 睡觉
Sleep()
// Run 跑
Run()
}
// animal 实现了 Animal接口
type animal struct{}
// cat 继承了animal 那么cat也实现了Animal接口
type cat struct {
animal
}
func (anim animal) Each() {}
func (anim animal) Drink() {}
func (anim animal) Sleep() {}
func (anim animal) Run() {}
// NewAnimal 工厂函数创建animal实例
func NewAnimal() AnimalI {
return &animal{} // 返回地址
}
// NewCat 工厂函数创建cat实例
func NewCat() AnimalI {
return cat{} //
}
在大多数教程中,接口的教程往往是这样告诉你怎么去认识他的。
func main(){
var animali AnimalI
a := animal{}
animali = a
animali.Each()
}
这样旨在展示当一个数据类型实现了一个接口后,这个接口就可以存放他的值,并调用它的方法。事实上在正常使用上,用接口去调用的方法,并不是显式的展现出来的,这样的教程很容易让人误以为,我们应该在main中去new一个接口,然后再进行赋值;调用方法的操作,这样不就显得很臃肿很没用了吗。
我们不妨换一种方式
func NewAnimal() AnimalI {
return &animal{}
}
NewAnimal 工厂函数创建animal实例,因为animal实现了接口AnimalI,所以AnimalI可以接收&animal,同样的也可以接收animal类型的值,原因如下:
类型 T 实现接口,不管是 T 还是*T 都实现了该接口 cat 同理
这样做让我们可以不必再new一个接口的实例,而是直接返回接口类型的值,并且将animal实例传入这个接口一起返回,从而实现用接口调用方法。
如果我们并不是这样,而是直接返回*animal类型的值,
func NewAnimal() *animal {
return &animal{}
}
那么再创建子类的时候就需要再修改子类的返回值类型,不符合面向对象的可继承,同样也不方便维护和更改。
基于这样的特性,接口往往并不显式的展示出来。
我们需要一个好的应用场景,比如在下面这段代码中,我们创建一个工厂函数。一样是new了一个接口,但是将创建一个对象实例,和将对象赋值给AnimalI同时实现,这个赋值过程自然就为接口开辟了空间,不必再去用new()。
这样的操作简洁明了,也突出了工厂模式的封装特性。
func NewAnimal() AnimalI {
return &animal{}
}
func main(){
animal := NewAnimal()
animal.Each()
}
事实上这两段代码的作用几乎相同,但是在实用性上有很大的区别,没有人会想在自己的main程序中去声明一大堆变量。可以看到,在工厂模式下,我们的main程序中只用用NewAnimal这个简单明了的函数去新建一个实例,然后直接使用就可以了。
可以看到,*animal并没有显式的去实现AnimalI接口,但是AnimalI依然可以接收*animal,这是go语言关于接口的规则,规则如下:
-
golang对象实现interface 无需任何关键词,只要该对象的方法集中包含的接口定义的所有方法且方法签名一致
-
对象实现接口可以借助struct内嵌特性,实现接口的默认实现
type cat struct { animal }
-
类型T 方法集包含全部receiver T 方法,类型 *T 方法集包含 receiver T + *T 方法
可以发现我们实现接口的是receiver T 但是我们依然可以将 *T 传入这个接口中,并且*T拥有receiver T 的所有方法集
func NewAnimal() AnimalI { return &animal{} }
比如说像这样做,*animal 依旧是实现了这个接口,但是animal没有实现,并且并不建议这么做,即使这么做不会出现error
func (anim animal) Each() {} func (anim animal) Drink() {} func (anim animal) Sleep() {} func (anim *animal) Run() {} // 这里
-
类型 T 的实例 value 或者 pointer可以调用全部的方法,编译器会自动转换
例如:(注:这里不在意接口实现),可以看到Each是 animal 的方法 但是我们用pointer实例依然可以调用,反之亦然
func (anim animal) Each() {} func (anim animal) Drink() {} func (anim *animal) Sleep() {} func (anim *animal) Run() {}
var animp *animal var animv animal animp.Each() // 最后的结果是 animal.Each() animv.Sleep() // 最后的结果是 *animal.Sleep()
可以看到虽然用pointer调用value的方法,其实最后程序执行的还是value自己调用自己的方法,只是你可以这么写而已
-
类型 T 实现接口,不管是 T 还是*T 都实现了该接口(不多赘述)
-
类型 *T 实现接口,只有*T 实现了该接口
所以如果实现接口的是 *animal 的话 ,AnimalI是不能接受animal的,因为animal没有实现该接口,所以在使用时,往往用animal实现接口,确保无论是指针类型还是值类型都实现该接口