Go 的接口

106 阅读4分钟

「这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战」。

和结构体的区别

结构体类型包裹的是它的字段声明,而接口类型包裹的是它的方法定义

定义接口

type interface_name interface {
   method_name1([args ...arg_type]) [return_type]
   method_name2([args ...arg_type]) [return_type]
   method_name3([args ...arg_type]) [return_type]
   ...
   method_namen([args ...arg_type]) [return_type]
}

可以看到是通过 type 来定义接口的

实现接口的简单🌰

package main

import "fmt"

// 接口
var stuInterface interface {
    // 定义了一个方法
	PrintAge()
}

// 结构体
type Student struct {
	name string
	age  int
}

// 方法
func (s *Student) PrintAge() {
	fmt.Println(s.age)
}

func main() {
	stu := Student{
		name: "poloyy",
		age:  24,
	}
	stu.PrintAge()
}

运行结果

24
  • Student 结构体实现了接口的 PrintAge 方法,就可以认为 Student 结构体是 stuInterface 接口的一个实现
  • 可以看到上述代码没有 stuInterface 接口的影子,因为 Go 中接口的实现都是隐式的,只需要实现 PrintAge 方法就实现了 stuInterface 接口

接口有多个方法的🌰

package main

import "fmt"

type Phone interface {
	call(int) string
	sendMessage(string) string
}

type PhoneStruct struct {
	name  string
	price float64
}

type HuaWei struct {
	PhoneStruct
}

type XiaoMi struct {
	PhoneStruct
}

func (x XiaoMi) sendMessage(msg string) string {
	return fmt.Sprintf("msg is %v", msg)
}

func (x XiaoMi) call(num int) string {
	return fmt.Sprintf("u call phone is %v", num)
}

func main() {
	h := XiaoMi{}
	fmt.Println(h)
	fmt.Println(h.call(13501483922))
	fmt.Println(h.sendMessage("hi"))

	h = XiaoMi{}
	h.name = "test"
	h.price = 1.11
	fmt.Println(h)
}

运行结果

{{ 0}}
u call phone is 13501483922
msg is hi
{{test 1.11}}

如何判断接口是否实现成功?

package main

import "fmt"

type Phone interface {
	call(int) string
	sendMessage(string) string
}

type PhoneStruct struct {
	name  string
	price float64
}

type HuaWei struct {
	PhoneStruct
}

func (h HuaWei) call(num int) string {
	return fmt.Sprintf("华为 call phone is %v", num)
}

func main() {
	var _ Phone = new(HuaWei)
}

这样编译期间就会报错,因为 HuaWei 结构体没有实现 sendMessage() 方法

Goland 智能提醒

会在 interface 那提醒实现了接口的方法

也会在方法那提醒实现了哪个接口

接口嵌套

接口嵌套就是一个接口中包含了其他接口,如果要实现外部接口,那么就要把内部嵌套的接口对应的所有方法全实现了

package main

import "fmt"

// A 定义3个接口
type A interface {
	test1()
}

type B interface {
	test2()
}

// C 定义嵌套接口
type C interface {
	A
	B
	test3()
}

type Person struct {
	// 如果想要实现接口 C,那不止要实现接口 C 的方法, 还要实现接口A、B 的方法
}

func (p Person) test1() {
	fmt.Println("test1 方法................")
}

func (p Person) test2() {
	fmt.Println("test2 方法................")
}

func (p Person) test3() {
	fmt.Println("test3 方法................")
}

func main() {
	// 声明为 A 接口类型,因为 Person 实现了接口,所以可以赋值
	var a A = Person{}
	a.(C).test1()
	// 但 a 仍然是 A 接口类型,需要强转为 C 才有test2、test3 方法
	a.(C).test2()
	a.(C).test3()

	person := Person{}
	person.test3()
	person.test2()
	person.test1()
}

运行结果

test1 方法................
test2 方法................
test3 方法................
test3 方法................
test2 方法................
test1 方法................

重点

  • 因为接口类型与其他数据类型不同,它是没法被实例化的
  • 既不能通过调用 new 函数或 make 函数创建出一个接口类型的值,也无法用字面量来表示一个接口类型的值
  • 接口类型声明中的这些方法所代表的就是该接口的方法集合
  • 一个接口的方法集合就是它的全部特征

鸭子类型

对于任何数据类型,只要它的方法集合中完全包含了一个接口的全部特征(即全部的方法),那么它就一定是这个接口的实现类型

type Pet interface {
    SetName(name string)
    Name() string
    Category() string
}
  • Pet 接口,包含了 3 个方法定义,这 3 个方法共同组成了接口类型 Pet 的方法集合
  • 只要一个数据类型的方法集合中有这 3 个方法,那么它就一定是 Pet 接口的实现类型
  • 这是 一种无侵入式的接口实现方式,这种方式还有一个专有名词,叫“Duck typing”,即鸭子类型

怎样判定一个数据类型的某一个方法实现的就是某个接口类型中的某个方法呢?

有两个充分必要条件

  1. 两个方法的签名需要完全一致
  2. 两个方法的名称要一模一样

显然,这比判断一个函数是否实现了某个函数类型要更加严格一些

接口的定义

使用者:接口由使用者定义

实现者:接口的实现是隐式的,只要实现接口里的方法

接口的值类型

接口一般不会用到接口指针

接口变量里面有什么

实现者的类型和实现者的值/实现者的指针,指向了实现者

接口变量自带指针

接口变量同样采用值传递,几乎不需要使用接口的指针

指针接收者实现只能以指针方式使用,值都可以