Go语言(基础)——接口

1、前言

  • 接口是 Go 语言整个类型系列的基石,C++,Java 使用"侵入式"接口,主要表现在实现类需要明确声明自己实现(implement)了某个接口。这种强制性的接口继承方式是面向对象编程思想发展过程中一个遭受相当多质疑的特性。Go 语言采用的是“非侵入式接口",Go 语言的接口有其独到之处:只要类型 T 的公开方法完全满足接口 I 的要求,就可以把类型 T 的对象用在需要接口 I 的地方,所谓类型 T 的公开方法完全满足接口 I 的要求,也即是类型 T 实现了接口 I 所声明的所有方法。会一点 Java 的朋友应该知道,Java 中的接口实现了多态这一作用,Go 语言中的接口也不例外。

2、初识接口

2.1、接口的定义
type interfaceName interface {
	funcName1(params...) dataType...
	funcName2(params...) dataType...
	//省略若干方法
}
  • 上面代码里在 interfaceName 接口里定义了一堆方法,params… 代表方法中的若干参数,dataType… 表示方法的返回列表,现在,只要有一个类型实现了 interfaceName 里的所有方法,那么这个类型就实现了 interfaceName 接口。
2.2、入门案例
type Person interface {
	Talk() string
	Eat(what string)
}

type Man struct {
	Message string
}

type Woman struct {
	Message string
}

func (m Man) Talk() string {
	return m.Message + "在说话"
}

func (m Man) Eat(what string) {
	fmt.Printf("%s说%s真好吃", m.Message, what)
}

func (w Woman) Talk() string {
	return w.Message + "在说话"
}

func main() {
	m := Interface.Man{Message: "男人"}
	//w := Interface.Woman{Message: "女人"}
	var p Interface.Person
	p = m
	p.Eat("火锅")
	fmt.Println(p.Talk())
	//p = w
	//报错:cannot use w (type Interface.Woman) as type Interface.Person in assignment:Interface.Woman does not implement Interface.Person (missing Eat method)
}
2.3、空接口
  • 空接口就是不含任何方法的接口,所以所有类型都实现了空接口,那么任何类型都可以给空接口赋值,这样也就实现了多态,话不多说,上例子:
type Man struct {
	Message string
	Name string
}

func DoSth(p interface{}) { //p 可以存储任意类型的值
	fmt.Println(p)
}

func main() {
	Interface.DoSth(1) //可以存储基本数据类型
	m := &Interface.Man{
		Message: "男人",
		Name:    "嬴政",
	}
	Interface.DoSth(*m) //可以存储结构体值
	Interface.DoSth(m) //可以存储指针
}
  • 运行上述代码,输出如下:
1
{男人 嬴政}
&{男人 嬴政}
2.4、类型断言
  • 所谓类型断言,就是判断一个接口,它存储的数据到底是什么类型的,比如我要写一个函数,这个函数接受一个空接口作为参数,这个函数的作用是,判断传进来的接口存储的是 Man 结构体还是 Woman 结构体,如果是 Man 结构体,那么请去男厕所,如果是 Woman 结构体,请去女厕所,看代码:
func Toilet(p interface{}) {
	fmt.Println("欢迎来到智能厕所")
	if man, ok := p.(Man); ok {
		fmt.Printf("%s先生,请去左手边的男厕所\n", man.Name)
	} else if woman, ok := p.(Woman); ok {
		fmt.Printf("%s女士,请去右手边的女厕所\n", woman.Name)
	} else {
		fmt.Println("你是男人还是女人?")
	}
}

func main() {
	man := Interface.Man{
		Message: "男人",
		Name:    "嬴政",
	}
	woman := Interface.Woman{
		Message: "女人",
		Name:    "吕雉",
	}
	Interface.Toilet(man)
	Interface.Toilet(woman)
	Interface.Toilet("机器人")
}
  • 输出如下:
欢迎来到智能厕所
嬴政先生,请去左手边的男厕所
欢迎来到智能厕所
吕雉女士,请去右手边的女厕所
欢迎来到智能厕所
你是男人还是女人?
  • 上面代码使用 if 判断语句进行断言,还有一种常用用法是使用 switch-case 来进行类型断言,代码如下:
func Toilet2(p interface{}) {
	fmt.Println("欢迎来到智能厕所")
	switch p.(type) {
	case Man:
		fmt.Printf("%s先生,请去左手边的男厕所\n", p.(Man).Name)
	case Woman:
		fmt.Printf("%s女士,请去右手边的女厕所\n", p.(Woman).Name)
	default:
		fmt.Println("你是男人还是女人?")
	}
}

3、接口的实现与结构体的嵌套 >> 多态

  • 这里定义了五种结构体类型,Object 是基结构体,Creature、Item、Monster 与 Corpse 都是派生结构体,ObjectInterface 是一个接口,上述五种结构体都实现了这个接口。
type Object uint64

type Creature struct {
	Object
}

type Item struct {
	Object
}

type Monster struct {
	Creature
}

type Corpse struct {
	Item
}

type ObjectInterface interface {
	Object_Class() Object
}

func (c Creature) Object_Class() Object {
	return c.Object
}

func (i Item) Object_Class() Object {
	return i.Object
}

func (m Monster) Object_Class() Object {
	return m.Object
}

func (c Corpse) Object_Class() Object {
	return c.Object
}

func Logic(objectInterface ObjectInterface) {
	switch objectInterface.(type) {
	case Creature:
		fmt.Println("是一个生物")
	case Item:
		fmt.Println("是一个物品")
	case Monster:
		fmt.Println("是一个怪兽")
	case Corpse:
		fmt.Println("是一个尸体")
	default:
		fmt.Println("啥也不是")
	}
}

func main() {
	c := Struct.Creature{}
	Struct.Logic(c)
	m := Struct.Monster{}
	Struct.Logic(m)
	i := Struct.Item{}
	Struct.Logic(i)
	co := Struct.Corpse{}
	Struct.Logic(co)
}
  • 输出如下:
是一个生物
是一个怪兽
是一个物品
是一个尸体

4、关于接口在使用上需要注意的问题

4.1、值接收者和指针接收者
  • 实现了接收者是值类型的方法,相当于自动实现了接收者是指针类型的方法;而实现了接收者是指针类型的方法,不会自动生成对应接收者是值类型的方法。看例子:
type coder interface {
	code()
	debug()
}

type Gopher struct {
	language string
}

func (g Gopher)code() {
	fmt.Printf("I am coding %s language.\n", g.language)
}

func (g *Gopher)debug() {
	fmt.Printf("I am debugging %s language.\n", g.language)
}

func TransValueTest() {
	//var c coder = Gopher{language: "go"} 这里会报错,错误信息如下
	//cannot use Gopher literal (type Gopher) as type coder in assignment:Gopher does not implement coder (debug method has pointer receiver)
	//这样改就正确了:
	var c coder = &Gopher{language: "go"}
}
  • 看出正确代码和错误代码的差别了吗?第一次是将 Gopher 赋给了 coder;第二次则是将 &Gopher 赋给了 coder。
  • 错误代码报错是说,Gopher 没有实现 coder。因为 Gopher 类型并没有实现 debug 方法;表面上看, *Gopher 类型也没有实现 code 方法,但是因为 Gopher 类型实现了 code 方法,所以让 *Gopher 类型自动拥有了 code 方法。
  • 当然,上面的说法有一个简单的解释:接收者是指针类型的方法,很可能在方法中会对接收者的属性进行更改操作,从而影响接收者;而对于接收者是值类型的方法,在方法中不会对接收者本身产生影响。
  • 所以,当实现了一个接收者是值类型的方法,就可以自动生成一个接收者是对应指针类型的方法,因为两者都不会影响接收者。但是,当实现了一个接收者是指针类型的方法,如果此时自动生成一个接收者是值类型的方法,原本期望对接收者的改变(通过指针实现),现在无法实现,因为值类型会产生一个拷贝,不会真正影响调用者。
  • 最后,只要记住下面这点就可以了:
如果实现了接收者是值类型的方法,会隐含地也实现了接收者是指针类型的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wuxy_Fansj_Forever

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值