178 lines
7.1 KiB
Python
178 lines
7.1 KiB
Python
# 第 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. 程序实际输出的业务结果正确;说明文字或非必要类型注解不阻塞验收。
|