概述
在23种经典设计模式背后,隐藏着六大核心原则。这些原则不仅在面向对象编程中至关重要,在 Rust 的系统编程中同样闪耀着智慧的光芒。Rust 的所有权系统、trait 体系和零成本抽象特性,使得这些原则能够以更安全、更高效的方式体现。
六大设计原则概览
- 单一职责原则 (SRP) - 一个类只负责一项职责
- 开闭原则 (OCP) - 对扩展开放,对修改关闭
- 里氏替换原则 (LSP) - 子类必须能够替换父类
- 接口隔离原则 (ISP) - 不应该依赖不需要的接口
- 依赖倒置原则 (DIP) - 面向接口编程,不面向具体
- 迪米特法则 (LoD) - 最少知识原则
单一职责原则 (SRP)
核心概念
一个类/模块应该仅有一个引起它变化的原因。在 Rust 中,我们通过模块划分、trait 分离来体现这一原则。
Rust 实践
// ❌ 违反单一职责原则:一个结构体承担过多职责
struct BadService {
user_data: UserData,
database: DatabaseConnection,
email_client: EmailClient,
file_logger: FileLogger,
}
impl BadService {
fn create_user(&mut self, user: User) -> Result<(), Error> { /* ... */ }
fn send_email(&self, to: &str, content: &str) -> Result<(), Error> { /* ... */ }
fn log_message(&self, message: &str) { /* ... */ }
fn backup_data(&self) -> Result<(), Error> { /* ... */ }
}
// ✅ 遵循单一职责原则:每个模块专注于自己的领域
struct UserService {
database: DatabaseConnection,
}
impl UserService {
fn create_user(&mut self, user: User) -> Result<(), Error> {
// 只负责用户管理
self.database.insert(user)
}
}
struct EmailService {
client: EmailClient,
}
impl EmailService {
fn send_email(&self, to: &str, content: &str) -> Result<(), Error> {
// 只负责邮件发送
self.client.send(to, content)
}
}
struct Logger {
writer: FileLogger,
}
impl Logger {
fn log(&self, message: &str) {
// 只负责日志记录
self.writer.write(message)
}
}
Rust 特有优势
- Trait 分离:通过 trait 定义最小功能集合
- 模块系统:Rust 的模块系统天然支持职责分离
- 所有权清晰:明确的数据归属有助于职责划分
开闭原则 (OCP)
核心概念
软件实体应该对扩展开放,对修改关闭。通过抽象和多态实现,无需修改现有代码就能扩展功能。
Rust 实践
// 定义行为抽象
trait Notification {
fn send(&self, message: &str) -> Result<(), Error>;
}
// 具体实现
struct EmailNotification {
smtp_server: String,
}
impl Notification for EmailNotification {
fn send(&self, message: &str) -> Result<(), Error> {
println!("Sending email via {}: {}", self.smtp_server, message);
Ok(())
}
}
struct SMSNotification {
gateway: String,
}
impl Notification for SMSNotification {
fn send(&self, message: &str) -> Result<(), Error> {
println!("Sending SMS via {}: {}", self.gateway, message);
Ok(())
}
}
struct SlackNotification {
webhook_url: String,
}
impl Notification for SlackNotification {
fn send(&self, message: &str) -> Result<(), Error> {
println!("Sending Slack via webhook: {}", message);
Ok(())
}
}
// 使用 trait 对象,支持运行时扩展
struct NotificationService {
channels: Vec<Box<dyn Notification>>,
}
impl NotificationService {
fn new() -> Self {
Self { channels: Vec::new() }
}
fn add_channel(&mut self, channel: Box<dyn Notification>) {
self.channels.push(channel);
}
fn notify_all(&self, message: &str) -> Result<(), Error> {
for channel in &self.channels {
channel.send(message)?;
}
Ok(())
}
}
// 使用示例
fn main() -> Result<(), Error> {
let mut service = NotificationService::new();
// 扩展新的通知方式,无需修改现有代码
service.add_channel(Box::new(EmailNotification {
smtp_server: "smtp.example.com".to_string(),
}));
service.add_channel(Box::new(SMSNotification {
gateway: "twilio".to_string(),
}));
service.notify_all("Hello, Design Patterns!")?;
Ok(())
}
Rust 泛型实现
// 泛型实现:编译期多态
struct NotificationService<T: Notification> {
channel: T,
}
impl<T: Notification> NotificationService<T> {
fn new(channel: T) -> Self {
Self { channel }
}
fn notify(&self, message: &str) -> Result<(), Error> {
self.channel.send(message)
}
}
// 零成本抽象,没有运行时开销
里氏替换原则 (LSP)
核心概念
子类必须能够替换父类而不影响程序的正确性。在 Rust 中,trait 确保了实现的一致性。
Rust 实践
// 定义矩形
trait Rectangle {
fn area(&self) -> f64;
fn set_width(&mut self, width: f64);
fn set_height(&mut self, height: f64);
}
struct StandardRectangle {
width: f64,
height: f64,
}
impl Rectangle for StandardRectangle {
fn area(&self) -> f64 {
self.width * self.height
}
fn set_width(&mut self, width: f64) {
self.width = width;
}
fn set_height(&mut self, height: f64) {
self.height = height;
}
}
// ❌ 违反里氏替换原则的正方形
struct BadSquare {
side: f64,
}
impl Rectangle for BadSquare {
fn area(&self) -> f64 {
self.side * self.side
}
fn set_width(&mut self, width: f64) {
self.side = width; // 破坏了正方形的性质
}
fn set_height(&mut self, height: f64) {
self.side = height; // 破坏了正方形的性质
}
}
// ✅ 正确的设计:正方形和矩形是独立的概念
struct Square {
side: f64,
}
impl Square {
fn new(side: f64) -> Self {
Self { side }
}
fn area(&self) -> f64 {
self.side * self.side
}
fn set_side(&mut self, side: f64) {
self.side = side;
}
}
// 使用 trait 确保一致性
trait Shape {
fn area(&self) -> f64;
}
impl Shape for StandardRectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
impl Shape for Square {
fn area(&self) -> f64 {
self.side * self.side
}
}
// 现在可以安全地替换
fn calculate_total_area(shapes: &[Box<dyn Shape>]) -> f64 {
shapes.iter().map(|s| s.area()).sum()
}
接口隔离原则 (ISP)
核心概念
客户端不应该依赖它不需要的接口。应该使用多个小接口而不是一个胖接口。
Rust 实践
// ❌ 胖接口:包含不相关的方法
trait DocumentProcessor {
fn read(&self) -> String;
fn write(&self, content: &str) -> Result<(), Error>;
fn parse(&self, content: &str) -> Vec<String>;
fn format(&self, content: &str) -> String;
fn validate(&self, content: &str) -> bool;
}
// ✅ 接口隔离:拆分为多个小 trait
trait Readable {
fn read(&self) -> String;
}
trait Writable {
fn write(&self, content: &str) -> Result<(), Error>;
}
trait Parsable {
fn parse(&self, content: &str) -> Vec<String>;
}
trait Formattable {
fn format(&self, content: &str) -> String;
}
trait Validatable {
fn validate(&self, content: &str) -> bool;
}
// 文本编辑器:只需要读写
struct TextEditor {
content: String,
}
impl Readable for TextEditor {
fn read(&self) -> String {
self.content.clone()
}
}
impl Writable for TextEditor {
fn write(&mut self, content: &str) -> Result<(), Error> {
self.content = content.to_string();
Ok(())
}
}
// 文档解析器:只需要解析和验证
struct DocumentParser {
rules: Vec<String>,
}
impl Parsable for DocumentParser {
fn parse(&self, content: &str) -> Vec<String> {
content.lines().map(|s| s.to_string()).collect()
}
}
impl Validatable for DocumentParser {
fn validate(&self, content: &str) -> bool {
!content.is_empty()
}
}
// 组合使用:按需实现
struct AdvancedEditor {
content: String,
parser: DocumentParser,
}
impl Readable for AdvancedEditor {
fn read(&self) -> String {
self.content.clone()
}
}
impl Writable for AdvancedEditor {
fn write(&mut self, content: &str) -> Result<(), Error> {
self.content = content.to_string();
Ok(())
}
}
impl Parsable for AdvancedEditor {
fn parse(&self, content: &str) -> Vec<String> {
self.parser.parse(content)
}
}
impl Validatable for AdvancedEditor {
fn validate(&self, content: &str) -> bool {
self.parser.validate(content)
}
}
依赖倒置原则 (DIP)
核心概念
高层模块不应该依赖低层模块,两者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。
Rust 实践
// 定义抽象:依赖接口,不依赖具体实现
trait Database {
fn save(&self, data: &str) -> Result<(), Error>;
fn load(&self, id: &str) -> Result<String, Error>;
}
trait Logger {
fn log(&self, message: &str);
}
// 低层模块:实现抽象
struct MySQLDatabase {
connection_string: String,
}
impl Database for MySQLDatabase {
fn save(&self, data: &str) -> Result<(), Error> {
println!("Saving to MySQL: {}", data);
Ok(())
}
fn load(&self, id: &str) -> Result<String, Error> {
println!("Loading from MySQL: {}", id);
Ok(format!("Data for {}", id))
}
}
struct PostgresDatabase {
connection_string: String,
}
impl Database for PostgresDatabase {
fn save(&self, data: &str) -> Result<(), Error> {
println!("Saving to PostgreSQL: {}", data);
Ok(())
}
fn load(&self, id: &str) -> Result<String, Error> {
println!("Loading from PostgreSQL: {}", id);
Ok(format!("Data for {}", id))
}
}
struct ConsoleLogger;
impl Logger for ConsoleLogger {
fn log(&self, message: &str) {
println!("[LOG] {}", message);
}
}
// 高层模块:依赖抽象
struct UserService<D: Database, L: Logger> {
database: D,
logger: L,
}
impl<D: Database, L: Logger> UserService<D, L> {
fn new(database: D, logger: L) -> Self {
Self { database, logger }
}
fn create_user(&self, user_data: &str) -> Result<(), Error> {
self.logger.log(&format!("Creating user: {}", user_data));
self.database.save(user_data)
}
fn get_user(&self, id: &str) -> Result<String, Error> {
self.logger.log(&format!("Fetching user: {}", id));
self.database.load(id)
}
}
// 使用示例:依赖注入
fn main() -> Result<(), Error> {
// 可以轻松替换具体实现,无需修改高层模块
let mysql_service = UserService::new(
MySQLDatabase {
connection_string: "mysql://localhost".to_string(),
},
ConsoleLogger,
);
mysql_service.create_user("Alice")?;
let postgres_service = UserService::new(
PostgresDatabase {
connection_string: "postgresql://localhost".to_string(),
},
ConsoleLogger,
);
postgres_service.create_user("Bob")?;
Ok(())
}
迪米特法则 (LoD)
核心概念
一个对象应该对其他对象保持最少的了解。只与直接的朋友通信。
Rust 实践
// ❌ 违反迪米特法则:过度耦合
struct Engine {
cylinders: u8,
}
impl Engine {
fn start(&self) {
println!("Engine with {} cylinders starting", self.cylinders);
}
}
struct Transmission {
gears: u8,
}
impl Transmission {
fn shift(&self, gear: u8) {
println!("Shifting to gear {}", gear);
}
}
struct CarBad {
engine: Engine,
transmission: Transmission,
}
impl CarBad {
fn start_and_drive_bad(&self) {
// 直接访问内部对象的内部细节
self.engine.start();
if self.engine.cylinders > 4 {
self.transmission.shift(2);
} else {
self.transmission.shift(1);
}
}
}
// ✅ 遵循迪米特法则:封装细节
struct Engine {
cylinders: u8,
}
impl Engine {
fn new(cylinders: u8) -> Self {
Self { cylinders }
}
fn start(&self) -> bool {
println!("Engine with {} cylinders starting", self.cylinders);
true
}
fn is_high_performance(&self) -> bool {
self.cylinders > 4
}
}
struct Transmission {
gears: u8,
}
impl Transmission {
fn new(gears: u8) -> Self {
Self { gears }
}
fn shift(&self, gear: u8) {
println!("Shifting to gear {}", gear);
}
}
struct CarGood {
engine: Engine,
transmission: Transmission,
}
impl CarGood {
fn new(engine: Engine, transmission: Transmission) -> Self {
Self { engine, transmission }
}
// 高层抽象,隐藏内部细节
fn start(&self) -> Result<(), Error> {
if self.engine.start() {
Ok(())
} else {
Err("Engine failed to start".into())
}
}
fn drive(&self) {
// 封装内部逻辑,外部不需要知道具体的判断规则
let gear = if self.engine.is_high_performance() { 2 } else { 1 };
self.transmission.shift(gear);
}
// 统一的启动和驾驶接口
fn start_and_drive(&self) -> Result<(), Error> {
self.start()?;
self.drive();
Ok(())
}
}
// 使用示例
fn main() -> Result<(), Error> {
let car = CarGood::new(
Engine::new(8), // V8 发动机
Transmission::new(6),
);
car.start_and_drive()?;
Ok(())
}
Rust 与设计原则的独特结合
1. 所有权系统的自然约束
trait MessageHandler {
fn handle(&mut self, message: Message);
}
// 所有权自动确保单一职责
struct EmailHandler;
impl MessageHandler for EmailHandler {
fn handle(&mut self, message: Message) {
// 只能处理邮件,编译器强制约束
}
}
struct SMSHandler;
impl MessageHandler for SMSHandler {
fn handle(&mut self, message: Message) {
// 只能处理短信,编译器强制约束
}
}
2. Trait 对象的运行时多态
// 运行时灵活组合
pub struct PluginManager {
plugins: Vec<Box<dyn Plugin>>,
}
impl PluginManager {
pub fn register(&mut self, plugin: Box<dyn Plugin>) {
self.plugins.push(plugin);
}
pub fn execute_all(&mut self, context: &mut Context) {
for plugin in &mut self.plugins {
plugin.execute(context);
}
}
}
3. 零成本抽象的编译期优化
// 编译期多态:无运行时开销
pub struct Service<T: Repository> {
repository: T,
}
impl<T: Repository> Service<T> {
pub fn new(repository: T) -> Self {
Self { repository }
}
pub fn process(&mut self) -> Result<(), Error> {
let data = self.repository.find()?;
self.repository.save(data)
}
}
23种设计模式中的原则体现
创建型模式
| 模式 | 主要原则 | Rust 特性 |
|---|---|---|
| 单例模式 | 单一职责原则 | lazy_static, once_cell |
| 工厂方法 | 开闭原则 | trait + 关联类型 |
| 抽象工厂 | 依赖倒置原则 | trait 对象 |
| 建造者模式 | 单一职责原则 | builder 模式 + 所有权 |
| 原型模式 | 开闭原则 | Clone trait |
结构型模式
| 模式 | 主要原则 | Rust 特性 |
|---|---|---|
| 适配器模式 | 接口隔离原则 | trait 实现 |
| 桥接模式 | 依赖倒置原则 | trait 组合 |
| 组合模式 | 里氏替换原则 | 递归 trait 对象 |
| 装饰器模式 | 开闭原则 | trait 包装器 |
| 外观模式 | 迪米特法则 | 结构体封装 |
| 享元模式 | 单一职责原则 | Arc, Rc |
| 代理模式 | 单一职责原则 | Deref, DerefMut |
行为型模式
| 模式 | 主要原则 | Rust 特性 |
|---|---|---|
| 策略模式 | 开闭原则 | trait 对象 |
| 模板方法 | 里氏替换原则 | trait 默认实现 |
| 观察者模式 | 依赖倒置原则 | Arc<Mutex<>> + 闭包 |
| 迭代器模式 | 单一职责原则 | Iterator trait |
| 责任链模式 | 单一职责原则 | Option 链 |
| 命令模式 | 单一职责原则 | 闭包 + trait 对象 |
| 备忘录模式 | 单一职责原则 | Clone trait |
| 状态模式 | 开闭原则 | trait 对象 |
| 访问者模式 | 开闭原则 | trait 对象 |
| 中介者模式 | 迪米特法则 | 结构体封装 |
| 解释器模式 | 单一职责原则 | trait 组合 |
实践建议
1. 优先使用组合而非继承
Rust 没有继承,鼓励组合:
// ✅ 组合优于"继承"
struct Parser {
lexer: Box<dyn Lexer>,
ast_builder: Box<dyn AstBuilder>,
validator: Box<dyn Validator>,
}
impl Parser {
fn parse(&self, input: &str) -> Result<Ast, Error> {
let tokens = self.lexer.tokenize(input)?;
let ast = self.ast_builder.build(tokens)?;
self.validator.validate(&ast)?;
Ok(ast)
}
}
2. 合理使用 trait 边界
// 清晰的 trait 边界有助于遵循原则
fn process_data<T>(data: T)
where
T: Serialize + Send + Sync + 'static
{
// 编译器确保所有约束
}
3. 模块化设计
src/
├── lib.rs # 公共 API,遵循接口隔离
├── core/ # 核心逻辑,单一职责
├── storage/ # 存储抽象,依赖倒置
├── network/ # 网络层,独立模块
└── utils/ # 工具函数,职责明确
总结
六大设计原则在 Rust 中得到了更安全、更优雅的体现:
- 单一职责原则:通过模块系统和 trait 自然实现
- 开闭原则:利用 trait 和泛型实现零成本抽象
- 里氏替换原则:trait 体系确保类型安全
- 接口隔离原则:小而精的 trait 设计
- 依赖倒置原则:trait 作为依赖的抽象
- 迪米特法则:所有权和可见性控制系统
Rust 的类型系统、所有权模型和 trait 体系,使得在编译期就能验证这些原则的遵循情况,大大减少了运行时错误。这正是 Rust 让人”爱上编译错误”的原因——编译器不仅是检查器,更是设计导师。
参考资源
- The Rust Programming Language
- Rust Design Patterns
- API Guidelines
- Clean Architecture - Robert C. Martin
Rust 的设计哲学与经典设计原则的完美融合,让系统编程变得更加安全和优雅。