接口与多态:只定规矩不管实现,以及「鸭子类型」的落地
接口与多态:只定规矩不管实现,以及「鸭子类型」的落地
这一章讲 Go 的抽象机制:接口。 一句话版本:接口只规定「必须会干什么」, 不规定「你是谁、怎么干」。 谁的方法集对得上,谁就自动满足接口—— 没有 implements 关键字,没有显式声明。读完你会走通接口的完整生命周期: 声明 → 隐式实现 → 多态调用 → 把值取回来(类型断言与 type switch)。
1. 接口是什么
书里给的定义:接口是一组方法声明的集合——只有方法的名字、参数、返回类型, 没有方法体(实现)。 具体怎么干,由实现它的类型自己定1。
声明语法和结构体平行:
type AirthOperation interface {
Add() float32
Subtract(float32, float32) float32
Multiply(int, int) int
}接口变量也能声明:var i AirthOperation——此刻它是 nil。
书里在这里立了一对术语:只声明、还没装具体值的接口变量叫静态值;
装进一个具体类型的值之后,那个值叫动态值(或具体值)2。
接口变量的肚子里其实装着两样东西:值的类型 + 值本身;
nil 状态是「类型和值都是空」。
2. 主线例子:接口的完整生命周期
照原书的六步走通3。
第 ① 步:定接口(上面的 AirthOperation,三个方法)。
第 ②、③ 步:造两个实现者。 Operandset1(两个 float32)、Operandset2(两个 int):
type Operandset1 struct{ op1, op2 float32 }
type Operandset2 struct{ op1, op2 int }第 ④ 步:写方法——实现自动发生。 给两个结构体各写 Add:
func (o Operandset1) Add() float32 { return o.op1 + o.op2 }
func (o Operandset2) Add() float32 { return float32(o.op1 + o.op2) }书里把步骤 ④ 的要求说得清楚:方法的名字、参数、返回类型必须和接口声明 一字不差,接收者是哪个类型,就等于哪个类型在实现接口4。
关键点(书里没单独强调,但必须点破):Go 没有任何 implements 声明。
不像 Java 要写 class X implements Y——方法集对上了,实现关系就成立。
这在软件圈有个形象的名字叫「鸭子类型」:像鸭子一样走、游、叫,就是鸭子。
(补充,不在书里:这个术语出自 Python 社区的行话。)
第 ⑤、⑥ 步:装进接口变量、按接口调用——多态登场。
var i AirthOperation
i = Operandset1{5.78, 6.8}
fmt.Println(i.Add()) // → 12.58
i = Operandset2{5, 6}
fmt.Println(i.Add()) // → 11图说:同一个接口变量、同一句 i.Add(),装谁就像谁。
书里给多态下的定义:接口变量的行为随装进去的具体类型而改变,这就是多态5。
完整版例子里还有一个耐人寻味的输出:Operandset1{3.6, 6.9} 做减法得
-3.3000002——float32 精度不够,差值带着尾巴。这是浮点数的常态,
不是 Go 的 bug(第 3 章的浮点类型在这里接上了)。
3. 一个类型可以同时实现多个接口
书里给的例子:Operandset1 既有 Add()(满足 AirthOperation)又有
Compare(float32, float32) bool(满足 CompareOperation)——
同一个值可以分别赋给两个接口变量,各用各的能力6:
var a AirthOperation = o
var c CompareOperation = o
a.Add(); c.Compare(...)反过来也对:一个接口可以被无数类型实现。这两个方向的自由度, 是接口比继承灵活的根本:继承里一个类只能有一个爹, 接口实现里没有「爹」这个概念。
4. 空接口:能装任何值的盒子
一个方法都没有的接口叫空接口,写作 interface{}(或大题也可以写 any)。
因为每个类型都「实现了零个方法」,所以任何值都能塞进空接口7。
书里给的例子:一个接收空接口的函数,能打印任何类型的值7:
func show_value(v interface{}) { fmt.Println(v) }
show_value(string_struct{"Hello world"}) // → {Hello world}
show_value(float_struct{77.88}) // → {77.88}fmt.Println 能打印任何东西,秘密就在它的参数是空接口7。
代价:装进空接口之后,你失去了具体类型——只能打印、传走,
不能直接点它的字段和方法。想干具体的事,得先「取回来」,这就是下一节的类型断言。
5. 把值取回来:类型断言与 type switch
类型断言(type assertion):检查接口变量里装的到底是不是某个具体类型, 是就把值取出来。
t, ok := i.(Operandset1) // 问:i 里装的是 Operandset1 吗?
// 是 → ok = true,t 就是那个值 {3.6 6.9}
// 否 → ok = false,t 是该类型的零值两个返回值的形式叫「ok 模式」:第二个值告诉你断言成不成立, 程序永远不崩。书里也演示了失败路径:接口里装的是 Operandset1, 硬断言成 Operandset2,编译器直接报「不可能的断言」 (impossible type assertion)——类型断言只能在你「可能装过」的类型里选8。
type switch:一串类型断言的整齐写法,按类型而不是按值分支。
func check(i interface{}) {
switch v := i.(type) {
case Operandset1:
fmt.Println("类型是 Operandset1,值是", v)
case Operandset2:
fmt.Println("类型是 Operandset2,值是", v)
default:
fmt.Println("默认分支,值是", v)
}
}
check(Operandset1{3.7, 6.7}) // → 类型是 Operandset1
check(Operandset2{37, 67}) // → 类型是 Operandset2
check(Operandset3{"abc", "def"}) // → 默认分支i.(type) 只能出现在 switch 里;每个 case 里 v 已经是该 case 类型的具体值,
不用再做断言9。type switch 是处理「一个函数收任意类型」的标准姿势——
JSON 解码、错误分类、命令分发,底层都是它。
6. 接口嵌入:接口也能组合
接口可以嵌入别的接口:大接口 = 小接口的并集。
type Operation interface {
AirthOperation // 嵌入:把 Add 等方法整体收进来
Compare() bool // 再加一个自己的
}满足 Operation 的类型必须同时满足 AirthOperation 和 Compare—— 小接口拼成大接口,和结构体嵌入同一套思路10。 书里甚至说,「这有点像继承——从另一个接口那里获得方法签名」; 注意措辞:获得的是签名(规矩),不是实现(干法)——实现永远在具体类型手里。
7. 作者的判断与证据
| 书里的说法 | 性质 |
|---|---|
| 「Go 的多态通过接口实现」 | 语言设计事实 |
| 「接口嵌入以某种方式像继承」 | 类比,容易误导:继承传递实现,接口嵌入只合并签名 |
| 「空接口能装任何值」 | 机制事实;代价(丢类型)书里点了但没展开 |
8. 边界与局限
- 接口变量的零值陷阱没讲:一个 nil 接口调方法会 panic—— 「接口是 nil」和「接口里装着 nil 指针」是两个不同的崩溃场景(补充,不在书里);
- **隐式实现的「意外实现」**风险没讲:给类型加一个方法, 它可能突然满足了某个接口(或不再是某个接口)——这是鸭子类型的账单;
- io.Reader/io.Writer 这些标准库地标接口没有提, 而它们是 Go 接口哲学的最佳教材(补充,不在书里);
- 书里 AirthOperation 拼写(疑应为 ArithmeticOperation)照原书引用,特此说明。
9. 可带走的
- 接口 = 一组方法签名,无方法体;只定规矩,不管实现;
- 实现是隐式的:方法集对上就成立,没有 implements 关键字(鸭子类型);
- 接口变量的两态:声明未装 = nil(静态);装进具体值 = 动态值;
- 多态 = 同一个接口变量装谁就像谁;一个类型可实现多个接口,一个接口可被无数类型实现;
- 空接口
interface{}装任何值,代价是丢失具体类型; - 类型断言
t, ok := i.(T):ok 模式永不崩;「不可能的断言」编译期就拦; - type switch 按类型分支,是处理「任意类型入参」的标准姿势;
- 接口可以嵌入:大接口 = 小接口签名之和;嵌入只合并规矩,不传实现;
- 方法签名必须和接口声明逐字一致(名字、参数、返回值);
- 标准库里 io.Reader 这类小接口,是「接口要小而专注」的最好示范。
10. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 接口定义、多态定义 | Interfaces and Polymorphism | text/21-fm-learning-go-programming.txt:61(搜「collection of multiple method names」) · text/21-fm-learning-go-programming.txt:64(搜「achieved by interfaces」) |
| 声明接口与 nil 静态值 | Interfaces and Polymorphism | text/21-fm-learning-go-programming.txt:88(搜「AirthOperation interface」) · text/21-fm-learning-go-programming.txt:136(搜「<nil>」) · text/21-fm-learning-go-programming.txt:142(搜「dynamic value」) |
| 实现的六个步骤 | Interfaces and Polymorphism | text/21-fm-learning-go-programming.txt:214(搜「operandset1_var Operandset1」) · text/21-fm-learning-go-programming.txt:259(搜「same receiver type」) |
| 完整实现的输出(含浮点尾数) | Interfaces and Polymorphism | text/21-fm-learning-go-programming.txt:394(搜「10.5」) · text/21-fm-learning-go-programming.txt:400(搜「-3.3000002」) |
| 空接口 | Interfaces and Polymorphism | text/21-fm-learning-go-programming.txt:412(搜「empty interface」) · text/21-fm-learning-go-programming.txt:466(搜「{Hello world}」) |
| 多接口实现 | Interfaces and Polymorphism | text/21-fm-learning-go-programming.txt:478(搜「multiple interfaces」) · text/21-fm-learning-go-programming.txt:550(搜「AirthOperation_var」) |
| 类型断言与 ok 模式 | Interfaces and Polymorphism | text/21-fm-learning-go-programming.txt:565(搜「concrete value」) · text/21-fm-learning-go-programming.txt:640(搜「{3.6 6.9} true」) · text/21-fm-learning-go-programming.txt:658(搜「impossible type assertion」) |
| type switch | Interfaces and Polymorphism | text/21-fm-learning-go-programming.txt:664(搜「compares the type」) · text/21-fm-learning-go-programming.txt:802(搜「Default case」) |
| 接口嵌入 | Interfaces and Polymorphism | text/21-fm-learning-go-programming.txt:808(搜「embedding of interfaces」) · text/21-fm-learning-go-programming.txt:913(搜「{5.78 6.8} 12.58 false」) |
| 接口作为多态 | Interfaces and Polymorphism | text/21-fm-learning-go-programming.txt:925(搜「behavior of interface changes」) · text/21-fm-learning-go-programming.txt:997(搜「12.58」) · text/21-fm-learning-go-programming.txt:1000(搜「11」) |
Footnotes
-
出处:「Interfaces and Polymorphism」第 61 段(
text/21-fm-learning-go-programming.txt:61,搜「collection of multiple method names」)。 ↩ -
出处:「Interfaces and Polymorphism」第 136、142 段(
text/21-fm-learning-go-programming.txt:136,搜「<nil>」;text/21-fm-learning-go-programming.txt:142,搜「dynamic value」)。 ↩ -
出处:「Interfaces and Polymorphism」第 154–265 段,六步流程见第 250–265 段的分解。 ↩
-
出处:「Interfaces and Polymorphism」第 259 段(
text/21-fm-learning-go-programming.txt:259,搜「same receiver type」)。 ↩ -
出处:「Interfaces and Polymorphism」第 922–1002 段(
text/21-fm-learning-go-programming.txt:925,搜「behavior of interface changes」;text/21-fm-learning-go-programming.txt:997,搜「12.58」)。 ↩ -
出处:「Interfaces and Polymorphism」第 478–559 段(
text/21-fm-learning-go-programming.txt:550,搜「AirthOperation_var」)。 ↩ -
出处:「Interfaces and Polymorphism」第 409–472 段(
text/21-fm-learning-go-programming.txt:412,搜「empty interface」;text/21-fm-learning-go-programming.txt:466,搜「{Hello world}」)。 ↩ ↩2 ↩3 -
出处:「Interfaces and Polymorphism」第 562–659 段(
text/21-fm-learning-go-programming.txt:565,搜「concrete value」;text/21-fm-learning-go-programming.txt:640,搜「{3.6 6.9} true」;text/21-fm-learning-go-programming.txt:658,搜「impossible type assertion」)。 ↩ -
出处:「Interfaces and Polymorphism」第 661–802 段(
text/21-fm-learning-go-programming.txt:664,搜「compares the type」;text/21-fm-learning-go-programming.txt:802,搜「Default case」)。 ↩ -
出处:「Interfaces and Polymorphism」第 805–919 段(
text/21-fm-learning-go-programming.txt:808,搜「embedding of interfaces」;text/21-fm-learning-go-programming.txt:913,搜「{5.78 6.8} 12.58 false」)。 ↩