类(下)— 鸭子类型、继承的两张脸与组合¶
本章是原书第 7 章的下半,也是全书「立场」浓度最高的一章。 三个硬主张:成员变量像半个全局变量,所以类必须小; 实现继承别用,同样的功能用组合重写一遍也值; getter/setter 是反模式,别写。每一条作者都给了理由,每一条也都值得和自己的经验对质。
1. 这一章讲什么¶
上半章(第 11 章)解决了「类怎么用」;本章解决「类怎么设计才不烂」。 原书的结构是一条怀疑之路:先是类的隐忧(全局变量的幽灵),再给混排 polymorphism 的能力与代价, 然后正面处理继承——先告诉你它分成两种,再告诉你其中一种基本不该用。
类的隐忧(成员变量) → 鸭子类型与多态(Billionaire 例子)
→ 实现继承(危险) → 组合(替代方案) → 接口继承/抽象基类(无害的化妆品)
→ 封装收束(私有约定) → 抽象与适配器(该谁写适配代码)
图说:这是一条「能力 → 代价 → 纪律」的下坡路,越走越保守。
2. 核心原理一:成员变量是「半个全局变量」,所以类必须小¶
解决什么问题: 第 03 章宣判了全局变量的死刑;但类自己的成员变量,风险结构几乎一样—— 类体内任何地方都能读写它,类一大,「你永远说不准某时刻它的值是什么」,调试变成噩梦。1
怎么做(作者的解药只有一味):保持类小。 具体的三层论证:
- 类比层: 成员变量「在类的任何地方可用」——和全局变量「在代码的任何地方可用」是同一个结构,只是半径小了点;
- 诱惑层: 代码天然会长大,给类加方法太顺手了(所有私有变量唾手可得)——熵增的方向就是类膨胀的方向;
- 临界点层: 「你能理解一个类」的尺寸上限,比你想的低得多——50 行写得烂就已经是噩梦; 类一定要短于 100 行。2
书里那句 50 行的原话值得记住:「已经,一个 50 行长的类就可能写得烂到让人完全无法理解」—— 注意限定词是「写得烂」,不是「50 行」。类的可理解尺寸没有硬公式,但糟糕写法把上限拉得极低: 状态被十几个方法来回改、方法之间隐晦地依赖调用顺序——这种类,30 行就没人读得懂了。 反过来,职责单一、方法彼此正交的类,几百行也未必难读。100 行是作者的安全默认,不是物理定律。
判据给得很妙:「只要你能为它写出测试(第 15 章),这个类就还没复杂到失控—— 因为要写测试,你就必须真的理解它做什么。」 可测试性 = 可理解性的探针。2
还有一句预防针,针对「等看懂了再拆」:「等你到看不懂的那一步,你已经拆不动了——问题更大。 解法是在还看得懂的时候就保持它小。」2
2.1 成员变量和全局变量的差别,只剩一层¶
公平起见,两者的差别也要说清:全局变量的「半径」是整个程序——任何地方、任何函数; 成员变量的「半径」是一个类——但类里的每个方法都能碰它。所以书里那句类比精确地说应该是: 一个 20 个方法的类,它的成员变量就是 20 个函数共享的全局变量。 拆类之所以有效,就是把这条共享链剪短:每个方法只碰自己需要的几个状态,状态的变化就有了边界。
3. 核心原理二:鸭子类型与多态——混排的能力与代价¶
解决什么问题: 第 11 章收尾处,student 和 lawyer 是同一个类的实例,天然同列表。 如果两个对象不同类,但「支持同样的方法」,能不能也同列表、同循环?
怎么做(主线例子:Billionaire): 回顾第 08 章——消灭 if 的正规军是多态。 Python 的实现极其朴素:不检查类型,运行时只问「你有没有这个方法」。
class Person:
def withdraw(self, amount): ...
def read_balance_sheet(self): ...
def work(self, hours): ...
class Billionaire:
def withdraw(self, amount):
pass # 钱无限,取了白取
def read_balance_sheet(self):
return "Billions"
people = [student, lawyer, BillGates()]
for person in people:
person.withdraw(100) # 一视同仁,多态发生
在 C++ 里这不可能(两个不同类型的对象进同一个列表需要显式的共同基类,第 5 节的接口继承); Python 里只要方法名对得上就放行。这种「迟检查」遍布动态类型语言:类型不符不是「编译错误」, 而是「某次调用时的现场事故」——正是第 01 章那句「错误在运行时才发现」的类版本。3
代价,书里用一次真实的崩溃讲清:
程序不是启动时报错,而是跑到一半、在那个对象身上才炸。对比:C++ 的编译器会提前告诉你「这行不可能执行」; Python 让你先跑,中途翻车——「鸭子类型是把双刃剑。」4
补救的岔路口也照实讲了: 你可以给 Billionaire 补一个空的 work,报错消失——
但「一个实现了 work 却不能 work 的亿万富翁」本身又成了新的困惑;或者让 work 真的报错;或者保证永不这样调。
作者在这里停下不裁决:「软件里常常有很多选项,选哪个不总是那么清楚。你想达成什么,不总是那么轻易定下来的
——而搞清代码该干什么,往往比实现它还费劲。」(第 11 章需求工程的回声)5
4. 核心原理三:实现继承——被高估的复用,被低估的耦合¶
4.1 什么是实现继承,危险在哪¶
解决什么问题: 子类想白拿父类已经写好的方法。
怎么做(主线例子:自行车族谱):
class Bicycle:
def number_of_gears(self):
print(f"Has 12 gears")
class Mountainbike(Bicycle):
pass # 白拿父类的 number_of_gears
class Roadbike(Bicycle):
def number_of_gears(self): # 覆盖:重新定义
print(f"Has 2 times 11 gears")
Mountainbike() 的实例能直接调父类的 number_of_gears()——继承「节省了大量重复代码」,第一眼很美。6
危险在哪,书里说得透彻: 它强耦合代码——父类一变,子类全跟着变,有时反方向也是; 「到处都是继承的话,重构会变成一场噩梦;而且你永远搞不清某个函数到底实现在哪—— 你可能找到了一个定义,但它又在某处被覆盖了。」7
两种继承的区分也在这节给出,值得记牢:
| 实现继承 | 接口继承 | |
|---|---|---|
| 子类拿走 | 方法实现(代码)+ 接口 | 只有接口(方法名清单) |
| 耦合 | 强:共享代码,一改俱改 | 弱:没有共享代码可改坏 |
| 稳定性 | 实现常变 | 接口很少变——这是它更安全的原因 |
| 本书态度 | 默认别用 | 无害但非必需(化妆品) |
「接口比实现稳定」是一条可以带出本书的通则:任何系统里,公开承诺(接口)变化的频率, 总是远低于内部做法(实现)——所以依赖应该挂在承诺上,不挂在做法上。
4.2 组合:同样的功能,多写两行,买来自由¶
怎么做: 不复用父类的实现,而是把能力对象作为成员装进来(这就是组合,composition):
class OneByGear:
def get_number_of_gears(self):
print(f"Has 12 gears")
class Mountainbike:
def __init__(self):
self.gear = OneByGear() # 装一个「齿轮能力」对象
def get_number_of_gears(self):
self.gear.get_number_of_gears()
功能一样,代码略多,但每条调用都指名道姓——「功能定义在哪个对象上,你永远知道」。 作者承认组合唯一的缺点:「给装进来的对象起名字要费点脑子。」(命名,又是命名)8
4.3 那个流传甚广的教条是错的¶
书里点名了一条老规矩:「公路车是自行车,所以 Roadbike 该继承 Bicycle—— 这是个很老、很多课堂还在教的教条,它是错的。只有从实现角度看也说得通,才该这么写; 没有公共功能,就别继承。」(这正对应业界原则 LSP,里氏替换原则的精神:子类必须能替换父类而不出意外; 书里没提这个缩写,思想相同。)9
加框的总结,态度最硬的一段:
「默认不要用实现继承。它像全局变量——初看美妙(能复用代码!),用一阵子就尝到代价。 到处继承的代码你无法重构、找不到实现。组合多写一点点代码,但清楚得多。」10
5. 核心原理四:接口继承与抽象基类——无害的化妆品¶
解决什么问题: 实现继承危险,但「多个类遵守同一套接口」这件事本身有价值——怎么要接口不要实现?
怎么做: Python 用 ABC(abstract base class,抽象基类):
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod # 声明:子类必须实现这个方法,但本类不给实现
def area(self):
pass
class Rectangle(Shape):
def __init__(self, a, b):
self.a = a; self.b = b
def area(self):
return self.a * self.b
my_square = Rectangle(3, 3)
print(my_square.area()) # 9
@abstractmethod 是另一个装饰器:它把方法标成「抽象的」——子类不实现它,连实例化都会被拒:
class Circle(Shape):
def areaaa(self): # 方法名拼错!没实现 area
...
my_circle = Circle(2) # TypeError: Can't instantiate abstract class Circle with abstract method area
错误在创建对象时就炸,根本轮不到调用——「代码越早报错越好。」 对比实现继承:那里拼错方法名不报错,而是悄悄调用父类的版本——代码脆得无声无息。11
为什么有效、边界在哪: 接口继承不耦合(没有共享实现可以改坏),所以「问题小得多」。 但作者泼了冷水:「在 Python 里,ABC 纯属化妆品——多态不靠它也能跑(第 3 节的鸭子类型就是证据)。 什么时候真有用?规定别人必须实现一组方法时(比如布置作业:定义好接口,学生去实现),或者想公开一个正式接口。」12
6. 核心原理五:封装的收束与 getter/setter 反模式¶
回到 Car 例子收束封装:_speed 下划线私有,外部只该用 accelerate/decelerate/get_speed 三个方法——
「用户应该只用他被指望用的接口;偷看私有变量,就像掀开发动机盖。」13
Java 风的 getter/setter(一对只读/只写某成员的方法)在本书被判反模式,论证干净利落:
- data class 场景:字段本来就随便访问,getter/setter 是纯粹的仪式;
- 有行为的类:变量只在方法内部用,「既不需要直接访问,也不需要通过 getter/setter 访问」。
「长话短说:别写 getter 和 setter。」14
这个反模式值得多说一句,因为 Java 系的教材几乎把它们当礼节教。作者的拆法分两种情况:
字段没人外部访问时,getter/setter 是死代码;字段经常被外部读写时,
说明封装本身已经破了——该做的是「把围绕这个字段的行为收进类里」(第 11 章 Person 例子的 deposit/withdraw
正是「不写 setter」的正面教材:没有 set_amount_saved,只有存钱取钱两个有业务含义的方法)。
真正合理的例外只有一处:读取时带计算或校验(比如 get_full_name() 拼姓和名)——但那种方法本来就该有自己的名字,
不叫「getter」。
7. 核心原理六:抽象与适配器——谁该写翻译代码¶
解决什么问题: 代码的作者和使用者,想要的数据模型经常不一样(内部存得细,外部要得粗)。
怎么做: 中间要一层适配代码(adapter,把一种接口翻译成另一种的薄层)。谁写? 作者的答案「非常明确」:写代码的那一方(或那个团队)写。 理由是一条算术:「程序员通常只有一个(或一队),用户却有很多—— 少数程序员花一份力气,换多数用户各省一份麻烦,这买卖划算。」15
抽象(abstraction,隐藏复杂度、暴露统一接口)的宏观例子:用户付钱,不管 PayPal 还是信用卡——
网站不该管这个差异,只调 pay();至于电驱还是燃油,「车开起来一个样」。15
这条「一个程序员对多个用户」的算术,在开源世界里每天都上演:库作者多写一层适配, 成千上万的调用者就少一层烦恼。反过来也成立——如果你是用户,读到设计糟糕的接口时, 请记住它大概率是「作者省了自己两小时」的产物;第 02 章那句「读不懂是作者的错」,在这里有了经济层面的解释。
边界: 抽象也有成本——多一层接口,就多一层「找实现」的跳转。 所以作者的用法是「需要第二个实现时才引入抽象」(PayPal/信用卡的例子正是两个实现); 只有一个实现时硬造接口,是给未来的变化付利息,而未来未必来。抽象是为了「变」买的保险, 不变的部分不需要保险;而判断「哪里会变、哪里不会」的经验,正是读完这两章攒下的东西——它比任何一条具体规则都保值,也比任何具体规则都难从别处抄来——只有读和写能攒——读别人的烂接口长见识,写自己的烂接口长记性,两头都不可或缺,也都急不来——这大概就是为什么这类书只能给判断、给不出答案。
8. 作者的判断与证据¶
书里给了证据的: 继承/组合的对照代码、ABC 拼错方法名的当场报错,都可直接运行验证; Billionaire 崩溃的 Traceback 是真实的。411
作者的判断(本章的密度最高,逐条标):
- 「类 < 100 行」「可测试即可理解」——经验标尺,实用;
- 「别用实现继承」——强立场;与 Python 标准库(大量用继承)实践相反,但与小类 + 组合的当代主流(尤其 Go、Rust 的设计)同向;
- 「ABC 是化妆品」——在 Python 社区有争议(dataclass + Protocol 正在取代它的部分角色),但「非必需」属实;
- 「别写 getter/setter」——作者立场;Java 传统会反对。折中实践:需要校验/计算的字段访问才用方法,纯透传访问直接用属性。
判断(我们的,不是书里的): 本章四连击(小类、拒实现继承、ABC 化妆品、拒 getter/setter) 共同的底层是「显式优于隐式」:组合让依赖显式,小类让状态半径显式,拒 getter/setter 让访问路径显式。 这一套与 Python 之禅同调,也和现代系统设计(组合、接口)合流。代价是写起来啰嗦—— 「多写两行换清楚」是本章反复付的价,预算紧的项目会觉得贵。 如果错,会错在: 深度框架开发(如 ORM、GUI toolkit)里,实现继承是生态本身的语言, 拒用等于自绝于框架;那种场景该守的纪律是「继承深度 ≤ 2、只继承设计来被继承的类」, 而不是全盘拒绝——作者的教学语境是一般应用代码。
9. 设计决策的完整推演:三条岔路怎么走¶
本章给了许多「别用 X」的禁令,禁令背后的推演值得单独串一遍——因为真正带走的不是禁令,是推演的形状。
8.1 Billionaire 崩溃之后,三个选项的完整对比¶
BillGates.work() 抛出 AttributeError 之后,书里把三条路都走了给你看:5
选项一:补一个空方法。
报错消失。但书里立刻指出新的困惑:「一个实现了 work 却不能 work 的亿万富翁」——
接口对上了,语义是谎话。读者看到 work(10) 会以为真干了什么。
选项二:让它真报错。 work 内部抛异常——至少「报错」是诚实的,但调用处依然要处理, 和直接 AttributeError 的差别不大。
三个选项的对照,本质是「谎言、真话、绕行」的对照:空方法说谎(能 work 其实不能), 显式报错说真话(这里不能,但你要自己消化),类型检查绕行(回到 if 链)。 作者不替你选,因为正确答案取决于「这个系统的用户是谁」——内部工具选真话, 对外产品多半要守接口(谎话或绕行);设计决策的输入永远是使用场景,不是规则本身。
选项三:保证永不这样调。 调用处自己分辨类型——这等于放弃了多态, 回到第 08 章要消灭的 if 链。
作者的收尾不裁决:「软件里常常有很多选项,选哪个不总是那么清楚——取决于你到底想达成什么; 而你想达成什么,不总是那么轻易定下来的。」5 这句「无力感」恰恰是诚实的:设计决策的输入是需求,需求不明时,任何「最佳实践」都是假币。
8.2 自行车例子的两次完整运行¶
实现继承版,逐行看输出:
class Bicycle:
def number_of_gears(self):
print(f"Has 12 gears")
class Mountainbike(Bicycle):
pass # 什么都没写,白拿父类的
class Roadbike(Bicycle):
def number_of_gears(self): # 覆盖:重新定义
print(f"Has 2 times 11 gears")
my_mountainbike = Mountainbike()
my_mountainbike.number_of_gears() # Has 12 gears —— 父类的实现
my_roadbike = Roadbike()
my_roadbike.number_of_gears() # Has 2 times 11 gears —— 子类自己的
运行时发生了什么:Python 在对象上找 number_of_gears——Mountainbike 自己没有,顺着继承链向上,
在 Bicycle 里找到;Roadbike 自己有,直接用。「函数实现在哪」这个问题的答案,取决于一条隐藏的查找链——
这正是作者说「你永远搞不清实现在哪」的机制层面:链条短还好,链条一长(祖父类、曾祖父类……),
找实现就是考古。610
组合版,同样输出的另一种写法:
class OneByGear:
def get_number_of_gears(self):
print(f"Has 12 gears")
class TwoByGear:
def get_number_of_gears(self):
print(f"Has 2 times 11 gears")
class Mountainbike:
def __init__(self):
self.gear = OneByGear() # 把能力装进来
def get_number_of_gears(self):
self.gear.get_number_of_gears() # 点名转调
class Roadbike:
def __init__(self):
self.gear = TwoByGear()
def get_number_of_gears(self):
self.gear.get_number_of_gears()
输出一模一样,但查找链消失了:每个方法体里都写着 self.gear.——实现位置显式地写在脸上。
代价书里也承认:「继承版本里 Roadbike 是 Bicycle 这件事很『显然』;组合版本里你得给装进来的对象起个名字」
(gear)——命名的那点心力,就是买「无考古」的价钱。8
8.3 接口继承的完整防线¶
把第 5 节的 Shape 例子走到底,三层防线各看一次报错:11
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self): # 只声明,不实现
pass
class Rectangle(Shape):
def __init__(self, a, b):
self.a = a; self.b = b
def area(self):
return self.a * self.b
my_square = Rectangle(3, 3)
print(my_square.area()) # 9
防线一:忘了实现。 假设 Circle 写的时候把方法名拼错:
class Circle(Shape):
def areaaa(self): # 拼错:area 多了一个 a
...
my_circle = Circle(2)
# TypeError: Can't instantiate abstract class Circle with abstract method area
注意报错时机:创建对象那一刻就炸,根本轮不到调用 area。 对比实现继承的世界:拼错方法名不会报错,而是悄悄调用父类实现——或者更糟,父类也没有,AttributeError 迟到。 「代码越早报错越好」,ABC 是这句话的机制化。11
防线二:这就是接口继承「不耦合」的含义: Shape 里没有一行实现可以「被改坏」, 子类之间也没有共享代码可以「互相牵连」。接口是一张合同,不是一根电缆。
防线三:合同在 Python 里是自愿的。 ABC 给的多是「早报错」;Python 不检查类型, 你不继承 Shape 也能有 area() 方法——鸭子类型兜底。所以书里那句「ABC 是化妆品」的意思是: 它没有给语言增加能力,只给了形式——而形式的价值,在多人协作和教学场景里是真实的。12
书里给了两个「值得用」的场景,值得原样记住:
- 布置作业式的协作:「如果你有一个学生,必须实现一组类——定义好抽象基类, 学生就得照着实现每一个方法。」接口变成合同,漏实现当场报错;
- 公开接口的宣示:「想定义一个公开接口」时,ABC 让意图显式化—— 读代码的人一眼看到「这是接口,不是普通类」。
除此之外,duck typing(第 3 节)的世界不靠它运转。「需不需要」的判据一句话: 有多个作者、需要合同 enforce 时用它;自己写、自己读,duck typing 够用。
10. 类设计的总检查表¶
把第 11、12 两章的判断收成一张「写完一个类之后的自检表」(我们的归拢):
| # | 问题 | 不达标怎么办 |
|---|---|---|
| 1 | 这个类能写出测试吗?(第 12 章探针) | 写不出 = 还没懂它,先搞懂再说 |
| 2 | 它短于 100 行、只做一件事吗? | 按职责拆 |
| 3 | 成员变量都被下划线藏着、只被自己的方法碰吗? | 收编或改名 |
| 4 | 用户在用的接口有几个方法?多吗? | 能藏则藏 |
| 5 | 有继承吗?是「接口」还是「实现」? | 实现继承→改组合 |
| 6 | 有没有 getter/setter 仪式? | 删 |
| 7 | 换一个类放进来,循环还跑得动吗?(鸭子类型自检) | 跑不动 = 有隐性的类型假设,显式化或补接口 |
七条过完,类的「形状」基本就是作者会点头的那种:小、藏、显式、组合。
11. 边界与局限¶
- 没讲
super()、方法解析顺序(MRO)——多重继承在 Python 合法,本章完全不碰; - 组合例子的代码在书中有若干笔误(如
class Montainbike():后写self.gear = ...而类没有__init__), 我们按意图讲解,照书抄会报错; - 鸭子类型没讲
Protocol(静态鸭子类型,类型标注时代的答案)——2026 年的现代方案缺席; - 抽象一节没给适配器的代码例子,只给了原则。
12. 可带走的¶
本章主线例子,一行写完: Mountainbike(Bicycle) 能白拿 number_of_gears,
但父类一改全跟着变;改成「装一个 OneByGear 对象、方法里转调」,代码多两行,依赖全显式。68
- 成员变量 = 半个全局变量:类必须小(<100 行);可测试 = 还没失控的探针。
- 鸭子类型:方法名对得上就能混排;代价是 AttributeError 推迟到运行中途。
- 实现继承:别用(默认);「是-a 就继承」的教条是错的,看实现有无公共功能。
- 组合优于继承:多写两行,换来「功能在哪永远知道」。
- 接口继承(ABC)无害但非必需:最好用的场景是「规定别人必须实现什么」和拼错名早报错。
- 代码越早报错越好——这是 ABC 例子的真正教训。
- 别写 getter/setter;data class 直接用字段。
- 适配代码归作者方写:一个程序员省出 N 个用户的麻烦。
- 设计类的总口令:显式优于隐式,能藏则藏,保持 small。
一句话收束本章: 类的设计没有标准答案,但有标准问题——「这个类多大?藏了多少?依赖显式吗?」 三个问题代替了一大堆「该用继承还是组合」的纠结;答完它们,继承与组合的选择往往已经不言自明。
12.1 两章连读后的全景¶
把 11、12 两章合起来,作者给类画出的像是:
data class 装数据 ──→ 普通类 装数据+行为 ──→ 小类(<100 行、SRP)──→ 组合拼装
↑ ↑
「行为多了再升级」 「测试写得出来 = 还没失控」
图说:类的生命周期是一条「升级链」,不是一张平铺的菜单;
每一级升级都有明确的触发条件(行为多了、类太大了、依赖要替换了)。
「什么时候用什么」的答案不在任何单一章节里,而在升级链的触发条件里: 识别触发条件,比背「设计模式」清单可靠得多。
13. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 类的缺点之问(为什么不用别的) | 7 Classes | text/23-ch07-7-classes.txt:312(搜「own set of disadvantages」) |
| 成员变量=半个全局变量 | 7 Classes | text/23-ch07-7-classes.txt:316(搜「Global Variables and Member Variables」) |
| 类要小于 100 行、可测试探针 | 7 Classes | text/23-ch07-7-classes.txt:340(搜「certainly be shorter than 100 lines」) |
| 「看不懂就拆不动了」 | 7 Classes | text/23-ch07-7-classes.txt:338(搜「can’t break it down」) |
| Billionaire 混排 | 7 Classes | text/23-ch07-7-classes.txt:359(搜「duck typing」) · text/23-ch07-7-classes.txt:380(搜「people = [student」) |
| work() 崩溃与双刃定性 | 7 Classes | text/23-ch07-7-classes.txt:388(搜「double-edged sword」) |
| 补救岔路与「选哪个不清楚」 | 7 Classes | text/23-ch07-7-classes.txt:410(搜「many options」) |
| 继承定义与耦合警告 | 7 Classes | text/23-ch07-7-classes.txt:415(搜「Inheritance」) |
| 自行车实现继承例子 | 7 Classes | text/23-ch07-7-classes.txt:432(搜「Mountainbike」) |
| 「是-a 教条是错的」 | 7 Classes | text/23-ch07-7-classes.txt:495(搜「old dogma」) |
| 组合版本与「起名的代价」 | 7 Classes | text/23-ch07-7-classes.txt:450(搜「Prefer composition」) · text/23-ch07-7-classes.txt:481(搜「small price to pay」) |
| 「别用实现继承」加框总结 | 7 Classes | text/23-ch07-7-classes.txt:484(搜「shouldn’t use implementation inheritance」) |
| 接口继承与 ABC/abstractmethod | 7 Classes | text/23-ch07-7-classes.txt:500(搜「Interface Inheritance」) · text/23-ch07-7-classes.txt:512(搜「ABC」) |
| 拼错方法名提前报错 | 7 Classes | text/23-ch07-7-classes.txt:538(搜「areaaa」) · text/23-ch07-7-classes.txt:545(搜「as early as possible」) |
| 「ABC 是化妆品」 | 7 Classes | text/23-ch07-7-classes.txt:550(搜「cosmetic choice」) |
| Car 封装收束 | 7 Classes | text/23-ch07-7-classes.txt:558(搜「utmost importance」) · text/23-ch07-7-classes.txt:576(搜「_speed」) |
| getter/setter 反模式 | 7 Classes | text/23-ch07-7-classes.txt:599(搜「antipattern」) · text/23-ch07-7-classes.txt:605(搜「Don’t write getters」) |
| 抽象与「适配代码归程序员写」 | 7 Classes | text/23-ch07-7-classes.txt:607(搜「Abstraction」) · text/23-ch07-7-classes.txt:619(搜「very clear」) |
-
出处:「7 Classes」第 316–328 段(
text/23-ch07-7-classes.txt:326,搜「similar problem」)。 ↩ -
出处:「7 Classes」第 316–343 段(
text/23-ch07-7-classes.txt:328,搜「keep your classes small」;第 339 段,搜「shorter than 100 lines」;「可测试探针」见第 341–342 段,搜「write tests」)。 ↩↩↩ -
出处:「7 Classes」第 344–388 段(
text/23-ch07-7-classes.txt:359,搜「duck typing」;「在 C++ 不可能」见第 372–373 段,搜「In C++」)。 ↩ -
出处:「7 Classes」第 389–406 段(
text/23-ch07-7-classes.txt:388,搜「double-edged sword」;「在 C++,编译器会告诉你」见第 385 段,搜「compiler will tell you」)。 ↩↩ -
出处:「7 Classes」第 407–413 段(
text/23-ch07-7-classes.txt:410,搜「many options」)。 ↩↩↩ -
出处:「7 Classes」第 415–429 段(
text/23-ch07-7-classes.txt:418,搜「inherits the methods」;「saves a lot of repetitive code」见第 419 段)。 ↩↩↩ -
出处:「7 Classes」第 419–422 段(
text/23-ch07-7-classes.txt:420,搜「strongly couples the code」)与第 483–491 段(加框「Prefer Composition over Inheritance」,text/23-ch07-7-classes.txt:488,搜「never know where a function is actually」)。 ↩ -
出处:「7 Classes」第 450–481 段(
text/23-ch07-7-classes.txt:450,搜「With composition」;「起名的代价」见第 478 段,搜「little price」)。 ↩↩↩ -
出处:「7 Classes」第 494–498 段(
text/23-ch07-7-classes.txt:495,搜「old dogma」)。 ↩ -
出处:「7 Classes」第 483–491 段(加框,
text/23-ch07-7-classes.txt:484,搜「you shouldn’t use」)。 ↩↩ -
出处:「7 Classes」第 500–545 段(
text/23-ch07-7-classes.txt:512,搜「ABC」;拼错名例子见第 530 段,搜「areaaa」;提前报错见第 542 段,搜「as early as possible」)。 ↩↩↩↩ -
出处:「7 Classes」第 547–555 段(「When to Use Abstract Base Classes」框,
text/23-ch07-7-classes.txt:550,搜「cosmetic choice」)。 ↩↩ -
出处:「7 Classes」第 556–594 段(
text/23-ch07-7-classes.txt:576,搜「_speed」;「掀开发动机盖」见第 571 段,搜「peeking under the hood」)。 ↩ -
出处:「7 Classes」第 596–605 段(「Getters and Setters」框,
text/23-ch07-7-classes.txt:599,搜「antipattern」;结论见第 605 段,搜「Don’t write getters」)。 ↩ -
出处:「7 Classes」第 607–621 段(
text/23-ch07-7-classes.txt:619,搜「the programmer should write the adapter」;「一个程序员对多个用户」的算术见同段)。 ↩↩