跳转至

类(上)— data class、封装与特殊方法

这两章(11、12)拆原书的第 7 章——全书最长的一章。 上半(本章)回答「类是什么、怎么用」: data class 几行顶一个类、封装为什么是把复杂度关进笼子的唯一钥匙、特殊方法怎么让对象融入 Python 的语法。 下半(第 12 章)回答更难的问题:「类该怎么设计」——继承的危险、组合的替代、鸭子类型的双刃。

1. 这一章讲什么

动机从第 04 章就埋下了:列表只该装性质相同的对象;一个对象的不同属性(一个人的姓名、街道、城市) 硬塞进列表是灾难——「该定义一个类。」1

本章的两条腿:

  ① 类的基本功:data class → 普通类 → 特殊方法 → 银行账户主线例子
  ② 类的第一价值观:封装(encapsulation)——能藏的都藏起来
  图说:①教动作,②教品味;原书把②的论证嵌在①的例子里。

2. 核心原理一:data class——三行定义一个结构化对象

解决什么问题: 一个地址 = 姓名 + 街道 + 城市,三个值总被一起传来传去。

怎么做(主线例子的起点): 最省力的版本——data class(数据类,Python 标准库 dataclasses 模块提供, 专为「只存数据」设计的类):

from dataclasses import dataclass

@dataclass
class Address:
    name: str
    street: str
    city: str

逐件看:@dataclass 是装饰器(decorator,贴在类定义前、替它自动加功能的标记; 这里它自动生成 __init__ 等样板代码);类体里的三行是字段(field,这个对象持有的数据槽), : str 是类型提示——「告诉使用者这里该放字符串」,不强制。2

然后它立刻好用:

my_address = Address(name="Donald Duck",
                     street="1847 Bungalow Lane",
                     city="Duckburg")
print(my_address)           # Address(name='Donald Duck', street='1847 Bungalow Lane', city='Duckburg')
print(my_address.name)      # Donald Duck —— 用点号按名字取字段

为什么有效: 你不再需要「拎着三个变量到处走」——一个名字装三件事,心智负担当场减半; 自动获得的打印格式、相等比较都是送的。作者的汽车类比:「一辆车几百个零件你叫不全名字, 但『车』这个概念你一坐进去就会用」——类就是把零件封装成概念的工具。3

边界: data class 官方定位是「只存数据、不带方法」;Python 不拦你在里面写方法,但作者的口味是: 数据归 data class,行为归正经类——混着装,等于回到「列表混装类型」的老问题。

2.0 data class 白送了什么,拆开看

print(my_address) 的输出值得逐字符看:2

Address(name='Donald Duck', street='1847 Bungalow Lane', city='Duckburg')

这一行是 @dataclass 免费给的:类名 + 每个字段的名字和值。自己写普通类要达到同样效果, 得手写一个 __repr__——这就是装饰器 @dataclass 替你省下的样板代码之一(构造器 __init__ 同理, 不用写 self.name = name 三连)。相等比较(== 按字段比)也是送的。 「免费」的另一面是定制空间小:想改打印格式,还是得回到手写特殊方法的老路。 所以作者的用法是:data class 当「加强版记录」用,行为一多,就升级成普通类。

类型提示(: str)在这三行里也值得再看一眼:它写给读代码的人和 IDE 看—— 现代编辑器会拿它做自动补全和检查;解释器运行时照旧不拦你传错类型。 「文档化但不强制」是这个语法的设计初衷,和第 01 章 type hints 的口径完全一致。

2.1 类 vs 列表:完整对照代码

书里把裁判过程写成了完整代码,值得逐行看:4

class Address:
    def __init__(self, name, street, city):
        self.name = name
        self.street = street
        self.city = city

my_address = Address("Donald Duck", "1847 Bungalow Lane", "Duckburg")
my_other_address = ["Mickey Mouse", "123 Main Street", "Disneyland"]

两个对象装的东西完全一样(三个字段),差别全在使用代码上:

print(my_address.name)        # Donald Duck
print(my_other_address[0])    # Mickey Mouse

判据书里说得极直白:「把使用代码读几遍,你会注意到第一行比第二行可读得多, 因为你不知道 [0] 返回的是什么。这意味着第一行更好。句号。」4 [0] 的问题不是「不方便」,是信息丢失:位置本身没有含义,读者必须回到创建处数一遍。 名字永远不丢信息——这就是「类严格优于列表」的全部论证。

2.2 历史那一课的完整推理

书里几段历史的落点,比「类没有带来根本新概念」还多一点:

  • C(1972)就有 struct:「不同对象存在一起」这件事,五十多年前就解决了;
  • C++ 把函数也塞进类,封装才顺手——方法(类里的函数)可以访问类的所有其他方法和变量, 「类因此能把一段代码非常方便地捆在一起」;
  • 连 C 都能用「头文件里不写」来藏实现;C++ 发明 private 关键字, 只是因为编译器需要头文件里列出全部成员——不是为了程序员,是为了编译器;
  • 结论:「如果你不喜欢类,用 data class(或 C 的 struct)+ 函数,也能写完整程序。」5

最后这条是全书对「要不要用面向对象」这个问题最完整的回答: 类是便利设施,不是认知必需品——和继承无关,和封装有关。

3. 核心原理二:封装——能藏的都藏起来

解决什么问题: 代码的使用者(包括三个月后的你)不想、也不需要知道内部细节。

怎么做: 书里用足了这个比喻:你开车,但你不知道引擎、刹车、转向各是怎么工作的—— 「大多数正常人连机油都不自己换。他们只想从 A 开到 B,别的一概不想知道。」 代码同理:把实现细节藏起来,用户只看见几个函数(类的「接口」)。6

封装之所以要紧,书的论证分两层:

  1. 认知层面: 用户要记的东西越少,用错的可能越小(「就像你不知道 Python 默认用的是什么排序算法——不在乎,能用就行」);
  2. 变更层面: 内部随便改,只要接口不变,用户无感——这是代码能长期演化的前提。

Python 的封装全靠约定:没有 private 关键字(不像 Java/C++); 下划线开头的成员(_speed)按约定是「内部专用」,外人不是访问不到,而是访问了就自担风险—— 「就像掀开发动机的盖子动手,不推荐,后果自负。」7

这两层论证的分量不一样:认知层面管的是每天,变更层面管的是每年。 每天省下的心智是封装的日常收益;每年「内部重构不惊动用户」的能力,则是系统能活过三年的前提。 作者排序上把认知放前面,是因为这本书的读者大多还在写小项目——但两条都成立。

书里对私有下划线的态度还有一层:「你可以无视这个约定继续用,没人拦你—— 就像对车的引擎动手,不推荐,风险自担。」注意这个措辞:「自担风险」的风险是什么? 内部实现随时会变:今天 _amount_saved 是数字,明天作者可能改成带货币单位的类型, 直接读它的代码就静默出错。私有约定保护的不是「作者的私产」,是「你的代码不依赖会变的细节」。7

单一职责原则(SRP)与私有约定

封装在类级别有一个度量伙伴(第 07 章见过):SRP——任何对象只做一件事。 「一个职责」难精确定义,但信号清晰:类太长了,就大概率在干好几件事,该拆。8 私有下划线 + SRP + 小类,是作者给出的「类不变成大泥球」的三件套(第 12 章补全)。

4. 核心原理三:类与对象、特殊方法

4.1 类是图纸,对象是实例

类是「由变量和方法构成的一般结构」;对象是它的一个具体实现,想要多少造多少。 书里给的两个类比都很准:「一般每个类至少该有一个对象——没有对象的类, 就像设计了一辆车却永远不造一辆。」9

实例化(创建对象)那一刻发生了什么:anna = Person("Anna") 调用 __init__, self 指向刚出生的新对象,self.name = name 把 "Anna" 写进它自己的字段。 从此 anna 和 Person 图纸再无共享——改 anna 的字段,Person 一张图纸不动; 用 Person 再捏一个 elsa,两个对象互不相干。这就是「图纸与实例」关系的机制内容。

书里也列了两类没有对象的类,只为完整:抽象类(定义共同接口、不许实例化,第 12 章展开)和静态类—— 后者「我建议别用,写几个函数就行」,详见 §4.4。9

4.2 特殊方法:让对象接进 Python 的语法

解决什么问题: 你自定义的对象,也想支持 print(obj)、obj1 == obj2 这类内建语法。

怎么做: Python 给每个对象配了一批特殊方法(名字以双下划线开头结尾,俗称 dunder 方法), 语法糖背后调的就是它们。书里列了承重的那五个:10

特殊方法 什么时候被调用 用途
__init__ 创建对象时 构造器:把初始值装进 self;别在里面写复杂逻辑
__str__ 对象被转成字符串(print、f-string)时 返回给人读的字符串
__repr__ 类似场合,面向调试 返回无歧义的字符串(书未演示,记住分工即可)
__eq__ == 比较时 定义「两个对象相等」的含义
__dict__ 随时可查 对象的全部属性,以 dict 形式

走一遍一个实现了 __init__、__str__、__eq__ 的 Address:

class Address:
    def __init__(self, name, street, city):
        self.name = name           # self = 「这个对象自己」
        self.street = street
        self.city = city

    def __str__(self):
        return f"{self.name}, {self.street}, {self.city}"

    def __eq__(self, other):
        return self.name == other.name and \
               self.street == other.street and \
               self.city == other.city

print(my_address) 不再打印 <Address object at 0x...>,而是 "Donald Duck, 1847 Bungalow Lane, Duckburg"; == 按字段比较;my_address.__dict__ 直接给出 {'name': 'Donald Duck', ...}。10

为什么有效: self 把「这个对象自己的数据」显式写进每个方法,没有它方法不知道自己在操作谁; 特殊方法则是 Python 的「协议」思想——协议就是约定好的一组方法,对象支持了就算同类:不检查类型,只检查行为(鸭子类型的正面形态)。

作者的使用建议很克制: 「特殊方法一般不需要。我日常只用 __init__;其余都是好东西, 用上了能让代码更可读就用,否则别炫。」11

4.3 Address 例子的三次调用,逐个看输出

把上面那个 Address 类跑起来,三次调用各自触发一个特殊方法:10

my_address = Address("Donald Duck", "1847 Bungalow Lane", "Duckburg")
other_adress = Address("Mickey Mouse", "123 Main Street", "Disneyland")
# 创建 → 各自触发 __init__;字段按名字装进 self

print(my_address)
# Donald Duck, 1847 Bungalow Lane, Duckburg
# → print 需要字符串 → 触发 __str__,返回定制的格式

print(my_address == other_adress)
# False
# → == 触发 __eq__,按三个字段逐对比较;名字就不同,所以 False

print(my_address.__dict__)
# {'name': 'Donald Duck', 'street': '1847 Bungalow Lane', 'city': 'Duckburg'}
# → 对象的「全身体检表」:所有属性以 dict 形式摊开

一个没实现 __str__ 的对象,print 出来是 <Address object at 0x7f...> 这样的内存地址——对调试没帮助。 一行 __str__,对象就从「机器内部的东西」变成「能给人看的记录」。 __repr__ 的分工书里只点了一句:都管「转成字符串」,__str__ 求可读,__repr__ 求无歧义 (让开发者一眼认出这是哪个对象)。10

4.4 两类没有对象的类

书里补了两个「特殊类」,只为完整:9

抽象类(abstract class): 定义共同接口、不许实例化,只许被继承——「想给多个类定同一套接口、 但自己不实现时」用它。它真正的用处在第 12 章的接口继承展开。

静态类(static class): 只有静态方法(不用创建对象、类名直接调用的方法)的类。书里给了示例 (Elements.add_element('earth') 类名调用),然后立刻泼冷水:「我给这个例子只为完整。 一般我建议别用静态类——写几个函数就行。」9

这个「演示完就劝退」的写法在本章反复出现(特殊方法、静态类、继承……): 作者的风格是先给你看语言允许的一切,再告诉你他推荐哪一小撮。 读者要记住的从来不是清单,而是那几次「别用」。

6. 主线例子:Person 银行账户,从能跑到可靠

这是原书本章的重心,也是「需求比实现难」的最好教材。场景:一个人有存款和时薪,能存钱、取钱、打工、查余额。

6.0 写之前的三个设计决定

动手前,作者其实已经做了三个决定,值得显式列出:

  1. 状态叫什么、放哪: 存款叫 _amount_saved、时薪叫 _hourly_wage,都带下划线——内部状态,第 3 节刚立的规矩。
  2. 行为动词是哪几个: deposit(存)、withdraw(取)、work(打工)、read_balance_sheet(查)。 动词清单就是类的对外接口草案。
  3. 初始化要什么: __init__(self, initial_savings, hourly_wage)——出生带的两个数。

类设计的实际顺序就是这样的:先名词(状态)和动词(行为),后代码。反过来的写法(先写一堆方法再凑)是第 15 章要批评的「先实现后接口」。

6.1 第一版:照搬现实

class Person:
    def __init__(self, initial_savings, hourly_wage):
        self._amount_saved = initial_savings    # 下划线:内部数据
        self._hourly_wage = hourly_wage

    def deposit(self, amount):
        self._amount_saved += amount

    def withdraw(self, amount):
        self._amount_saved -= amount

    def work(self, hours):
        self._amount_saved += hours * self._hourly_wage

    def read_balance_sheet(self):
        return self._amount_saved

作者点评:「这个例子与现实非常相似——这是好兆头,说明建模建对了。」 现实的规则是:你不能直接改存款数字,只能存钱、取钱、打工——代码里 deposit/withdraw/work 就是仅有的三条修改路径,_amount_saved 被下划线藏了起来。12

使用端走一遍(书里的数):

anna = Person(initial_savings=100, hourly_rate=10)  # 书里此处笔误,参数名应是 hourly_wage
anna.deposit(100)
anna.work(10)             # 时薪 10 × 10 小时 = +100
anna.withdraw(50)
print(anna.read_balance_sheet())    # 100 + 100 + 100 - 50 = 250

每一步旁边都有具体的数:起点 100 → 存 100(200)→ 打 10 小时工(300)→ 取 50(250)。

6.2 第二版:余额永不为负

现在加规则:余额不允许变负。书里先问了一个比代码更难的问题—— 遇到违规,该怎么办?「回答这类问题,经常比实现对应的代码更难。」他给出的业务裁决:13

  • 存负数 → 这是恶意操作(历史上正是这类把戏开创了黑客时代),抛异常拦下;
  • 取得比余额多 → 当善意失误处理:钱全给,余额归零。

攻击者的视角值得先看一眼——书里真写了一个小偷:

thief = Person(0, 0)
print(thief.read_balance_sheet())    # 0
thief.deposit(-100)                  # 余额 0 存 −100 → 余额变成 −100:凭空造钱

「这类把戏正是黑客的起源」——防御规则必须从最坏输入出发设计, 而不是从「用户大概不会这么干」出发。14

    def deposit(self, amount):
        if amount < 0:
            raise ValueError("You can't deposit a negative amount.")
        self._amount_saved += amount

    def withdraw(self, amount):
        if amount > self._amount_saved:
            self._amount_saved = 0
        else:
            self._amount_saved -= amount

这个「先定业务裁决、再写实现」的顺序,书里用了一个加框的小节来讲它的行业名字: 需求工程(requirements engineering)——搞清楚软件该做什么,比做出来难得多; 他举的失败案例是诺基亚:有工程师提议做无键盘手机,管理层没看见智能手机的潜力—— 「软件团队做出来的东西没人用,通常因为没搞清需求。」14

加框里还有一句行业常识值得带走:「很多软件团队造出了根本没被使用的软件」—— 不是因为技术不行,是因为没人持续去问用户要什么。需求工程师这个角色存在的全部理由, 就是替程序员保住「做出来的东西有人用」这条底线;没有这个角色时, 每个程序员都得自己兼任一部分——而兼任的方式,就是像本例这样, 在写 if 之前先写下「遇到这种情况,业务上应该发生什么」。

6.3 第三版:一类人排队操作

类的另一个红利在第 12 章展开,但由这个例子收尾:student 和 lawyer 是不同类型的 Person? 不——是同一个类的两个对象,天然可以装进同一个列表,循环里一视同仁:

student = Person(initial_savings=10, hourly_wage=10)
lawyer  = Person(initial_savings=1000, hourly_wage=100)
people = [student, lawyer]
for person in people:
    person.withdraw(100)
# student 余额 0(只有 10,全取走归零);lawyer 余额 900

最后两行输出值得对账:student 只有 10,取的 100 超过余额 → 按第二版规则「全给、归零」,打印 0; lawyer 1000 − 100 = 900。两条业务裁决在循环里被一视同仁地执行—— 这正是第 08 章「别特判」的类版本:差异藏在对象内部,循环本身保持平凡。15

6.4 这个例子为什么值得整章记住

回看 Person 的三版演进,它其实是本书前半部的压缩回放:

版本 用到的章节 动作
第一版 02(命名)、03(下划线=私有)、11(类与 self) 把现实规则翻成代码
第二版 06(raise 异常)、08(if 最少化)、需求工程框 给接口补防御
第三版 04(列表循环)、07(SRP) 让对象们成批工作

一个 30 行的教学例子走完了六章的知识——这就是「类是粘合剂」的实证:机制散在各章, 类把它们组装成能跑的程序。读完本章回头看,前面每一章都不是孤立的。

但书里紧接着泼了冷水,这句转折是下半章(第 12 章)的引子:「类是极其有用的构造——存变量和函数, 写起来顺手,一切安好。不是吗?未必。要是编程就这么容易就好了…… 类也有它自己的一组缺点,否则就不会有喜欢函数式编程的程序员群体了。 Linux 内核用 C 写的,C 没有类;要是类真那么好,为什么不用别的?」16 接下来的问题清单——成员变量、继承、getter/setter——就是这一问的答案。

「能装进同一个列表、被同一行代码处理」——这正是第 07 章内聚和第 08 章「一视同仁」的类版本。

7. 作者的判断与证据

书里给了证据的: data class 与 Address 的完整可运行示例;银行账户例子的每步数字; TypeError/构造器行为可直接验证。21012

作者的判断(立场):

  • 「类 vs 列表,类严格更好」——在「结构化记录」这个场景成立;
  • 「类没有带来根本新概念」——准确的历史判断,也是反 OO(面向对象)崇拜的清醒剂;
  • 「特殊方法大多不用」——实用主义立场;
  • 「初始化里别放复杂逻辑」——普遍认同的工程规矩。

判断(我们的,不是书里的): 这个银行账户例子是全书的隐藏明珠——它的真正教训不在 Python, 而在「接口先于实现」:deposit/withdraw 三个动词就是需求文档,业务裁决(恶意/善意)先于 if 语句。 第 15 章的测试一章会把这同一句话再说一遍(「好代码是可测试的」)。 如果错,会错在: 对「查询即修改」边界模糊的领域(金融高频账务),「钱全给、余额归零」式的事后兜底不够, 需要事务(一组操作要么全部生效、要么全部不算数)和幂等(重复执行结果不变)保证——但那是另一本书的复杂度,作者的教学例子不背这个锅。

8. 边界与局限

  • data class 的高级选项(default、frozen=True 真不可变)没提;
  • self 的机制(方法其实是函数、self 是显式传入的第一个参数)没拆开讲;
  • __eq__ 实现后记得同时保持 __hash__ 一致——书里没提,自定义对象进 set/dict 键时会踩;
  • 特殊方法只演示了五个,__len__、__getitem__(让你的类支持下标和遍历)没出现。

9. 可带走的

本章主线例子,一行写完: Person(100, 10) → deposit(100) → work(10) → withdraw(50) → 查余额 250;加防御后:存负数抛 ValueError、取得比余额多则归零。1213

  1. 不同属性的组合用类,不用列表(调用处 a.name 对 a[0] 完胜)。
  2. data class 三行起步:字段带类型提示,自动获得打印、比较;数据与行为分离是它的本分。
  3. 类是图纸、对象是实例;「没有对象的类」(抽象类、静态类)少用后者。
  4. 封装 = 能藏都藏:用户只见几个方法;Python 用下划线约定代替 private。
  5. SRP:类太长 = 做多件事,该拆。
  6. 特殊方法接语法:__init__ 日常必备,__str__/__eq__ 按需;别炫。
  7. self 就是「这个对象自己」,每个方法的第一个参数。
  8. 需求先于实现:先定「违规怎么办」的业务裁决,再写 if 和 raise——想清楚比写出来难。
  9. 类的红利:同类实例天然同列表、同循环(异类对象怎么混排,见第 12 章鸭子类型)。
  10. 写类前先写三件东西:状态清单(名词)、行为清单(动词)、初始化参数——写不出来,说明还没想清楚这个类是什么。

10. 原文地图

主题 原书章 原文位置
动机:不同属性该进类 7 Classes text/23-ch07-7-classes.txt:5(搜「define a class」)
data class 定义与使用 7 Classes text/23-ch07-7-classes.txt:8(搜「from dataclasses」) · text/23-ch07-7-classes.txt:22(搜「my_address = Address」)
汽车类比:概念压缩零件 7 Classes text/23-ch07-7-classes.txt:35(搜「familiar with the concept」)
类 vs 列表的正面对决 7 Classes text/23-ch07-7-classes.txt:40(搜「Classes vs. Lists」) · text/23-ch07-7-classes.txt:67(搜「strictly better」)
历史:struct、C++、「无根本新概念」 7 Classes text/23-ch07-7-classes.txt:71(搜「structs」) · text/23-ch07-7-classes.txt:82(搜「fundamentally new concepts」)
封装与「掀开发动机盖」 7 Classes text/23-ch07-7-classes.txt:96(搜「encapsulation」) · text/23-ch07-7-classes.txt:114(搜「tinkering with the engine」)
SRP 7 Classes text/23-ch07-7-classes.txt:101(搜「Single Responsibility」)
类与对象、抽象类、静态类 7 Classes text/23-ch07-7-classes.txt:118(搜「general structure」) · text/23-ch07-7-classes.txt:138(搜「static class」)
特殊方法清单与五个说明 7 Classes text/23-ch07-7-classes.txt:155(搜「special methods」) · text/23-ch07-7-classes.txt:165(搜「init」)
Address 的 str/eq/dict 演示 7 Classes text/23-ch07-7-classes.txt:183(搜「str」) · text/23-ch07-7-classes.txt:203(搜「dict」)
「只用 init,其余是噱头」 7 Classes text/23-ch07-7-classes.txt:219(搜「on a regular basis」)
Person 第一版与「与现实相似是好兆头」 7 Classes text/23-ch07-7-classes.txt:225(搜「some savings」) · text/23-ch07-7-classes.txt:243(搜「good sign」)
使用端:100→250 的每一步 7 Classes text/23-ch07-7-classes.txt:253(搜「read_balance_sheet」)
负数攻击(hacker 起源) 7 Classes text/23-ch07-7-classes.txt:259(搜「origin of hacking」)
业务裁决与防御实现 7 Classes text/23-ch07-7-classes.txt:265(搜「may never be negative」) · text/23-ch07-7-classes.txt:295(搜「ValueError」)
需求工程与诺基亚 7 Classes text/23-ch07-7-classes.txt:280(搜「Requirements Engineering」)
多人列表循环 7 Classes text/23-ch07-7-classes.txt:348(搜「people =」)

  1. 出处:「7 Classes」第 3–6 段(text/23-ch07-7-classes.txt:5,搜「different properties」)。 ↩

  2. 出处:「7 Classes」第 8–28 段(text/23-ch07-7-classes.txt:8,搜「from dataclasses import dataclass」;「不强制」见第 17 段,搜「not enforced」)。 ↩↩↩

  3. 出处:「7 Classes」第 30–36 段(text/23-ch07-7-classes.txt:35,搜「familiar with the concept of a car」)。 ↩

  4. 出处:「7 Classes」第 40–68 段(text/23-ch07-7-classes.txt:61,搜「user code」;第 67 段,搜「strictly better」)。 ↩↩

  5. 出处:「7 Classes」第 70–84 段(text/23-ch07-7-classes.txt:71,搜「structs」;第 82 段,搜「fundamentally new concepts」)。 ↩

  6. 出处:「7 Classes」第 89–99 段(text/23-ch07-7-classes.txt:96,搜「Encapsulate your code as much as possible」)。 ↩

  7. 出处:「7 Classes」第 109–114 段(text/23-ch07-7-classes.txt:111,搜「variable names starting with」)与第 113 段(「tinkering with the engine」,搜「tinkering」)。 ↩↩

  8. 出处:「7 Classes」第 101–107 段(text/23-ch07-7-classes.txt:101,搜「Single Responsibility Principle」;第 106 段,搜「too long」)。 ↩

  9. 出处:「7 Classes」第 116–154 段(text/23-ch07-7-classes.txt:119,搜「specific implementation」;抽象类见第 134 段,搜「abstract classes」;静态类之贬见第 152 段,搜「advise against」)。 ↩↩↩↩

  10. 出处:「7 Classes」第 155–204 段(text/23-ch07-7-classes.txt:165,搜「init」;清单见第 162–174 段;演示代码见第 177–204 段)。 ↩↩↩↩↩

  11. 出处:「7 Classes」第 219–221 段(text/23-ch07-7-classes.txt:219,搜「on a regular basis」)。 ↩

  12. 出处:「7 Classes」第 222–253 段(text/23-ch07-7-classes.txt:229,搜「_amount_saved」;第 243 段,搜「good sign」;使用端见第 248–253 段)。 ↩↩↩

  13. 出处:「7 Classes」第 255–279 段(text/23-ch07-7-classes.txt:272,搜「more difficult than implementing」;两条业务裁决见第 275–278 段)。 ↩↩

  14. 出处:「7 Classes」第 281–287 段(「Requirements Engineering」框,text/23-ch07-7-classes.txt:280,搜「Requirements Engineering」)。 ↩↩

  15. 出处:「7 Classes」第 344–358 段(text/23-ch07-7-classes.txt:348,搜「people =」)。 ↩

  16. 出处:「7 Classes」第 310–314 段(text/23-ch07-7-classes.txt:312,搜「own set of disadvantages」;「Linux 内核」见第 313 段)。 ↩