feat(面向对象): 完成第三阶段差异化课程

This commit is contained in:
zhiye.sun
2026-08-11 17:53:53 +08:00
parent b348dc0a1f
commit f1d9548646
16 changed files with 2131 additions and 14 deletions

View File

@@ -0,0 +1,226 @@
# 第 3-5 课:面向对象综合项目
## 一、项目目标
本课完成一个内存版图书管理系统,集中验证第三阶段知识。数据只保存在程序运行期间,不读写文件,也不连接数据库,避免重复第二阶段内容。
完成后,你将能够:
1. 从业务职责中识别类;
2. 使用 dataclass 定义数据对象;
3. 使用普通类组织业务操作;
4. 使用组合建立对象关系;
5. 使用 property 和特殊方法提供清晰接口;
6. 使用自定义异常表达业务失败;
7. 使用鸭子类型接入通知能力;
8. 对借书、还书和异常流程进行完整验证。
## 二、项目角色
| 角色 | 主要职责 |
|---|---|
| `Book` | 保存图书信息和可借状态,提供友好显示 |
| `Member` | 保存会员信息和已借 ISBN计算已借数量 |
| `Library` | 管理图书、会员以及借还业务规则 |
| `ConsoleNotifier` | 输出借还成功通知 |
| 业务异常 | 区分图书不存在、会员不存在、图书不可借等失败 |
职责边界很重要:
- `Book` 不负责查找会员;
- `Member` 不负责维护整个图书馆;
- `ConsoleNotifier` 不负责改变借阅状态;
- `Library` 负责协调对象并保证借还规则一致。
## 三、为什么 Library 使用普通类
`Book``Member` 主要保存数据,适合使用 `@dataclass`
`Library` 的主要职责是管理业务行为和对象关系,并不是单纯的数据容器,因此使用普通类更清晰。不是所有类都应该加 `@dataclass`
## 四、组合关系
图书馆“拥有”图书和会员:
```python
self.books: dict[str, Book] = {}
self.members: dict[str, Member] = {}
```
这是组合,而不是继承。`Library` 不是一种 `Book`,所以不应写成:
```python
class Library(Book):
...
```
也不应为了复用字典操作就让 `Library` 继承 `dict`。把字典作为内部属性更容易维护业务约束。
## 五、业务异常体系
项目使用一个共同基础异常:
```python
class LibraryError(Exception):
pass
```
具体异常继承它:
```python
class BookNotFoundError(LibraryError):
pass
```
调用者既可以捕获具体错误,也可以统一处理所有图书馆业务错误:
```python
try:
library.borrow_book(...)
except LibraryError as error:
print(error)
```
与 Java 不同Python 没有受检异常的强制 `throws` 声明。
## 六、借书事务中的一致性
一次成功借书必须同时更新:
```text
Book.available = False
Member.borrowed_isbns 添加 ISBN
发送成功通知
```
更新前要先完成全部业务校验。如果先修改一部分状态再发现错误,容易留下不一致数据。
本课数据都在内存中,因此暂不涉及数据库事务。第四阶段学习数据库后,会把这里的业务模型升级为持久化版本。
## 七、鸭子类型通知器
`Library` 不要求通知器继承指定父类,只调用:
```python
notifier.send(message)
```
未来可以传入邮件通知器、短信通知器或测试通知器,只要它们提供兼容的 `send()` 方法。
不要在业务类中写:
```python
if isinstance(notifier, ConsoleNotifier):
...
```
这会让业务类依赖具体实现,降低扩展能力。
## 八、参考示例
先运行不同业务场景的仓库示例:
```powershell
python .\03_面向对象\3_5_面向对象综合项目\domain_model_example.py
```
示例展示:
- dataclass 数据对象;
- property 计算状态;
- 普通业务类通过组合管理对象;
- 自定义异常;
- 操作前校验和状态修改。
示例不是综合项目答案,只用于参考对象职责和代码组织方式。
## 九、完成综合项目
打开:
```text
03_面向对象/3_5_面向对象综合项目/practice.py
```
题目已经按实现顺序提供:
1. 业务异常;
2. `Book`
3. `Member`
4. 通知器;
5. `Library` 数据结构;
6. 添加与注册;
7. 内部查找;
8. 借书;
9. 还书;
10. 可借查询;
11. 正常流程;
12. 异常流程。
可以直接从 `practice.py` 开始,不必先完整阅读本文。
## 十、运行方法
完成代码后,在项目根目录运行:
```powershell
python .\03_面向对象\3_5_面向对象综合项目\practice.py
```
每完成一两个类就运行一次,不要等全部写完再集中排错。
## 十一、常见错误
### 11.1 把所有行为放进一个类
如果 `Library` 同时负责格式化所有对象、直接保存通知历史和处理用户输入,职责会迅速膨胀。让每个对象管理与自己紧密相关的数据和行为。
### 11.2 使用继承表达拥有关系
图书馆拥有图书,不代表图书馆是一种图书。拥有关系使用组合。
### 11.3 校验前修改状态
先确认会员、图书和可借状态全部合法,再同时更新图书与会员。
### 11.4 捕获 Exception 隐藏程序错误
练习中优先捕获 `LibraryError`。直接捕获所有 `Exception` 可能把拼写错误等程序缺陷误当成业务失败。
### 11.5 为通知器判断具体类型
直接调用 `notifier.send()`,保持鸭子类型。不要为每种通知方式增加一个条件分支。
## 十二、验证层级
完成后按以下层级检查:
- 必须修复:语法和运行错误、状态不一致、业务规则遗漏、共享借阅列表、具体类型分支;
- 建议改进:重复查找、职责混乱、容易误解的结构;
- 可选优化:非必要类型注解、说明文字差异、输出美化和格式统一。
只有“必须修复”会阻止项目和第三阶段验收。
## 十三、第三阶段知识回顾
- `self` 显式出现在实例方法定义中;
- `__init__` 负责初始化,创建对象不使用 `new`
- 下划线主要表达约定,不是 Java 访问控制的完全替代;
- property 保持属性语法并执行方法逻辑;
- `super()` 按 MRO 继续查找实现;
- 鸭子类型关注对象行为而非声明的共同接口;
- dataclass 减少数据对象样板代码;
- 特殊方法让对象参与 Python 标准语法;
- 组合通常比为了复用而继承更灵活。
## 十四、阶段验收标准
- `practice.py` 正常运行;
- 正常借还流程状态正确;
- 主要异常流程均被阻止;
- 类的职责边界清晰;
- 能解释项目中继承、组合和鸭子类型分别出现在哪里;
- 能说明 dataclass 和普通类的选择原因;
- 能说明 Python 面向对象写法与 Java 的主要差异。
通过后,第三阶段完成,下一阶段开始数据库编程,并逐步把本项目升级为持久化版本。