Files

227 lines
6.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第 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 的主要差异。
通过后,第三阶段完成,下一阶段开始数据库编程,并逐步把本项目升级为持久化版本。