📄 一句话概括
同一个比喻在不同人脑子里长得不一样——car.drive() 是车在跑,driver.drive() 是人在开;两个 submit 也可能完全不在一个层面。
📝 本节和前面几节的关系
2.4 讲继承,这一节回头处理比喻带来的副作用——它比结构复杂更难察觉。
一、司机开车这个比喻
讲【对象与消息】最常用【司机开车】:司机发消息,车执行。
但同一个 drive,两边的语义完全不同:车那边是【运动】,人那边是【操作】。
实测两个类都有 drive,一个返回【车在跑】,一个返回【人在开】——名字一样,意思两样。
二、同一个词,两种层面
在真实代码里,这种分歧更常见。拿【订单】看:
Order.submit 是改自己的状态;Payment.submit 是和外部系统打交道。
两个都叫 submit,一个在内部,一个在边界上。
比喻把这两件事说成同一件,代码里却是两回事。
三、比喻掩盖了什么
掩盖了边界。哪些操作只改自己、哪些要碰外部,比喻不说。 掩盖了顺序。谁先谁后、失败了怎么办,比喻也不说。 掩盖了代价。改自己一个字段和调一次网络,差着好几个数量级。 这三样恰恰是写代码时最要紧的。
四、怎么用才不出事
进门之前用比喻,进门之后看代码。
具体一条:同一个方法名出现在两个类上时,停下来问一句——它们做的事在同一个层面吗?
不同层面就改名:markPaid 和 callGateway,比两个 submit 清楚得多。
命名的准确度,直接等于代码的可维护性。
五、动手 Demo:使用比喻进行讲解容易造成混乱
两个类都写 drive,一个表示车在动,一个表示人在操作。
再看【订单】上同一个动词能指几件不同的事。
class Car:
def drive(self): return "车在跑"
class Driver:
def drive(self): return "人在开"
print("car.drive() ", Car().drive())
print("driver.drive()", Driver().drive())
实跑输出:
--------------------------------------------------------------------
使用比喻进行讲解容易造成混乱
--------------------------------------------------------------------
同一个比喻,在不同人脑子里长得不一样。**最典型的是【司机开车】。**
car.drive() → 车在跑
driver.drive() → 人在开
**两句都叫 `drive`,一个说的是【车运动】,一个说的是【人操作】。**
这不是笔误,是真实代码里常见的分歧。
再试一个词——【订单】上的方法,看同一个动词能指几件不同的事:
类 方法名 实际做的事
--------------------------------------------
Order submit 把状态从待付改成已付
Order cancel 把状态改成已取消
Order total 把每行小计加起来
Payment submit 把请求发给支付网关
**注意第 1 行和第 4 行**:两个 `submit`,做的事根本不在一个层面。
一个【改自己的状态】,一个【和外部系统打交道】。
**比喻说成是同一件事,代码里却是两回事。**
所以 2.5 的结论是:**比喻帮你进门,进门之后要按代码看。**
实测:一个返回【车在跑】,一个返回【人在开】。 同名不同义——比喻把这两件事说成一件,代码里却是两回事。
30 秒实验
- 翻一遍你的代码,找出同名的两个方法,看它们做的事在不在同一层面。
- 把不在一层的同名方法改成两个准确的名字。
常见误区
- 以为同名方法就该做同样的事。同一个词在不同层面很常见。
- 以为比喻讲通了就是懂了。比喻只负责把你送到门口。
自测三问(点开对答案)
1. 司机开车这个比喻歪在哪? —— 同一个 drive,在车那边指运动,在人那边指操作——名字一样,语义两样。 2. 比喻掩盖了哪三样东西? —— 边界(哪些操作碰外部)、顺序(谁先谁后、失败怎么办)、代价(改字段与调网络差好几个数量级)。 3. 怎么用比喻才不出事? —— 进门之前用,进门之后看代码;同名方法出现时先问它们是不是在同一层面,不同层就改名。- ☐ 下一节(2.6 面向对象和现实世界是似是而非的)
相关笔记
- 面向对象和现实世界是似是而非的 — 比喻最严重的后果
- 原因之二:滥用比喻引起混乱 — 上一章的同一条原因
- ⌨️ 编程原理 · 索引 — 命名与抽象
- 🧩 面向对象是怎样工作的 · 索引