Files
PythonLearn/03_面向对象/3_5_面向对象综合项目

第 3-5 课:面向对象综合项目

一、项目目标

本课完成一个内存版图书管理系统,集中验证第三阶段知识。数据只保存在程序运行期间,不读写文件,也不连接数据库,避免重复第二阶段内容。

完成后,你将能够:

  1. 从业务职责中识别类;
  2. 使用 dataclass 定义数据对象;
  3. 使用普通类组织业务操作;
  4. 使用组合建立对象关系;
  5. 使用 property 和特殊方法提供清晰接口;
  6. 使用自定义异常表达业务失败;
  7. 使用鸭子类型接入通知能力;
  8. 对借书、还书和异常流程进行完整验证。

二、项目角色

角色 主要职责
Book 保存图书信息和可借状态,提供友好显示
Member 保存会员信息和已借 ISBN计算已借数量
Library 管理图书、会员以及借还业务规则
ConsoleNotifier 输出借还成功通知
业务异常 区分图书不存在、会员不存在、图书不可借等失败

职责边界很重要:

  • Book 不负责查找会员;
  • Member 不负责维护整个图书馆;
  • ConsoleNotifier 不负责改变借阅状态;
  • Library 负责协调对象并保证借还规则一致。

三、为什么 Library 使用普通类

BookMember 主要保存数据,适合使用 @dataclass

Library 的主要职责是管理业务行为和对象关系,并不是单纯的数据容器,因此使用普通类更清晰。不是所有类都应该加 @dataclass

四、组合关系

图书馆“拥有”图书和会员:

self.books: dict[str, Book] = {}
self.members: dict[str, Member] = {}

这是组合,而不是继承。Library 不是一种 Book,所以不应写成:

class Library(Book):
    ...

也不应为了复用字典操作就让 Library 继承 dict。把字典作为内部属性更容易维护业务约束。

五、业务异常体系

项目使用一个共同基础异常:

class LibraryError(Exception):
    pass

具体异常继承它:

class BookNotFoundError(LibraryError):
    pass

调用者既可以捕获具体错误,也可以统一处理所有图书馆业务错误:

try:
    library.borrow_book(...)
except LibraryError as error:
    print(error)

与 Java 不同Python 没有受检异常的强制 throws 声明。

六、借书事务中的一致性

一次成功借书必须同时更新:

Book.available = False
Member.borrowed_isbns 添加 ISBN
发送成功通知

更新前要先完成全部业务校验。如果先修改一部分状态再发现错误,容易留下不一致数据。

本课数据都在内存中,因此暂不涉及数据库事务。第四阶段学习数据库后,会把这里的业务模型升级为持久化版本。

七、鸭子类型通知器

Library 不要求通知器继承指定父类,只调用:

notifier.send(message)

未来可以传入邮件通知器、短信通知器或测试通知器,只要它们提供兼容的 send() 方法。

不要在业务类中写:

if isinstance(notifier, ConsoleNotifier):
    ...

这会让业务类依赖具体实现,降低扩展能力。

八、参考示例

先运行不同业务场景的仓库示例:

python .\03_面向对象\3_5_面向对象综合项目\domain_model_example.py

示例展示:

  • dataclass 数据对象;
  • property 计算状态;
  • 普通业务类通过组合管理对象;
  • 自定义异常;
  • 操作前校验和状态修改。

示例不是综合项目答案,只用于参考对象职责和代码组织方式。

九、完成综合项目

打开:

03_面向对象/3_5_面向对象综合项目/practice.py

题目已经按实现顺序提供:

  1. 业务异常;
  2. Book
  3. Member
  4. 通知器;
  5. Library 数据结构;
  6. 添加与注册;
  7. 内部查找;
  8. 借书;
  9. 还书;
  10. 可借查询;
  11. 正常流程;
  12. 异常流程。

可以直接从 practice.py 开始,不必先完整阅读本文。

十、运行方法

完成代码后,在项目根目录运行:

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 的主要差异。

通过后,第三阶段完成,下一阶段开始数据库编程,并逐步把本项目升级为持久化版本。