类(上)— data class、封装与特殊方法
类(上)— 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
封装之所以要紧,书的论证分两层:
- 认知层面: 用户要记的东西越少,用错的可能越小(「就像你不知道 Python 默认用的是什么排序算法——不在乎,能用就行」);
- 变更层面: 内部随便改,只要接口不变,用户无感——这是代码能长期演化的前提。
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.cityprint(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 写之前的三个设计决定
动手前,作者其实已经做了三个决定,值得显式列出:
- 状态叫什么、放哪: 存款叫
_amount_saved、时薪叫_hourly_wage,都带下划线——内部状态,第 3 节刚立的规矩。 - 行为动词是哪几个: deposit(存)、withdraw(取)、work(打工)、read_balance_sheet(查)。 动词清单就是类的对外接口草案。
- 初始化要什么:
__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
- 不同属性的组合用类,不用列表(调用处
a.name对a[0]完胜)。 - data class 三行起步:字段带类型提示,自动获得打印、比较;数据与行为分离是它的本分。
- 类是图纸、对象是实例;「没有对象的类」(抽象类、静态类)少用后者。
- 封装 = 能藏都藏:用户只见几个方法;Python 用下划线约定代替 private。
- SRP:类太长 = 做多件事,该拆。
- 特殊方法接语法:
__init__日常必备,__str__/__eq__按需;别炫。 self就是「这个对象自己」,每个方法的第一个参数。- 需求先于实现:先定「违规怎么办」的业务裁决,再写 if 和 raise——想清楚比写出来难。
- 类的红利:同类实例天然同列表、同循环(异类对象怎么混排,见第 12 章鸭子类型)。
- 写类前先写三件东西:状态清单(名词)、行为清单(动词)、初始化参数——写不出来,说明还没想清楚这个类是什么。
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 =」) |
Footnotes
-
出处:「7 Classes」第 3–6 段(
text/23-ch07-7-classes.txt:5,搜「different properties」)。 ↩ -
出处:「7 Classes」第 8–28 段(
text/23-ch07-7-classes.txt:8,搜「from dataclasses import dataclass」;「不强制」见第 17 段,搜「not enforced」)。 ↩ ↩2 ↩3 -
出处:「7 Classes」第 30–36 段(
text/23-ch07-7-classes.txt:35,搜「familiar with the concept of a car」)。 ↩ -
出处:「7 Classes」第 40–68 段(
text/23-ch07-7-classes.txt:61,搜「user code」;第 67 段,搜「strictly better」)。 ↩ ↩2 -
出处:「7 Classes」第 70–84 段(
text/23-ch07-7-classes.txt:71,搜「structs」;第 82 段,搜「fundamentally new concepts」)。 ↩ -
出处:「7 Classes」第 89–99 段(
text/23-ch07-7-classes.txt:96,搜「Encapsulate your code as much as possible」)。 ↩ -
出处:「7 Classes」第 109–114 段(
text/23-ch07-7-classes.txt:111,搜「variable names starting with」)与第 113 段(「tinkering with the engine」,搜「tinkering」)。 ↩ ↩2 -
出处:「7 Classes」第 101–107 段(
text/23-ch07-7-classes.txt:101,搜「Single Responsibility Principle」;第 106 段,搜「too long」)。 ↩ -
出处:「7 Classes」第 116–154 段(
text/23-ch07-7-classes.txt:119,搜「specific implementation」;抽象类见第 134 段,搜「abstract classes」;静态类之贬见第 152 段,搜「advise against」)。 ↩ ↩2 ↩3 ↩4 -
出处:「7 Classes」第 155–204 段(
text/23-ch07-7-classes.txt:165,搜「init」;清单见第 162–174 段;演示代码见第 177–204 段)。 ↩ ↩2 ↩3 ↩4 ↩5 -
出处:「7 Classes」第 219–221 段(
text/23-ch07-7-classes.txt:219,搜「on a regular basis」)。 ↩ -
出处:「7 Classes」第 222–253 段(
text/23-ch07-7-classes.txt:229,搜「_amount_saved」;第 243 段,搜「good sign」;使用端见第 248–253 段)。 ↩ ↩2 ↩3 -
出处:「7 Classes」第 255–279 段(
text/23-ch07-7-classes.txt:272,搜「more difficult than implementing」;两条业务裁决见第 275–278 段)。 ↩ ↩2 -
出处:「7 Classes」第 281–287 段(「Requirements Engineering」框,
text/23-ch07-7-classes.txt:280,搜「Requirements Engineering」)。 ↩ ↩2 -
出处:「7 Classes」第 344–358 段(
text/23-ch07-7-classes.txt:348,搜「people =」)。 ↩ -
出处:「7 Classes」第 310–314 段(
text/23-ch07-7-classes.txt:312,搜「own set of disadvantages」;「Linux 内核」见第 313 段)。 ↩