Go 学习笔记(71)— Go 接口 interface (接口定义、接口实现、接口调用、值接收者、指针接收者)

1. 接口的定义

接口是和调用方的一种约定,它是一个高度抽象的类型,不用和具体的实现细节绑定在一起。接口要做的是定义好约定,告诉调用方自己可以做什么,但不用知道它的内部实现,这和我们见到的具体的类型如 intmapslice 等不一样。

接口的定义和结构体稍微有些差别,虽然都以 type 关键字开始,但接口的关键字是 interface,表示自定义的类型是一个接口。

也就是说 person 是一个接口,它有两个方法 sayName() stringsayAge() int,整体如下面的代码所示:

type person interface {
    
    
	sayName() string
	sayAge() int
}
}

针对 person 接口来说,它会告诉调用者可以通过它的 sayName() 方法获取姓名字符串,通过它的 sayAge() 方法获取年龄,这就是接口的约定。至于这个字符串怎么获得的,长什么样,接口不关心,调用者也不用关心,因为这些是由接口实现者来做的。

2. 接口的实现

接口的实现者必须是一个具体的类型,以 student 结构体为例,

type student struct {
    
    
	name string
	age  int
}

让它来实现 person 接口,如下代码所示:

func (s student) sayName() string {
    
    
	fmt.Printf("name is %v\n", s.name)
	return s.name
}

func (s student) sayAge() int {
    
    
	fmt.Printf("name is %v\n", s.age)
	return s.age
}

给结构体类型 student 定义一个方法,这个方法和接口里方法的签名(名称、参数和返回值)一样,这样结构体 student 就实现了 person接口。

注意:如果一个接口有多个方法,那么需要实现接口的每个方法才算是实现了这个接口。

3. 接口赋值

接口赋值在 Go 语言中分为如下两种情况:

  • 将对象实例赋值给接口;
  • 将一个接口赋值给另一个接口。

3.1 对象实例赋值给接口

先讨论将某种类型的对象实例赋值给接口,这要求该对象实例实现了接口要求的所有方法,如下:

type Integer int

func (a Integer) Less(b Integer) bool {
    
    
	return a < b
}

func (a *Integer) Add(b Integer) {
    
    
	*a += b
}

相应地,我们定义接口 LessAdder ,如下:

扫描二维码关注公众号,回复: 13073459 查看本文章
type LessAdder interface {
    
    
	Less(b Integer) bool
	Add(b Integer)
}

现在有个问题:假设我们定义一个 Integer 类型的对象实例,怎么将其赋值给 LessAdder接口呢?
应该用下面的语句(1),还是语句(2)呢?

var a Integer = 1
var b LessAdder = &a ... (1)
var b LessAdder = a ... (2)

答案是应该用语句(1)。原因在于,Go 语言可以根据下面的函数:

func (a Integer) Less(b Integer) bool {
    
    
	return a < b
}

自动生成一个新的 Less() 方法:

func (a *Integer) Less(b Integer) bool {
    
    
	return (*a).Less(b)
}

这样,类型 *Integer 就既存在 Less() 方法,也存在 Add() 方法,满足 LessAdder 接口。
而从另一方面来说,根据

	func (a *Integer) Add(b Integer)

这个函数无法自动生成以下这个成员方法:

func (a Integer) Add(b Integer) {
    
    
	(&a).Add(b)
}

因为 (&a).Add() 改变的只是函数参数a,对外部实际要操作的对象并无影响,这不符合用
户的预期。

所以,Go 语言不会自动为其生成该函数。因此,类型 Integer 只存在 Less()方法,缺少 Add() 方法,不满足 LessAdder 接口,故此上面的语句(2)不能赋值。

为了进一步证明以上的推理,我们不妨再定义一个 Lesser 接口,如下:

type Lesser interface {
    
    
	Less(b Integer) bool
}

然后定义一个 Integer 类型的对象实例,将其赋值给 Lesser 接口:

var a Integer = 1
var b1 Lesser = &a ... (1)
var b2 Lesser = a ... (2)

正如我们所料的那样,语句(1)和语句(2)均可以编译通过。

3.2 将一个接口赋值给另一个接口

Go 语言中,只要两个接口拥有相同的方法列表(次序不同不要紧),那么它们就是等同的,可以相互赋值。

4. 接口调用

实现了 person 接口后就可以使用了。首先我先来定义一个可以打印 person 接口的函数,如下所示:

func printPersonInfo(p person) {
    
    
	fmt.Printf("name is %v\n", p.sayName())
	fmt.Printf("age is %v\n", p.sayAge())
}

这个被定义的函数 printPersonInfo,它接收一个 person 接口类型的参数,然后打印出 person 接口的 sayName() 方法返回的字符串,sayAge() 方法返回年龄

printPersonInfo 这个函数的优势就在于它是面向接口编程的,只要一个类型实现了 Stringer 接口,都可以打印出对应的字符串,而不用管具体的类型实现。

因为 student 实现了 person 接口,所以变量 stu 可以作为函数 printPersonInfo 的参数,可以用如下方式打印:

printPersonInfo(stu)

输出

name is wohu
age is 20

现在让结构体 teacher 也实现 person 接口,如下面的代码所示:

type teacher struct {
    
    
	name string
	age  int
}

func (t teacher) sayName() string {
    
    
	return t.name
}

func (t teacher) sayAge() int {
    
    
	return t.age
}

因为结构体 teacher 也实现了 person 接口,所以 printPersonInfo 函数不用做任何改变,可以直接被使用,如下所示:

printPersonInfo(t)

输出结果:

name is Jack
age is 40

这就是面向接口的好处,只要定义和调用双方满足约定,就可以使用,而不用管具体实现。接口的实现者也可以更好的升级重构,而不会有任何影响,因为接口约定没有变。

5. 值接收者和指针接收者

我们已经知道,如果要实现一个接口,必须实现这个接口提供的所有方法,而且在上节课讲解方法的时候,定义一个方法,有值类型接收者和指针类型接收者两种。二者都可以调用方法,因为 Go 语言编译器自动做了转换,所以值类型接收者和指针类型接收者是等价的。但是在接口的实现中,值类型接收者和指针类型接收者不一样,下面我会详细分析二者的区别。

上面已经验证了结构体类型实现了 person 接口,那么结构体对应的指针是否也实现了该接口呢?我通过下面这个代码进行测试:

printPersonInfo(&s)

输出结果:

name is wohu
age is 20

测试后会发现,把变量 t 的指针作为实参传给 printPersonInfo 函数也是可以的,编译运行都正常。这就证明了以值类型接收者实现接口的时候,不管是类型本身,还是该类型的指针类型,都实现了该接口。

示例中值接收者(s student)实现了 person 接口,那么类型 student 和它的指针类型 *student 就都实现了 person 接口。

现在,我把接收者改成指针类型,如下代码所示:

func (s *student) sayName() string {
    
    
	return s.name
}

func (s *student) sayAge() int {
    
    
	return s.age
}

修改成指针类型接收者后会发现,示例中这行 printPersonInfo(stu) 代码编译不通过,提示如下错误:

demo.go:46:17: cannot use stu (type student) as type person in argument to printPersonInfo:
	student does not implement person (sayAge method has pointer receiver)

意思就是类型 student 没有实现 person 接口。这就证明了以指针类型接收者实现接口的时候,只有对应的指针类型才被认为实现了该接口。

总结:

  • 当值类型作为接收者时,student 类型和 *student 类型都实现了该接口;

  • 当指针类型作为接收者时,只有 *student 类型实现了该接口;

可以发现,实现接口的类型都有 *student,这也表明指针类型比较万能,不管哪一种接收者,它都能实现该接口。

package main

import "fmt"

type student struct {
    
    
	name string
	age  int
}

type teacher struct {
    
    
	name string
	age  int
}

type person interface {
    
    
	sayName() string
	sayAge() int
}

func (t teacher) sayName() string {
    
    
	return t.name
}

func (t teacher) sayAge() int {
    
    
	return t.age
}

func (s *student) sayName() string {
    
    
	// fmt.Printf("name is %v\n", s.name)
	return s.name
}

func (s *student) sayAge() int {
    
    
	// fmt.Printf("age is %v\n", s.age)
	return s.age
}

func printPersonInfo(p person) {
    
    
	fmt.Printf("name is %v\n", p.sayName())
	fmt.Printf("age is %v\n", p.sayAge())
}

func main() {
    
    
	stu := student{
    
    name: "wohu", age: 20}
	// t := teacher{name: "Jack", age: 40}
	printPersonInfo(stu)
	// printPersonInfo(t)

}

猜你喜欢

转载自blog.csdn.net/wohu1104/article/details/111242632