From d39ddbfdbcc882fc945ee50d670dc6c7c284d90a Mon Sep 17 00:00:00 2001 From: kingecg Date: Thu, 3 Jul 2025 23:30:51 +0800 Subject: [PATCH] =?UTF-8?q?"=E6=B7=BB=E5=8A=A0=E7=AC=AC4=E7=AB=A0=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E5=8C=96=E5=B7=A5=E7=A8=8B=E5=86=85=E5=AE=B9=EF=BC=8C?= =?UTF-8?q?=E5=8C=85=E5=90=AB=E6=A8=A1=E5=9D=97=E7=B3=BB=E7=BB=9F=E3=80=81?= =?UTF-8?q?=E5=8C=85=E7=AE=A1=E7=90=86=E3=80=81=E5=B7=A5=E4=BD=9C=E7=A9=BA?= =?UTF-8?q?=E9=97=B4=E5=8F=8A=E5=9B=BE=E4=B9=A6=E9=A6=86=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=AE=9E=E7=8E=B0"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 04.md | 1142 +++++++++++++++++++++++++ examples/ex04/Cargo.lock | 297 +++++++ examples/ex04/Cargo.toml | 17 + examples/ex04/src/lib.rs | 19 + examples/ex04/src/main.rs | 86 ++ examples/ex04/src/models/book.rs | 68 ++ examples/ex04/src/models/mod.rs | 7 + examples/ex04/src/models/user.rs | 93 ++ examples/ex04/src/services/catalog.rs | 113 +++ examples/ex04/src/services/lending.rs | 183 ++++ examples/ex04/src/services/mod.rs | 58 ++ examples/ex04/src/utils/mod.rs | 3 + examples/ex04/src/utils/validation.rs | 80 ++ 13 files changed, 2166 insertions(+) create mode 100644 04.md create mode 100644 examples/ex04/Cargo.lock create mode 100644 examples/ex04/Cargo.toml create mode 100644 examples/ex04/src/lib.rs create mode 100644 examples/ex04/src/main.rs create mode 100644 examples/ex04/src/models/book.rs create mode 100644 examples/ex04/src/models/mod.rs create mode 100644 examples/ex04/src/models/user.rs create mode 100644 examples/ex04/src/services/catalog.rs create mode 100644 examples/ex04/src/services/lending.rs create mode 100644 examples/ex04/src/services/mod.rs create mode 100644 examples/ex04/src/utils/mod.rs create mode 100644 examples/ex04/src/utils/validation.rs diff --git a/04.md b/04.md new file mode 100644 index 0000000..b24463d --- /dev/null +++ b/04.md @@ -0,0 +1,1142 @@ +# 第四章 结构化工程:模块与包 + +## 4.1 模块系统概述 + +随着项目规模增长,代码组织变得至关重要。Rust的模块系统提供了强大的工具,帮助开发者构建清晰、可维护的代码结构。本章将探索如何使用模块、包和工作空间来组织大型Rust项目。 + +### 为什么需要模块化? + +模块化设计带来的核心优势: + +- **代码组织** - 逻辑相关的代码集中在一起 +- **封装** - 隐藏实现细节,只暴露必要接口 +- **命名空间** - 避免名称冲突 +- **可重用性** - 促进代码复用 +- **可测试性** - 更容易为独立模块编写测试 + +### Rust模块系统的核心概念 + +| 概念 | 描述 | 关键字/文件 | +|------|------|------------| +| 模块(Module) | 代码的命名空间单元 | `mod` | +| 包(Package) | 一个或多个crate的集合 | `Cargo.toml` | +| Crate | 编译单元,生成库或可执行文件 | `lib.rs`/`main.rs` | +| 工作空间(Workspace) | 多个相关包的集合 | `[workspace]` | + +## 4.2 mod层级设计:构建代码树 + +### 模块声明与组织 + +Rust中的模块可以嵌套,形成树状结构: + +```rust +// lib.rs +mod front_desk; // 前台模块 +mod cataloging; // 编目模块 +mod user_services; // 用户服务模块 + +// 嵌套模块示例 +mod user_services { + mod registration { + // 用户注册相关功能 + } + + mod borrowing { + // 借阅相关功能 + } +} +``` + +### 模块文件组织策略 + +Rust提供两种组织模块的方式: + +1. **内联模块**:在同一文件中定义 + ```rust + // lib.rs + mod config { + pub const MAX_BOOKS: usize = 5; + + pub fn get_loan_duration() -> u32 { + 14 // 默认借阅期为14天 + } + } + ``` + +2. **文件模块**:在单独文件中定义 + ``` + lib.rs // 声明模块 + └── config.rs // 实现模块 + ``` + + ```rust + // lib.rs + mod config; // 声明模块,实现在config.rs中 + + // config.rs + pub const MAX_BOOKS: usize = 5; + + pub fn get_loan_duration() -> u32 { + 14 + } + ``` + +### 子模块文件组织 + +对于更复杂的模块结构,可以使用目录: + +``` +lib.rs // 声明顶层模块 +├── user_services.rs // 声明user_services子模块 +└── user_services/ // 子模块目录 + ├── registration.rs + └── borrowing.rs +``` + +```rust +// user_services.rs +pub mod registration; // 声明子模块 +pub mod borrowing; // 声明子模块 +``` + +### 模块路径与引用 + +使用`use`关键字简化模块路径: + +```rust +// 不使用use +fn main() { + crate::user_services::registration::register_new_user("Alice"); +} + +// 使用use +use crate::user_services::registration::register_new_user; + +fn main() { + register_new_user("Alice"); +} +``` + +**路径别名**: + +```rust +use crate::user_services::registration::register_new_user as register; + +fn main() { + register("Alice"); +} +``` + +**批量导入**: + +```rust +use crate::user_services::registration::{register_new_user, validate_email}; +``` + +## 4.3 pub权限控制:接口与实现分离 + +### 可见性规则 + +Rust中的所有项默认是私有的,使用`pub`关键字使其公开: + +```rust +mod library { + pub struct Book { // 公开结构体 + pub title: String, // 公开字段 + isbn: String, // 私有字段 + } + + impl Book { + pub fn new(title: &str, isbn: &str) -> Self { // 公开方法 + Self { + title: title.to_string(), + isbn: isbn.to_string(), + } + } + + fn validate_isbn(&self) -> bool { // 私有方法 + !self.isbn.is_empty() + } + } +} +``` + +### 可见性级别 + +Rust提供多种可见性级别: + +| 关键字 | 可见范围 | 用途 | +|--------|---------|------| +| (默认) | 仅当前模块及其子模块 | 隐藏实现细节 | +| `pub` | 任何引入该模块的代码 | 公开API | +| `pub(crate)` | 仅当前crate | 内部API | +| `pub(super)` | 仅父模块 | 限制子模块暴露 | +| `pub(in path)` | 指定路径 | 精细控制可见性 | + +示例: + +```rust +mod library { + pub(crate) struct Catalog { // 仅在crate内可见 + // ... + } + + pub(super) fn internal_helper() { // 仅对父模块可见 + // ... + } +} +``` + +### 接口设计最佳实践 + +1. **最小公开原则**:只公开必要的API +2. **抽象泄漏控制**:使用私有字段防止实现细节泄漏 +3. **API稳定性**:谨慎公开可能变化的内容 + +```rust +// 良好的接口设计 +pub struct Library { + // 私有实现细节 + books: Vec, + users: HashMap, +} + +impl Library { + // 公开稳定API + pub fn new() -> Self { /* ... */ } + pub fn add_book(&mut self, book: Book) { /* ... */ } + pub fn borrow_book(&mut self, user_id: UserId, book_id: BookId) -> Result<(), BorrowError> { /* ... */ } + + // 私有辅助函数 + fn find_available_copy(&self, book_id: BookId) -> Option<&Book> { /* ... */ } +} +``` + +## 4.4 workspace多crate管理:扩展到大型项目 + +### 工作空间基础 + +工作空间允许管理多个相关包: + +```toml +# Cargo.toml (根目录) +[workspace] +members = [ + "library-core", + "library-api", + "library-cli" +] +``` + +目录结构: + +``` +library-workspace/ +├── Cargo.toml # 工作空间配置 +├── library-core/ # 核心功能crate +├── library-api/ # API服务crate +└── library-cli/ # 命令行工具crate +``` + +### 工作空间依赖管理 + +工作空间内的crate可以相互依赖: + +```toml +# library-api/Cargo.toml +[dependencies] +library-core = { path = "../library-core" } +``` + +### 工作空间命令 + +在工作空间根目录执行命令会应用到所有成员: + +```bash +# 构建所有crate +cargo build + +# 测试特定crate +cargo test -p library-core + +# 运行特定crate +cargo run -p library-cli +``` + +### 共享依赖优化 + +工作空间中的共享依赖只编译一次,提高构建效率: + +```toml +# 在各crate的Cargo.toml中 +[dependencies] +serde = "1.0" # 在多个crate中使用的依赖只编译一次 +``` + +## 4.5 文档测试:代码即文档 + +### 文档注释类型 + +Rust支持两种文档注释: + +1. **外部文档注释**:用于项的文档 + ```rust + /// 表示图书馆中的一本书 + /// + /// # 示例 + /// ``` + /// let book = Book::new("Rust编程", "978-1234567890"); + /// assert_eq!(book.title(), "Rust编程"); + /// ``` + pub struct Book { + // ... + } + ``` + +2. **内部文档注释**:用于模块或crate整体文档 + ```rust + //! # 图书馆管理系统 + //! + //! 这个模块提供图书馆管理的核心功能,包括: + //! - 图书编目 + //! - 用户管理 + //! - 借阅流程 + ``` + +### 文档测试 + +文档中的代码块默认会作为测试运行: + +```rust +/// 计算借阅的逾期费用 +/// +/// # 参数 +/// * `days_overdue` - 逾期天数 +/// +/// # 返回值 +/// 应付的逾期费用(元) +/// +/// # 示例 +/// ``` +/// use library::calculate_overdue_fee; +/// assert_eq!(calculate_overdue_fee(5), 5.0); // 5天逾期,收费5元 +/// assert_eq!(calculate_overdue_fee(0), 0.0); // 未逾期,无费用 +/// ``` +pub fn calculate_overdue_fee(days_overdue: u32) -> f64 { + days_overdue as f64 +} +``` + +运行文档测试: + +```bash +cargo test --doc +``` + +### 文档测试特殊标记 + +控制文档测试行为: + +```rust +/// ``` +/// # use library::Book; // 这行在文档中隐藏,但测试时执行 +/// let book = Book::new("Rust编程", "978-1234567890"); +/// ``` +/// +/// ```no_run +/// // 编译但不运行的代码 +/// let book = fetch_from_database("978-1234567890"); +/// ``` +/// +/// ```compile_fail +/// // 应该编译失败的代码 +/// let book: Book = "不是书"; +/// ``` +/// +/// ```ignore +/// // 完全忽略的代码 +/// let x = 非法语法; +/// ``` +``` + +## 4.6 实例项目:图书馆管理系统 + +让我们应用所学知识,构建一个模块化的图书馆管理系统。 + +### 项目结构设计 + +``` +library/ +├── Cargo.toml +└── src/ + ├── lib.rs # 主模块和公共API + ├── models/ # 数据模型 + │ ├── mod.rs + │ ├── book.rs + │ └── user.rs + ├── services/ # 业务逻辑 + │ ├── mod.rs + │ ├── catalog.rs + │ └── lending.rs + └── utils/ # 工具函数 + ├── mod.rs + └── validation.rs +``` + +### 实现核心模块 + +**1. 主模块 (lib.rs)** + +```rust +//! # 图书馆管理系统 +//! +//! 一个模块化的图书馆管理系统,提供图书编目、用户管理和借阅服务。 + +pub mod models; +pub mod services; +mod utils; // 内部工具模块,不对外暴露 + +// 重新导出核心类型,简化用户导入 +pub use models::{Book, User}; +pub use services::{catalog::Catalog, lending::LendingService}; + +/// 系统版本号 +pub const VERSION: &str = "0.1.0"; + +/// 创建一个新的图书馆实例 +pub fn new_library() -> services::Library { + services::Library::new() +} +``` + +**2. 模型模块 (models/mod.rs)** + +```rust +//! 系统中使用的数据模型 + +pub mod book; +pub mod user; + +pub use book::Book; +pub use user::User; +``` + +**3. 图书模型 (models/book.rs)** + +```rust +//! 图书相关的数据模型 + +use std::fmt; + +/// 表示图书馆中的一本书 +#[derive(Debug, Clone, PartialEq)] +pub struct Book { + title: String, + author: String, + isbn: String, + available: bool, +} + +impl Book { + /// 创建一本新书 + /// + /// # 示例 + /// ``` + /// use library::Book; + /// let book = Book::new("Rust编程艺术", "张三", "978-1234567890"); + /// assert_eq!(book.title(), "Rust编程艺术"); + /// assert!(book.is_available()); + /// ``` + pub fn new(title: &str, author: &str, isbn: &str) -> Self { + Self { + title: title.to_string(), + author: author.to_string(), + isbn: isbn.to_string(), + available: true, + } + } + + /// 获取书名 + pub fn title(&self) -> &str { + &self.title + } + + /// 获取作者 + pub fn author(&self) -> &str { + &self.author + } + + /// 获取ISBN + pub fn isbn(&self) -> &str { + &self.isbn + } + + /// 检查书是否可借 + pub fn is_available(&self) -> bool { + self.available + } + + /// 标记为已借出 + pub(crate) fn mark_borrowed(&mut self) { + self.available = false; + } + + /// 标记为已归还 + pub(crate) fn mark_returned(&mut self) { + self.available = true; + } +} + +impl fmt::Display for Book { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} by {} (ISBN: {})", self.title, self.author, self.isbn) + } +} + +**4. 用户模型 (models/user.rs)** + +```rust +//! 用户相关的数据模型 + +use std::fmt; + +/// 用户ID类型 +pub type UserId = u32; + +/// 表示图书馆的一名用户 +#[derive(Debug, Clone, PartialEq)] +pub struct User { + id: UserId, + name: String, + email: String, + active: bool, + borrowed_books: Vec, // 存储已借阅书籍的ISBN +} + +impl User { + /// 创建一个新用户 + /// + /// # 示例 + /// ``` + /// use library::User; + /// let user = User::new(1, "李四", "lisi@example.com"); + /// assert_eq!(user.name(), "李四"); + /// assert!(user.is_active()); + /// ``` + pub fn new(id: UserId, name: &str, email: &str) -> Self { + Self { + id, + name: name.to_string(), + email: email.to_string(), + active: true, + borrowed_books: Vec::new(), + } + } + + /// 获取用户ID + pub fn id(&self) -> UserId { + self.id + } + + /// 获取用户名 + pub fn name(&self) -> &str { + &self.name + } + + /// 获取用户邮箱 + pub fn email(&self) -> &str { + &self.email + } + + /// 检查用户是否激活 + pub fn is_active(&self) -> bool { + self.active + } + + /// 获取用户已借阅的书籍ISBN列表 + pub fn borrowed_books(&self) -> &[String] { + &self.borrowed_books + } + + /// 记录用户借阅了一本书 + pub(crate) fn borrow_book(&mut self, isbn: &str) { + self.borrowed_books.push(isbn.to_string()); + } + + /// 记录用户归还了一本书 + pub(crate) fn return_book(&mut self, isbn: &str) -> bool { + if let Some(pos) = self.borrowed_books.iter().position(|b| b == isbn) { + self.borrowed_books.remove(pos); + true + } else { + false + } + } + + /// 停用用户账户 + pub(crate) fn deactivate(&mut self) { + self.active = false; + } + + /// 重新激活用户账户 + pub(crate) fn activate(&mut self) { + self.active = true; + } +} + +impl fmt::Display for User { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} (ID: {}, Email: {})", self.name, self.id, self.email) + } +} +``` + +**5. 服务模块 (services/mod.rs)** + +```rust +//! 图书馆业务服务模块 + +pub mod catalog; +pub mod lending; + +use std::collections::HashMap; +use crate::models::{Book, User, UserId}; + +/// 图书馆主服务 +pub struct Library { + catalog: catalog::Catalog, + lending: lending::LendingService, +} + +impl Library { + /// 创建一个新的图书馆实例 + pub fn new() -> Self { + Self { + catalog: catalog::Catalog::new(), + lending: lending::LendingService::new(), + } + } + + /// 获取目录服务 + pub fn catalog(&self) -> &catalog::Catalog { + &self.catalog + } + + /// 获取可变目录服务 + pub fn catalog_mut(&mut self) -> &mut catalog::Catalog { + &mut self.catalog + } + + /// 获取借阅服务 + pub fn lending(&self) -> &lending::LendingService { + &self.lending + } + + /// 获取可变借阅服务 + pub fn lending_mut(&mut self) -> &mut lending::LendingService { + &mut self.lending + } +} + +impl Default for Library { + fn default() -> Self { + Self::new() + } +} +``` + +**6. 目录服务 (services/catalog.rs)** + +```rust +//! 图书目录管理服务 + +use std::collections::HashMap; +use crate::models::Book; +use crate::utils::validation; + +/// 图书目录错误类型 +#[derive(Debug)] +pub enum CatalogError { + /// ISBN已存在 + DuplicateIsbn(String), + /// ISBN不存在 + IsbnNotFound(String), + /// ISBN格式无效 + InvalidIsbn(String), +} + +/// 图书目录服务 +pub struct Catalog { + books: HashMap, +} + +impl Catalog { + /// 创建一个新的目录服务 + pub fn new() -> Self { + Self { + books: HashMap::new(), + } + } + + /// 添加一本新书到目录 + /// + /// # 错误 + /// 如果ISBN已存在或无效,返回错误 + /// + /// # 示例 + /// ``` + /// use library::{Book, services::catalog::Catalog}; + /// + /// let mut catalog = Catalog::new(); + /// let book = Book::new("Rust编程", "张三", "978-1234567890"); + /// + /// // 首次添加成功 + /// assert!(catalog.add_book(book.clone()).is_ok()); + /// + /// // 重复添加失败 + /// assert!(catalog.add_book(book).is_err()); + /// ``` + pub fn add_book(&mut self, book: Book) -> Result<(), CatalogError> { + let isbn = book.isbn(); + + // 验证ISBN格式 + if !validation::is_valid_isbn(isbn) { + return Err(CatalogError::InvalidIsbn(isbn.to_string())); + } + + // 检查ISBN是否已存在 + if self.books.contains_key(isbn) { + return Err(CatalogError::DuplicateIsbn(isbn.to_string())); + } + + // 添加图书 + self.books.insert(isbn.to_string(), book); + Ok(()) + } + + /// 根据ISBN查找图书 + pub fn find_by_isbn(&self, isbn: &str) -> Option<&Book> { + self.books.get(isbn) + } + + /// 根据ISBN查找可变图书引用 + pub(crate) fn find_by_isbn_mut(&mut self, isbn: &str) -> Option<&mut Book> { + self.books.get_mut(isbn) + } + + /// 根据标题查找图书 + pub fn find_by_title(&self, title: &str) -> Vec<&Book> { + self.books.values() + .filter(|book| book.title().contains(title)) + .collect() + } + + /// 根据作者查找图书 + pub fn find_by_author(&self, author: &str) -> Vec<&Book> { + self.books.values() + .filter(|book| book.author().contains(author)) + .collect() + } + + /// 获取所有图书 + pub fn all_books(&self) -> Vec<&Book> { + self.books.values().collect() + } + + /// 获取可用图书数量 + pub fn available_count(&self) -> usize { + self.books.values() + .filter(|book| book.is_available()) + .count() + } + + /// 获取总图书数量 + pub fn total_count(&self) -> usize { + self.books.len() + } +} + +impl Default for Catalog { + fn default() -> Self { + Self::new() + } +} +``` + +**7. 借阅服务 (services/lending.rs)** + +```rust +//! 图书借阅管理服务 + +use std::collections::HashMap; +use crate::models::{Book, User, UserId}; +use crate::services::catalog::Catalog; + +/// 借阅记录 +#[derive(Debug)] +pub struct LoanRecord { + user_id: UserId, + isbn: String, + loan_date: chrono::DateTime, + due_date: chrono::DateTime, +} + +/// 借阅错误类型 +#[derive(Debug)] +pub enum LendingError { + /// 用户不存在 + UserNotFound(UserId), + /// 图书不存在 + BookNotFound(String), + /// 图书不可用(已借出) + BookUnavailable(String), + /// 用户账户未激活 + UserInactive(UserId), + /// 用户已达到最大借阅限制 + BorrowLimitReached(UserId), + /// 借阅记录不存在 + LoanNotFound, +} + +/// 借阅服务 +pub struct LendingService { + users: HashMap, + active_loans: Vec, + max_loans_per_user: usize, + loan_duration_days: i64, +} + +impl LendingService { + /// 创建一个新的借阅服务 + pub fn new() -> Self { + Self { + users: HashMap::new(), + active_loans: Vec::new(), + max_loans_per_user: 5, + loan_duration_days: 14, + } + } + + /// 设置每位用户的最大借阅数量 + pub fn set_max_loans_per_user(&mut self, max: usize) { + self.max_loans_per_user = max; + } + + /// 设置借阅期限(天数) + pub fn set_loan_duration(&mut self, days: i64) { + self.loan_duration_days = days; + } + + /// 添加用户 + pub fn add_user(&mut self, user: User) { + self.users.insert(user.id(), user); + } + + /// 查找用户 + pub fn find_user(&self, user_id: UserId) -> Option<&User> { + self.users.get(&user_id) + } + + /// 查找可变用户引用 + fn find_user_mut(&mut self, user_id: UserId) -> Option<&mut User> { + self.users.get_mut(&user_id) + } + + /// 借阅图书 + pub fn borrow_book( + &mut self, + user_id: UserId, + isbn: &str, + catalog: &mut Catalog + ) -> Result<(), LendingError> { + // 检查用户是否存在 + let user = self.find_user_mut(user_id) + .ok_or(LendingError::UserNotFound(user_id))?; + + // 检查用户是否激活 + if !user.is_active() { + return Err(LendingError::UserInactive(user_id)); + } + + // 检查用户是否达到借阅上限 + if user.borrowed_books().len() >= self.max_loans_per_user { + return Err(LendingError::BorrowLimitReached(user_id)); + } + + // 检查图书是否存在并可借 + let book = catalog.find_by_isbn_mut(isbn) + .ok_or_else(|| LendingError::BookNotFound(isbn.to_string()))?; + + if !book.is_available() { + return Err(LendingError::BookUnavailable(isbn.to_string())); + } + + // 更新图书状态 + book.mark_borrowed(); + + // 更新用户借阅记录 + user.borrow_book(isbn); + + // 创建借阅记录 + let now = chrono::Utc::now(); + let due_date = now + chrono::Duration::days(self.loan_duration_days); + + self.active_loans.push(LoanRecord { + user_id, + isbn: isbn.to_string(), + loan_date: now, + due_date, + }); + + Ok(()) + } + + /// 归还图书 + pub fn return_book( + &mut self, + user_id: UserId, + isbn: &str, + catalog: &mut Catalog + ) -> Result<(), LendingError> { + // 检查用户是否存在 + let user = self.find_user_mut(user_id) + .ok_or(LendingError::UserNotFound(user_id))?; + + // 检查图书是否存在 + let book = catalog.find_by_isbn_mut(isbn) + .ok_or_else(|| LendingError::BookNotFound(isbn.to_string()))?; + + // 更新用户借阅记录 + if !user.return_book(isbn) { + return Err(LendingError::LoanNotFound); + } + + // 更新图书状态 + book.mark_returned(); + + // 移除借阅记录 + let loan_index = self.active_loans.iter() + .position(|loan| loan.user_id == user_id && loan.isbn == isbn) + .ok_or(LendingError::LoanNotFound)?; + + self.active_loans.remove(loan_index); + + Ok(()) + } + + /// 获取用户的所有借阅 + pub fn get_user_loans(&self, user_id: UserId) -> Vec<&LoanRecord> { + self.active_loans.iter() + .filter(|loan| loan.user_id == user_id) + .collect() + } + + /// 获取所有逾期借阅 + pub fn get_overdue_loans(&self) -> Vec<&LoanRecord> { + let now = chrono::Utc::now(); + self.active_loans.iter() + .filter(|loan| loan.due_date < now) + .collect() + } +} + +impl Default for LendingService { + fn default() -> Self { + Self::new() + } +} +``` + +**8. 工具模块 (utils/mod.rs)** + +```rust +//! 内部工具函数 + +pub mod validation; +``` + +**9. 验证工具 (utils/validation.rs)** + +```rust +//! 数据验证工具 + +/// 检查ISBN是否有效 +/// +/// 简化版实现,仅检查基本格式 +pub fn is_valid_isbn(isbn: &str) -> bool { + // 移除所有连字符和空格 + let isbn = isbn.replace(['-', ' '], ""); + + // ISBN-13应为13位数字 + if isbn.len() != 13 { + return false; + } + + // 检查是否全为数字 + isbn.chars().all(|c| c.is_ascii_digit()) +} + +/// 检查邮箱格式是否有效 +pub fn is_valid_email(email: &str) -> bool { + // 简化版邮箱验证 + email.contains('@') && email.split('@').count() == 2 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_valid_isbn() { + assert!(is_valid_isbn("978-1234567890")); + assert!(is_valid_isbn("9781234567890")); + } + + #[test] + fn test_invalid_isbn() { + assert!(!is_valid_isbn("978-12345")); // 太短 + assert!(!is_valid_isbn("978-1234567890X")); // 包含非数字 + } + + #[test] + fn test_valid_email() { + assert!(is_valid_email("user@example.com")); + } + + #[test] + fn test_invalid_email() { + assert!(!is_valid_email("user@")); + assert!(!is_valid_email("user")); + assert!(!is_valid_email("user@domain@example")); + } +} +``` + +### 示例应用程序 + +让我们创建一个简单的命令行应用程序,展示如何使用我们的图书馆管理系统: + +**10. 主程序 (main.rs)** + +```rust +//! 图书馆管理系统示例应用 + +use library::{Book, User, new_library}; + +fn main() { + println!("=== 图书馆管理系统 ==="); + + // 创建图书馆实例 + let mut library = new_library(); + + // 添加图书 + let books = [ + Book::new("Rust编程艺术", "张三", "978-1234567890"), + Book::new("系统设计实践", "李四", "978-0987654321"), + Book::new("数据结构与算法", "王五", "978-5432109876"), + ]; + + for book in &books { + match library.catalog_mut().add_book(book.clone()) { + Ok(_) => println!("添加图书: {}", book), + Err(e) => println!("添加图书失败: {:?}", e), + } + } + + // 添加用户 + let users = [ + User::new(1, "赵六", "zhaoliu@example.com"), + User::new(2, "钱七", "qianqi@example.com"), + ]; + + for user in &users { + library.lending_mut().add_user(user.clone()); + println!("添加用户: {}", user); + } + + // 借阅图书 + let user_id = 1; + let isbn = "978-1234567890"; + + match library.lending_mut().borrow_book(user_id, isbn, library.catalog_mut()) { + Ok(_) => println!("用户 {} 成功借阅图书 {}", user_id, isbn), + Err(e) => println!("借阅失败: {:?}", e), + } + + // 查看用户借阅情况 + if let Some(user) = library.lending().find_user(user_id) { + println!("用户 {} 已借阅的图书:", user.name()); + for isbn in user.borrowed_books() { + if let Some(book) = library.catalog().find_by_isbn(isbn) { + println!(" - {}", book); + } + } + } + + // 归还图书 + match library.lending_mut().return_book(user_id, isbn, library.catalog_mut()) { + Ok(_) => println!("用户 {} 成功归还图书 {}", user_id, isbn), + Err(e) => println!("归还失败: {:?}", e), + } + + // 查看图书馆统计信息 + println!("\n=== 图书馆统计 ==="); + println!("总图书数: {}", library.catalog().total_count()); + println!("可借图书数: {}", library.catalog().available_count()); +} +``` + +### 项目配置 + +**11. 包配置 (Cargo.toml)** + +```toml +[package] +name = "library" +version = "0.1.0" +edition = "2021" +authors = ["Your Name "] +description = "A modular library management system" + +[dependencies] +chrono = "0.4" + +[dev-dependencies] +criterion = "0.5" + +[lib] +name = "library" +path = "src/lib.rs" + +[[bin]] +name = "library-cli" +path = "src/main.rs" +``` + +## 4.7 总结 + +在本章中,我们探索了Rust的模块系统,学习了如何组织和结构化大型项目。我们讨论了以下关键概念: + +1. **模块层级设计**:使用`mod`关键字创建模块树,通过文件系统组织代码 +2. **可见性控制**:使用`pub`及其变体控制API的暴露范围 +3. **工作空间管理**:使用工作空间组织多个相关crate +4. **文档测试**:编写可执行的文档,确保代码示例始终有效 + +通过图书馆管理系统的实例项目,我们实践了这些概念,构建了一个模块化、可维护的系统。这种结构化方法不仅提高了代码的可读性和可维护性,还促进了团队协作和代码重用。 + +### 关键要点回顾 + +- 使用模块组织相关功能,形成清晰的代码结构 +- 遵循"最小公开原则",只暴露必要的API +- 利用文件系统组织模块,使代码结构直观 +- 使用工作空间管理大型项目中的多个crate +- 编写文档测试,确保文档与代码同步 + +### 实践建议 + +1. **模块设计**:先设计模块结构,再编写实现代码 +2. **API设计**:谨慎考虑哪些项应该公开,哪些应该保持私有 +3. **文档优先**:编写文档和测试,然后实现功能 +4. **渐进式重构**:随着项目增长,不断调整模块结构 + +在下一章中,我们将探索Rust的并发安全特性,学习如何利用所有权系统编写高效、安全的并发代码。 \ No newline at end of file diff --git a/examples/ex04/Cargo.lock b/examples/ex04/Cargo.lock new file mode 100644 index 0000000..e7cd192 --- /dev/null +++ b/examples/ex04/Cargo.lock @@ -0,0 +1,297 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "library" +version = "0.1.0" +dependencies = [ + "chrono", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] diff --git a/examples/ex04/Cargo.toml b/examples/ex04/Cargo.toml new file mode 100644 index 0000000..09ee698 --- /dev/null +++ b/examples/ex04/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "library" +version = "0.1.0" +edition = "2021" +authors = ["Rust Book Author "] +description = "A modular library management system" + +[dependencies] +chrono = "0.4" + +[lib] +name = "library" +path = "src/lib.rs" + +[[bin]] +name = "library-cli" +path = "src/main.rs" \ No newline at end of file diff --git a/examples/ex04/src/lib.rs b/examples/ex04/src/lib.rs new file mode 100644 index 0000000..06caec8 --- /dev/null +++ b/examples/ex04/src/lib.rs @@ -0,0 +1,19 @@ +//! # 图书馆管理系统 +//! +//! 一个模块化的图书馆管理系统,提供图书编目、用户管理和借阅服务。 + +pub mod models; +pub mod services; +mod utils; // 内部工具模块,不对外暴露 + +// 重新导出核心类型,简化用户导入 +pub use models::{Book, User}; +pub use services::{catalog::Catalog, lending::LendingService}; + +/// 系统版本号 +pub const VERSION: &str = "0.1.0"; + +/// 创建一个新的图书馆实例 +pub fn new_library() -> services::Library { + services::Library::new() +} \ No newline at end of file diff --git a/examples/ex04/src/main.rs b/examples/ex04/src/main.rs new file mode 100644 index 0000000..78bad9d --- /dev/null +++ b/examples/ex04/src/main.rs @@ -0,0 +1,86 @@ +//! 图书馆管理系统命令行界面 + +use library::{Book, User, new_library}; + +fn main() { + println!("图书馆管理系统 v{}", library::VERSION); + println!("======================"); + + // 创建图书馆实例 + let mut library = new_library(); + + // 添加一些示例图书 + let books = [ + Book::new("Rust编程之道", "张三", "978-1234567890"), + Book::new("数据结构与算法", "李四", "978-2345678901"), + Book::new("计算机网络", "王五", "978-3456789012"), + Book::new("操作系统原理", "赵六", "978-4567890123"), + Book::new("数据库系统概念", "钱七", "978-5678901234"), + ]; + + println!("\n添加图书到目录..."); + for book in &books { + match library.catalog_mut().add_book(book.clone()) { + Ok(_) => println!(" 已添加: {}", book), + Err(e) => println!(" 添加失败: {:?}", e), + } + } + + // 添加一些用户 + let users = [ + User::new(1, "张明", "zhangming@example.com"), + User::new(2, "李华", "lihua@example.com"), + User::new(3, "王芳", "wangfang@example.com"), + ]; + + println!("\n添加用户..."); + for user in &users { + library.lending_mut().add_user(user.clone()); + println!(" 已添加: {}", user); + } + + // 演示借书 + println!("\n借阅图书..."); + let borrow_examples = [ + (1, "978-1234567890"), + (2, "978-2345678901"), + (2, "978-3456789012"), + (3, "978-4567890123"), + ]; + + for (user_id, isbn) in &borrow_examples { + match library.borrow_book(*user_id, isbn) { + Ok(_) => { + let user = library.lending().find_user(*user_id as u32).unwrap(); + let book = library.catalog().find_by_isbn(isbn).unwrap(); + println!(" {} 成功借阅: {}", user.name(), book.title()); + }, + Err(e) => println!(" 借阅失败: {:?}", e), + } + } + + // 显示目录状态 + println!("\n目录状态:"); + println!(" 总图书数: {}", library.catalog().total_count()); + println!(" 可借图书数: {}", library.catalog().available_count()); + + // 演示查找图书 + println!("\n查找图书 (标题包含'数据'):"); + for book in library.catalog().find_by_title("数据") { + println!(" 找到: {} ({})", book.title(), if book.is_available() { "可借" } else { "已借出" }); + } + + // 演示归还图书 + println!("\n归还图书..."); + match library.return_book(2, "978-2345678901") { + Ok(_) => println!(" 李华成功归还《数据结构与算法》"), + Err(e) => println!(" 归还失败: {:?}", e), + } + + // 再次显示目录状态 + println!("\n更新后的目录状态:"); + println!(" 总图书数: {}", library.catalog().total_count()); + println!(" 可借图书数: {}", library.catalog().available_count()); + + println!("\n程序结束"); +} \ No newline at end of file diff --git a/examples/ex04/src/models/book.rs b/examples/ex04/src/models/book.rs new file mode 100644 index 0000000..26ab456 --- /dev/null +++ b/examples/ex04/src/models/book.rs @@ -0,0 +1,68 @@ +//! 图书相关的数据模型 + +use std::fmt; + +/// 表示图书馆中的一本书 +#[derive(Debug, Clone, PartialEq)] +pub struct Book { + title: String, + author: String, + isbn: String, + available: bool, +} + +impl Book { + /// 创建一本新书 + /// + /// # 示例 + /// ``` + /// use library::Book; + /// let book = Book::new("Rust编程艺术", "张三", "978-1234567890"); + /// assert_eq!(book.title(), "Rust编程艺术"); + /// assert!(book.is_available()); + /// ``` + pub fn new(title: &str, author: &str, isbn: &str) -> Self { + Self { + title: title.to_string(), + author: author.to_string(), + isbn: isbn.to_string(), + available: true, + } + } + + /// 获取书名 + pub fn title(&self) -> &str { + &self.title + } + + /// 获取作者 + pub fn author(&self) -> &str { + &self.author + } + + /// 获取ISBN + pub fn isbn(&self) -> &str { + &self.isbn + } + + /// 检查书是否可借 + pub fn is_available(&self) -> bool { + self.available + } + + /// 标记为已借出 + pub(crate) fn mark_borrowed(&mut self) { + self.available = false; + } + + /// 标记为已归还 + pub(crate) fn mark_returned(&mut self) { + self.available = true; + } +} + +impl fmt::Display for Book { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} by {} (ISBN: {})", self.title, self.author, self.isbn) + } +} \ No newline at end of file diff --git a/examples/ex04/src/models/mod.rs b/examples/ex04/src/models/mod.rs new file mode 100644 index 0000000..c54bb85 --- /dev/null +++ b/examples/ex04/src/models/mod.rs @@ -0,0 +1,7 @@ +//! 系统中使用的数据模型 + +pub mod book; +pub mod user; + +pub use book::Book; +pub use user::User; \ No newline at end of file diff --git a/examples/ex04/src/models/user.rs b/examples/ex04/src/models/user.rs new file mode 100644 index 0000000..51b7ccf --- /dev/null +++ b/examples/ex04/src/models/user.rs @@ -0,0 +1,93 @@ +//! 用户相关的数据模型 + +use std::fmt; + +/// 用户ID类型 +pub type UserId = u32; + +/// 表示图书馆的一名用户 +#[derive(Debug, Clone, PartialEq)] +pub struct User { + id: UserId, + name: String, + email: String, + active: bool, + borrowed_books: Vec, // 存储已借阅书籍的ISBN +} + +impl User { + /// 创建一个新用户 + /// + /// # 示例 + /// ``` + /// use library::User; + /// let user = User::new(1, "李四", "lisi@example.com"); + /// assert_eq!(user.name(), "李四"); + /// assert!(user.is_active()); + /// ``` + pub fn new(id: UserId, name: &str, email: &str) -> Self { + Self { + id, + name: name.to_string(), + email: email.to_string(), + active: true, + borrowed_books: Vec::new(), + } + } + + /// 获取用户ID + pub fn id(&self) -> UserId { + self.id + } + + /// 获取用户名 + pub fn name(&self) -> &str { + &self.name + } + + /// 获取用户邮箱 + pub fn email(&self) -> &str { + &self.email + } + + /// 检查用户是否激活 + pub fn is_active(&self) -> bool { + self.active + } + + /// 获取用户已借阅的书籍ISBN列表 + pub fn borrowed_books(&self) -> &[String] { + &self.borrowed_books + } + + /// 记录用户借阅了一本书 + pub(crate) fn borrow_book(&mut self, isbn: &str) { + self.borrowed_books.push(isbn.to_string()); + } + + /// 记录用户归还了一本书 + pub(crate) fn return_book(&mut self, isbn: &str) -> bool { + if let Some(pos) = self.borrowed_books.iter().position(|b| b == isbn) { + self.borrowed_books.remove(pos); + true + } else { + false + } + } + + /// 停用用户账户 + pub(crate) fn deactivate(&mut self) { + self.active = false; + } + + /// 重新激活用户账户 + pub(crate) fn activate(&mut self) { + self.active = true; + } +} + +impl fmt::Display for User { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} (ID: {}, Email: {})", self.name, self.id, self.email) + } +} \ No newline at end of file diff --git a/examples/ex04/src/services/catalog.rs b/examples/ex04/src/services/catalog.rs new file mode 100644 index 0000000..b2c688d --- /dev/null +++ b/examples/ex04/src/services/catalog.rs @@ -0,0 +1,113 @@ +//! 图书目录管理服务 + +use std::collections::HashMap; +use crate::models::Book; +use crate::utils::validation; + +/// 图书目录错误类型 +#[derive(Debug)] +pub enum CatalogError { + /// ISBN已存在 + DuplicateIsbn(String), + /// ISBN不存在 + IsbnNotFound(String), + /// ISBN格式无效 + InvalidIsbn(String), +} + +/// 图书目录服务 +pub struct Catalog { + books: HashMap, +} + +impl Catalog { + /// 创建一个新的目录服务 + pub fn new() -> Self { + Self { + books: HashMap::new(), + } + } + + /// 添加一本新书到目录 + /// + /// # 错误 + /// 如果ISBN已存在或无效,返回错误 + /// + /// # 示例 + /// ``` + /// use library::{Book, Catalog}; + /// + /// let mut catalog = Catalog::new(); + /// let book = Book::new("Rust编程", "张三", "978-1234567890"); + /// + /// // 首次添加成功 + /// assert!(catalog.add_book(book.clone()).is_ok()); + /// + /// // 重复添加失败 + /// assert!(catalog.add_book(book).is_err()); + /// ``` + pub fn add_book(&mut self, book: Book) -> Result<(), CatalogError> { + let isbn = book.isbn(); + + // 验证ISBN格式 + if !validation::is_valid_isbn(isbn) { + return Err(CatalogError::InvalidIsbn(isbn.to_string())); + } + + // 检查ISBN是否已存在 + if self.books.contains_key(isbn) { + return Err(CatalogError::DuplicateIsbn(isbn.to_string())); + } + + // 添加图书 + self.books.insert(isbn.to_string(), book); + Ok(()) + } + + /// 根据ISBN查找图书 + pub fn find_by_isbn(&self, isbn: &str) -> Option<&Book> { + self.books.get(isbn) + } + + /// 根据ISBN查找可变图书引用 + pub(crate) fn find_by_isbn_mut(&mut self, isbn: &str) -> Option<&mut Book> { + self.books.get_mut(isbn) + } + + /// 根据标题查找图书 + pub fn find_by_title(&self, title: &str) -> Vec<&Book> { + self.books.values() + .filter(|book| book.title().contains(title)) + .collect() + } + + /// 根据作者查找图书 + pub fn find_by_author(&self, author: &str) -> Vec<&Book> { + self.books.values() + .filter(|book| book.author().contains(author)) + .collect() + } + + /// 获取所有图书 + pub fn all_books(&self) -> Vec<&Book> { + self.books.values().collect() + } + + /// 获取可用图书数量 + pub fn available_count(&self) -> usize { + self.books.values() + .filter(|book| book.is_available()) + .count() + } + + /// 获取总图书数量 + pub fn total_count(&self) -> usize { + self.books.len() + } +} + +impl Default for Catalog { + fn default() -> Self { + Self::new() + } +} \ No newline at end of file diff --git a/examples/ex04/src/services/lending.rs b/examples/ex04/src/services/lending.rs new file mode 100644 index 0000000..b40b223 --- /dev/null +++ b/examples/ex04/src/services/lending.rs @@ -0,0 +1,183 @@ +//! 图书借阅管理服务 + +use std::collections::HashMap; +use crate::models::User; +use crate::models::user::UserId; +use crate::services::catalog::Catalog; + +/// 借阅记录 +#[derive(Debug)] +pub struct LoanRecord { + user_id: UserId, + isbn: String, + loan_date: chrono::DateTime, + due_date: chrono::DateTime, +} + +/// 借阅错误类型 +#[derive(Debug)] +pub enum LendingError { + /// 用户不存在 + UserNotFound(UserId), + /// 图书不存在 + BookNotFound(String), + /// 图书不可用(已借出) + BookUnavailable(String), + /// 用户账户未激活 + UserInactive(UserId), + /// 用户已达到最大借阅限制 + BorrowLimitReached(UserId), + /// 借阅记录不存在 + LoanNotFound, +} + +/// 借阅服务 +pub struct LendingService { + users: HashMap, + active_loans: Vec, + max_loans_per_user: usize, + loan_duration_days: i64, +} + +impl LendingService { + /// 创建一个新的借阅服务 + pub fn new() -> Self { + Self { + users: HashMap::new(), + active_loans: Vec::new(), + max_loans_per_user: 5, + loan_duration_days: 14, + } + } + + /// 设置每位用户的最大借阅数量 + pub fn set_max_loans_per_user(&mut self, max: usize) { + self.max_loans_per_user = max; + } + + /// 设置借阅期限(天数) + pub fn set_loan_duration(&mut self, days: i64) { + self.loan_duration_days = days; + } + + /// 添加用户 + pub fn add_user(&mut self, user: User) { + self.users.insert(user.id(), user); + } + + /// 查找用户 + pub fn find_user(&self, user_id: UserId) -> Option<&User> { + self.users.get(&user_id) + } + + /// 查找可变用户引用 + fn find_user_mut(&mut self, user_id: UserId) -> Option<&mut User> { + self.users.get_mut(&user_id) + } + + /// 借阅图书 + pub fn borrow_book( + &mut self, + user_id: UserId, + isbn: &str, + catalog: &mut Catalog + ) -> Result<(), LendingError> { + // 先获取最大借阅数量 + let max_loans = self.max_loans_per_user; + + // 检查用户是否存在 + let user = self.find_user_mut(user_id) + .ok_or(LendingError::UserNotFound(user_id))?; + + // 检查用户是否激活 + if !user.is_active() { + return Err(LendingError::UserInactive(user_id)); + } + + // 检查用户是否达到借阅上限 + if user.borrowed_books().len() >= max_loans { + return Err(LendingError::BorrowLimitReached(user_id)); + } + + // 检查图书是否存在并可借 + let book = catalog.find_by_isbn_mut(isbn) + .ok_or_else(|| LendingError::BookNotFound(isbn.to_string()))?; + + if !book.is_available() { + return Err(LendingError::BookUnavailable(isbn.to_string())); + } + + // 更新图书状态 + book.mark_borrowed(); + + // 更新用户借阅记录 + user.borrow_book(isbn); + + // 创建借阅记录 + let now = chrono::Utc::now(); + let due_date = now + chrono::Duration::days(self.loan_duration_days); + + self.active_loans.push(LoanRecord { + user_id, + isbn: isbn.to_string(), + loan_date: now, + due_date, + }); + + Ok(()) + } + + /// 归还图书 + pub fn return_book( + &mut self, + user_id: UserId, + isbn: &str, + catalog: &mut Catalog + ) -> Result<(), LendingError> { + // 检查用户是否存在 + let user = self.find_user_mut(user_id) + .ok_or(LendingError::UserNotFound(user_id))?; + + // 检查图书是否存在 + let book = catalog.find_by_isbn_mut(isbn) + .ok_or_else(|| LendingError::BookNotFound(isbn.to_string()))?; + + // 更新用户借阅记录 + if !user.return_book(isbn) { + return Err(LendingError::LoanNotFound); + } + + // 更新图书状态 + book.mark_returned(); + + // 移除借阅记录 + let loan_index = self.active_loans.iter() + .position(|loan| loan.user_id == user_id && loan.isbn == isbn) + .ok_or(LendingError::LoanNotFound)?; + + self.active_loans.remove(loan_index); + + Ok(()) + } + + /// 获取用户的所有借阅 + pub fn get_user_loans(&self, user_id: UserId) -> Vec<&LoanRecord> { + self.active_loans.iter() + .filter(|loan| loan.user_id == user_id) + .collect() + } + + /// 获取所有逾期借阅 + pub fn get_overdue_loans(&self) -> Vec<&LoanRecord> { + let now = chrono::Utc::now(); + self.active_loans.iter() + .filter(|loan| loan.due_date < now) + .collect() + } +} + +impl Default for LendingService { + fn default() -> Self { + Self::new() + } +} \ No newline at end of file diff --git a/examples/ex04/src/services/mod.rs b/examples/ex04/src/services/mod.rs new file mode 100644 index 0000000..549f175 --- /dev/null +++ b/examples/ex04/src/services/mod.rs @@ -0,0 +1,58 @@ +//! 图书馆业务服务模块 + +pub mod catalog; +pub mod lending; + +// 不需要在这里导入,因为每个子模块都有自己的导入 + +/// 图书馆主服务 +pub struct Library { + catalog: catalog::Catalog, + lending: lending::LendingService, +} + +impl Library { + /// 创建一个新的图书馆实例 + pub fn new() -> Self { + Self { + catalog: catalog::Catalog::new(), + lending: lending::LendingService::new(), + } + } + + /// 获取目录服务 + pub fn catalog(&self) -> &catalog::Catalog { + &self.catalog + } + + /// 获取可变目录服务 + pub fn catalog_mut(&mut self) -> &mut catalog::Catalog { + &mut self.catalog + } + + /// 获取借阅服务 + pub fn lending(&self) -> &lending::LendingService { + &self.lending + } + + /// 获取可变借阅服务 + pub fn lending_mut(&mut self) -> &mut lending::LendingService { + &mut self.lending + } + + /// 借阅图书 + pub fn borrow_book(&mut self, user_id: usize, isbn: &str) -> Result<(), lending::LendingError> { + self.lending.borrow_book(user_id as u32, isbn, &mut self.catalog) + } + + /// 归还图书 + pub fn return_book(&mut self, user_id: usize, isbn: &str) -> Result<(), lending::LendingError> { + self.lending.return_book(user_id as u32, isbn, &mut self.catalog) + } +} + +impl Default for Library { + fn default() -> Self { + Self::new() + } +} \ No newline at end of file diff --git a/examples/ex04/src/utils/mod.rs b/examples/ex04/src/utils/mod.rs new file mode 100644 index 0000000..33bd2fa --- /dev/null +++ b/examples/ex04/src/utils/mod.rs @@ -0,0 +1,3 @@ +//! 内部工具函数 + +pub mod validation; \ No newline at end of file diff --git a/examples/ex04/src/utils/validation.rs b/examples/ex04/src/utils/validation.rs new file mode 100644 index 0000000..3924f1a --- /dev/null +++ b/examples/ex04/src/utils/validation.rs @@ -0,0 +1,80 @@ +//! 数据验证工具 + +/// 检查ISBN是否有效 +/// +/// 简化的ISBN-13验证,仅检查格式和长度 +/// +/// # 示例 +/// ``` +/// use library::utils::validation::is_valid_isbn; +/// +/// assert!(is_valid_isbn("978-1234567890")); +/// assert!(!is_valid_isbn("invalid-isbn")); +/// ``` +pub fn is_valid_isbn(isbn: &str) -> bool { + // 移除所有连字符 + let cleaned = isbn.replace('-', ""); + + // 检查长度(ISBN-13应为13位数字) + if cleaned.len() != 13 { + return false; + } + + // 检查是否全为数字 + cleaned.chars().all(|c| c.is_ascii_digit()) +} + +/// 检查电子邮件格式是否有效 +/// +/// 简化的电子邮件验证,仅检查基本格式 +pub fn is_valid_email(email: &str) -> bool { + // 检查是否包含@符号 + if !email.contains('@') { + return false; + } + + // 检查@符号前后是否有内容 + let parts: Vec<&str> = email.split('@').collect(); + if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() { + return false; + } + + // 检查域名部分是否包含至少一个点 + if !parts[1].contains('.') { + return false; + } + + true +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_valid_isbn() { + assert!(is_valid_isbn("9781234567890")); + assert!(is_valid_isbn("978-1234567890")); + } + + #[test] + fn test_invalid_isbn() { + assert!(!is_valid_isbn("123")); + assert!(!is_valid_isbn("abcdefghijklm")); + assert!(!is_valid_isbn("978-12345")); + } + + #[test] + fn test_valid_email() { + assert!(is_valid_email("user@example.com")); + assert!(is_valid_email("name.surname@domain.co.uk")); + } + + #[test] + fn test_invalid_email() { + assert!(!is_valid_email("invalid")); + assert!(!is_valid_email("@domain.com")); + assert!(!is_valid_email("user@")); + assert!(!is_valid_email("user@domain")); + } +} \ No newline at end of file