🧩 第二章 似是而非:面向对象与现实世界
📄 一句话概括

同一个比喻在不同人脑子里长得不一样——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. 翻一遍你的代码,找出同名的两个方法,看它们做的事在不在同一层面。
  2. 把不在一层的同名方法改成两个准确的名字。

常见误区

  1. 以为同名方法就该做同样的事。同一个词在不同层面很常见。
  2. 以为比喻讲通了就是懂了。比喻只负责把你送到门口。
自测三问(点开对答案) 1. 司机开车这个比喻歪在哪? —— 同一个 drive,在车那边指运动,在人那边指操作——名字一样,语义两样。 2. 比喻掩盖了哪三样东西? —— 边界(哪些操作碰外部)、顺序(谁先谁后、失败怎么办)、代价(改字段与调网络差好几个数量级)。 3. 怎么用比喻才不出事? —— 进门之前用,进门之后看代码;同名方法出现时先问它们是不是在同一层面,不同层就改名。

相关笔记