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