Go 从 0 到精通 · 第 14 课:接口 interface 入门
Go 从 0 到精通 · 第 14 课:接口 interface 入门
学习定位:这是整套 Go 教程的第 14 课,也是阶段三(核心抽象阶段)的第一课。
前置要求:已经完成第 13 课,掌握了方法的定义、值接收者与指针接收者的区别、方法的本质。
本课目标:理解 Go 的抽象能力来自接口而非继承,掌握接口的定义与实现方式,理解"鸭子类型"思想,能写出简单的接口并理解其设计意义。
1. 本课你要解决的核心问题
到目前为止,你写的代码都是"具体类型"——Student、BankAccount、Counter。
但现实中经常有这种需求:
- 我有一个函数,它需要"能打印自身信息的东西"——不管是
Student还是BankAccount,只要能打印就行 - 我想写一个通用的排序逻辑,不管是按成绩排序还是按年龄排序,只要能比较就行
在 Java 或 C++ 里,你会用继承和抽象类来解决。Go 没有继承——它用接口来表达抽象。
你需要搞明白以下问题:
- 接口是什么
- Go 的接口和其他语言有什么不同
- 什么是"鸭子类型"
- 怎么定义接口、怎么实现接口
- 空接口
interface{}有什么用 - 接口在实际开发中的价值
学完这一课,你就能理解 Go 的抽象能力是怎么来的了。
2. 接口是什么
2.1 接口是一组方法的集合
接口定义的不是"数据长什么样",而是"能做什么操作":
1 | type Speaker interface { |
这行代码说:任何类型,只要有一个 Speak() string 方法,就满足 Speaker 接口。
2.2 一个简单的例子
1 | package main |
Dog 和 Cat 都没有显式声明"我实现了 Speaker 接口"——它们只是恰好有一个 Speak() string 方法。Go 编译器自动判断:你有这个方法,那你就是这个接口。
这就是鸭子类型(duck typing)。
3. 鸭子类型
3.1 什么是鸭子类型
如果它走起路来像鸭子,叫起来像鸭子,那它就是鸭子。
在 Go 中,一个类型是否满足某个接口,不取决于它声明了什么,而取决于它有没有接口要求的方法。
1 | type Speaker interface { |
3.2 和其他语言的对比
| 语言 | 实现接口的方式 |
|---|---|
| Java | class Dog implements Speaker(显式声明) |
| C++ | class Dog : public Speaker(显式继承) |
| Go | 只要有方法就行(隐式满足) |
Go 的好处:
- 接口和实现解耦——定义接口的人不需要知道谁会实现它
- 减少了"为了实现接口而写的样板代码"
- 可以给已有的类型(甚至其他包的类型)添加接口支持
4. 接口的定义与实现
4.1 定义接口
1 | type 接口名 interface { |
接口里只有方法签名,没有字段:
1 | type Writer interface { |
4.2 实现接口
不需要 implements 关键字。只要类型的方法集包含了接口的所有方法,就自动满足:
1 | type File struct { |
4.3 一个类型可以实现多个接口
1 | type Writer interface { |
5. 接口作为函数参数
接口最大的价值是让函数接受"任何满足条件的类型":
1 | package main |
PrintInfo 不关心传入的具体类型,只关心"你有没有 String() string 方法"。
6. 空接口 interface{}
6.1 什么是空接口
空接口不包含任何方法:
1 | type Any interface{} |
因为没有任何方法要求,所有类型都满足空接口:
1 | func printAnything(v interface{}) { |
6.2 空接口的用途
空接口常用于需要"接受任意类型"的场景:
fmt.Println的参数就是...interface{}- JSON 解码时目标类型可能是
interface{} - 通用的数据容器
6.3 从空接口取回具体类型(类型断言)
1 | func main() { |
v.(Type) 就是类型断言——尝试把 interface{} 转成具体类型。用"逗号 ok"模式避免 panic。
6.4 用 switch 做类型判断
1 | func classify(v interface{}) { |
v.(type) 是专门用在 switch 中的类型判断语法。
7. 接口变量的底层
7.1 接口变量包含两个信息
一个接口变量内部存储了两样东西:
- 具体类型:实际赋给它的类型
- 具体值:实际的值
1 | var s Speaker = Dog{Name: "旺财"} |
7.2 nil 接口变量
1 | var s Speaker // s 是 nil 接口(类型和值都是 nil) |
这是一个常见的坑:把 nil 的具体类型赋给接口后,接口变量不等于 nil。
8. 一段综合示例
1 | package main |
输出:
1 | === 形状信息 === |
这个示例展示了接口的核心价值:用统一的方式操作不同的具体类型。
9. 常见坑总结
9.1 以为需要显式声明实现接口
1 | // 不需要这样写: |
Go 中只要方法签名对得上,自动满足接口。
9.2 nil 接口和 nil 具体值
1 | var d *Dog |
9.3 在接口中定义太多方法
1 | // 不好的设计 |
接口越小越好。Go 标准库中最常用的接口通常只有 1~3 个方法:
io.Reader:1 个方法io.Writer:1 个方法fmt.Stringer:1 个方法
接口越大,抽象越弱。——Rob Pike
9.4 过早定义接口
不要在写代码第一天就定义一堆接口。先写具体类型,等到确实需要抽象时再提取接口。
Go 社区的名言:
Accept interfaces, return structs.(接收接口,返回结构体。)
9.5 接口变量比较
如果接口的底层类型可比较,接口可以用 == 比较:
1 | var a Speaker = Dog{Name: "旺财"} |
但如果底层类型不可比较(如切片),会 panic。
10. 本课练习
练习 1:定义接口
要求:
- 定义
Describer接口,包含Describe() string方法 - 创建
Book、Movie两个结构体,都实现Describer - 写一个函数接收
Describer,打印描述信息
练习 2:空接口与类型断言
要求:
- 写一个函数
describe(v interface{}),对int、string、float64、bool分别输出不同信息 - 用
switch v.(type)实现
练习 3:鸭子类型验证
要求:
- 定义
Singer接口(Sing() string) - 定义
Dancer接口(Dance() string) - 创建
Person类型,同时实现两个接口 - 验证同一个
Person可以赋给Singer和Dancer两种接口变量
练习 4:接口切片
要求:
- 定义
Shape接口(Area() float64) - 实现
Circle、Rectangle、Triangle三种形状 - 创建
[]Shape切片,遍历计算总面积
练习 5:小接口设计
要求:
- 设计一个
Validator接口,只有一个Validate() error方法 - 为
Email、Phone结构体实现该接口 - 写一个通用函数,接收
Validator并打印验证结果
11. 自测题
11.1 概念题
- 接口定义的是什么?
- Go 中一个类型怎么"实现"接口?
- 什么是鸭子类型?
- 空接口
interface{}表示什么? - 类型断言的语法是什么?怎么安全地做类型断言?
- 接口变量的
nil和具体类型的nil有什么区别? - Go 社区对接口设计有什么常见建议?
- 一个类型可以同时满足多个接口吗?
- 接口中能定义字段吗?
switch v.(type)语法是做什么的?
12. 本课总结
这一课你学到了 Go 的核心抽象能力——接口。
你现在应该已经理解:
- 接口是一组方法的集合,定义"能做什么"
- Go 用鸭子类型:有方法就满足接口,不需要显式声明
- 接口让函数接受多种具体类型,实现通用逻辑
- 空接口
interface{}可以接收任何类型 - 用类型断言
v.(Type)从接口取回具体类型 - 接口越小越好,不要过早定义接口
nil具体值赋给接口后,接口不等于nil
13. 下一课预告
下一课我们学习 Go 的核心设计哲学:组合优于继承。
会重点讲:
- 为什么 Go 没有继承
- 结构体组合(嵌入)怎么替代继承
- 接口 + 组合的配合使用
- 和传统 OOP 继承的对比
学完下一课,你就能避免用旧语言的思维硬套 Go,真正理解 Go 的设计哲学。





