章节

9.1 接口定义与实现

本篇学习 Go 接口的定义、隐式实现与多态调用,并能用接口约束对象行为。

接口定义与实现

概念说明

接口(interface)用于描述“行为”。
只要某个类型拥有接口要求的全部方法,它就自动满足这个接口。

Go 不需要显式写 implements
implements 可以理解为“声明某个类型实现了某个接口”,一些语言需要这样写,但 Go 只看方法是否匹配。
这种方式叫隐式实现,也就是不写额外声明,只要方法集匹配就自动满足接口。

关键规则

  1. 接口里只声明方法签名,不写方法体。
  2. 类型只要方法匹配,就自动实现接口。
  3. 接口变量可以保存任意满足该接口的值。
  4. 接口适合让同一段代码处理多种类型。

语法模板

1
2
3
type 接口名 interface {
	方法名(参数列表) 返回值列表
}

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import "fmt"

type Speaker interface { // 只要有 Speak() string 方法,就满足 Speaker 接口
	Speak() string
}

type Dog struct {
	Name string
}

func (dog Dog) Speak() string { // Dog 实现 Speaker 接口
	return dog.Name + ": wang"
}

type Cat struct {
	Name string
}

func (cat Cat) Speak() string { // Cat 也实现 Speaker 接口
	return cat.Name + ": miao"
}

func say(speaker Speaker) { // 接收任何满足 Speaker 接口的值
	fmt.Println(speaker.Speak())
}

func main() {
	say(Dog{Name: "dog"}) // Dog{Name: "dog"} 是创建结构体值,不是函数调用
	say(Cat{Name: "cat"})
}

输出结果:

1
2
dog: wang
cat: miao

怎么理解这个示例

  1. say(speaker Speaker) 接收的不是固定结构体,而是任何满足 Speaker 接口的值。
  2. Dog{Name: "dog"} 不是 Dog() 函数调用,而是创建一个 Dog 结构体值。
  3. 下面这段展开写法和 say(Dog{Name: "dog"}) 的理解方式是等价的:
1
2
3
4
dog := Dog{Name: "dog"} // 创建一个 Dog 类型的值
var speaker Speaker = dog // 放进接口变量,因为 Dog 实现了 Speaker

fmt.Println(speaker.Speak()) // 实际调用 Dog 的 Speak 方法
  1. speaker.Speak() 调用的是接口里实际保存值对应的方法。保存 Dog 就调用 Dog.Speak(),保存 Cat 就调用 Cat.Speak()

什么时候使用接口

多个类型要完成同一行为时,就可以考虑接口。

  1. 有多个不同类型,要做同一件事。
  2. 你希望调用方只关心“能不能做”,不关心“具体是什么类型”。
  3. 以后可能还会新增更多同类实现。

场景:发送通知

假设程序现在支持邮件通知和短信通知。它们的共同点都是“发送通知”,只是实现方式不同。

不使用接口时

不用接口时,通常会为每种类型单独写函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
type Email struct{}

func (email Email) Send(message string) {
	fmt.Println("邮件通知:", message)
}

type SMS struct{}

func (sms SMS) Send(message string) {
	fmt.Println("短信通知:", message)
}

func sendEmail(email Email, message string) { // 只能处理 Email
	email.Send(message)
}

func sendSMS(sms SMS, message string) { // 只能处理 SMS
	sms.Send(message)
}

调用时要分别写:

1
2
sendEmail(Email{}, "订单已支付")
sendSMS(SMS{}, "验证码是 1234")

问题在于:

  1. 每增加一种通知方式,往往就要多写一个函数。
  2. 调用代码不统一,函数名会越来越多。
  3. 以后新增微信通知,还得继续补 sendWeChat

使用接口时

把“发送通知”抽成接口后,可以统一处理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
type Notifier interface {
	Send(message string)
}

type Email struct{}

func (email Email) Send(message string) {
	fmt.Println("邮件通知:", message)
}

type SMS struct{}

func (sms SMS) Send(message string) {
	fmt.Println("短信通知:", message)
}

func notify(notifier Notifier, message string) { // 统一接收所有满足 Notifier 的类型
	notifier.Send(message)
}

调用时统一写成:

1
2
notify(Email{}, "订单已支付")
notify(SMS{}, "验证码是 1234")

为什么更方便

  1. 调用方式统一,只需要 notify(...)
  2. 新增实现时,通常不用改原来的 notify 函数。
  3. 调用方只关心“能不能发送通知”,不关心具体类型。

如果以后新增微信通知,只要实现同样的方法即可:

1
2
3
4
5
6
7
type WeChat struct{}

func (wechat WeChat) Send(message string) {
	fmt.Println("微信通知:", message)
}

notify(WeChat{}, "你有一条新消息") // 新类型只要实现同样方法,也能直接复用 notify

什么时候适合用接口

适合:

  1. 多个类型有相同行为。
  2. 希望同一个函数处理多种类型。
  3. 代码以后可能扩展新的实现。

暂时不一定要用:

  1. 只有一个具体类型。
  2. 业务非常简单,不存在“多种实现”。
  3. 为了用接口而用接口,反而会让代码更绕。

常见错误

  1. 以为 Go 需要显式声明实现接口,实际只看方法集合是否匹配。
  2. Dog{Name: "dog"} 误认为函数调用,实际它是创建 Dog 结构体值。
  3. 方法名、参数或返回值有一点不同,就不能满足接口。
  4. 把接口当成“任意类型容器”使用,忽略接口本质是行为约束。
本文禁止转载
使用 Hugo 构建
主题 StackJimmy 设计 由 Hobin 魔改
最近构建时间:2026-04-17 19:07:48 CST
载入天数...载入时分秒...
发表了 1 篇文章 · 发表了 152 篇笔记 · 总计 18 万 0 千字