对于golang接口interface的学习记录

1 篇文章 0 订阅
1 篇文章 0 订阅

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(){
	Inf := new(AnimalI)
	a := animal{}
	Inf = a
	Inf.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语言关于接口的规则,规则如下:

  1. golang对象实现interface 无需任何关键词,只要该对象的方法集中包含的接口定义的所有方法且方法签名一致

  2. 对象实现接口可以借助struct内嵌特性,实现接口的默认实现

    type cat struct {
    	animal
    }
    
  3. 类型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()  {}  // 这里
    
  4. 类型 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自己调用自己的方法,只是你可以这么写而已

  5. 类型 T 实现接口,不管是 T 还是*T 都实现了该接口(不多赘述)

  6. 类型 *T 实现接口,只有*T 实现了该接口
    所以如果实现接口的是 *animal 的话 ,AnimalI是不能接受animal的,因为animal没有实现该接口,所以在使用时,往往用animal实现接口,确保无论是指针类型还是值类型都实现该接口

  • 14
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值