Files

178 lines
7.1 KiB
Python
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. 定义 LibraryError(Exception),类体中只写 pass。
# 2. 定义 BookNotFoundError(LibraryError),类体中只写 pass。
# 3. 定义 BookUnavailableError(LibraryError),类体中只写 pass。
# 4. 定义 MemberNotFoundError(LibraryError),类体中只写 pass。
#
# Python 与 Java 对照:
# - 自定义异常同样通过继承建立分类;
# - Python 没有 Java 受检异常Checked Exception的强制 throws 声明;
# - 调用者可以捕获具体异常,也可以统一捕获 LibraryError。
# 第二部分:定义 Book 数据类
# 1. 使用 @dataclass。
# 2. 定义字段:
# isbn: str
# title: str
# author: str
# available: bool = True
# 3. 定义 __str__返回格式
# “《Python 编程》|作者:小明|状态:可借”。
# 4. available 为 False 时,状态文字应为“已借出”。
#
# 设计提醒:
# - dataclass 自动生成初始化、调试显示和字段相等比较;
# - __str__ 只负责一个 Book 自身的友好显示。
# 第三部分:定义 Member 数据类
# 1. 使用 @dataclass。
# 2. 定义字段:
# member_id: str
# name: str
# borrowed_isbns: list[str] = field(default_factory=list)
# 3. 定义 borrowed_count 只读 property返回已借 ISBN 的数量。
#
# 设计提醒:
# - 使用 default_factory避免不同会员共享同一借阅列表
# - Member 只保存自己的借阅结果,不负责查找图书馆中的书。
# 第四部分:定义 ConsoleNotifier
# 1. 不需要继承任何通知父类或接口。
# 2. 定义 send(self, message) 方法,直接输出:
# “通知:{message}”。
#
# 鸭子类型提醒:
# - Library 只要求通知器具有 send() 方法;
# - 不要在 Library 中使用 isinstance() 判断通知器类型。
# 第五部分:定义 Library 类和初始化方法
# 1. 定义 __init__(self, name) 方法并保存 self.name。
# 2. 创建实例属性:
# self.books: dict[str, Book] = {}
# self.members: dict[str, Member] = {}
# 3. books 使用 ISBN 作为键members 使用会员编号作为键。
#
# 组合提醒:
# - Library “拥有”多个 Book 和 Member因此这里使用组合而不是继承
# - 不要让 Library 继承 list 或 dict。
# 第六部分:实现 add_book(self, book)
# 1. book.isbn 已存在时抛出 LibraryError("ISBN 已存在。")。
# 2. 不存在时把 book 保存到 self.books。
# 3. 本方法不需要输出。
# 第七部分:实现 register_member(self, member)
# 1. member.member_id 已存在时抛出 LibraryError("会员编号已存在。")。
# 2. 不存在时把 member 保存到 self.members。
# 3. 本方法不需要输出。
# 第八部分:实现两个内部查找方法
# 1. 定义 _get_book(self, isbn),找到时返回 Book。
# 2. 找不到时抛出 BookNotFoundError("图书不存在。")。
# 3. 定义 _get_member(self, member_id),找到时返回 Member。
# 4. 找不到时抛出 MemberNotFoundError("会员不存在。")。
#
# 单下划线提醒:
# - _get_book 和 _get_member 表示 Library 内部使用的辅助方法;
# - 单下划线是约定,不是 Java private 式的强制限制。
# 第九部分:实现 borrow_book(self, member_id, isbn, notifier)
# 1. 通过 _get_member() 查找会员。
# 2. 通过 _get_book() 查找图书。
# 3. book.available 为 False 时抛出:
# BookUnavailableError("图书已被借出。")。
# 4. 校验通过后执行:
# book.available = False
# member.borrowed_isbns.append(isbn)
# 5. 调用 notifier.send(),消息格式:
# “小明成功借阅《Python 编程》”。
#
# 多态提醒:
# - notifier 的类型注解可以省略,不影响验收;
# - 只调用 send(),不要判断通知器的具体类型。
# 第十部分:实现 return_book(self, member_id, isbn, notifier)
# 1. 通过两个内部查找方法取得会员和图书。
# 2. isbn 不在 member.borrowed_isbns 中时抛出:
# LibraryError("该会员没有借阅这本书。")。
# 3. 校验通过后执行:
# member.borrowed_isbns.remove(isbn)
# book.available = True
# 4. 调用 notifier.send(),消息格式:
# “小明成功归还《Python 编程》”。
# 第十一部分:实现 available_books(self)
# 1. 返回所有 available 为 True 的 Book 对象组成的新列表。
# 2. 不要修改 self.books。
# 3. 可以使用列表推导式。
# 第十二部分:定义 main() 并完成正常流程
# 1. 创建 Library("城市图书馆")。
# 2. 创建 ConsoleNotifier()。
# 3. 添加以下两本书:
# Book("978-001", "Python 编程", "小明")
# Book("978-002", "流畅的 Python", "Luciano")
# 4. 注册 Member("M001", "小红")。
# 5. 调用 library.available_books() 取得当前可借图书列表。
# 6. 使用 len() 统计列表长度并输出“初始可借数量2”参考写法
# print(f"初始可借数量:{len(library.available_books())}")
# 7. 借出 ISBN 为 978-001 的书。
# 8. 再次调用 available_books(),使用 len() 输出“借阅后可借数量1”。
# 9. 读取前面创建的 Member 对象的 borrowed_count输出“会员已借数量1”。
# 10. 归还 ISBN 为 978-001 的书。
# 11. 再次调用 available_books(),使用 len() 输出“归还后可借数量2”。
# 12. 再次读取该 Member 对象的 borrowed_count输出“会员已借数量0”。
# 第十三部分:在 main() 中完成异常流程
# 1. 再次借出 978-001。
# 2. 使用 try...except 再借一次同一本书。
# 3. 捕获 LibraryError并输出
# “重复借阅已被阻止:图书已被借出。”。
# 4. 使用 try...except 借阅 ISBN 为 978-999 的书。
# 5. 捕获 LibraryError并输出
# “不存在的图书已被阻止:图书不存在。”。
# 6. 添加程序入口判断并调用 main()。
#
# 完整预期输出:
# 初始可借数量2
# 通知小红成功借阅《Python 编程》
# 借阅后可借数量1
# 会员已借数量1
# 通知小红成功归还《Python 编程》
# 归还后可借数量2
# 会员已借数量0
# 通知小红成功借阅《Python 编程》
# 重复借阅已被阻止:图书已被借出。
# 不存在的图书已被阻止:图书不存在。
# 最终验收标准:
# 1. Book 和 Member 使用 dataclass且不同会员不共享借阅列表
# 2. Book 的 __str__ 能区分“可借”和“已借出”;
# 3. Member.borrowed_count 能随借还操作变化;
# 4. Library 通过组合管理 Book 和 Member
# 5. 借书和还书同时更新图书状态与会员借阅记录;
# 6. 通知器通过鸭子类型调用,没有具体类型判断;
# 7. 业务异常继承关系正确,并能统一捕获 LibraryError
# 8. 重复 ISBN、重复会员、图书不存在、会员不存在、重复借阅和错误归还均被阻止
# 9. available_books() 返回新列表,不修改图书字典;
# 10. 程序实际输出的业务结果正确;说明文字或非必要类型注解不阻塞验收。