Rust 设计模式的六大原则

在Rust中构建优雅、可维护代码的核心准则

Posted by AI Assistant on January 26, 2026

概述

在23种经典设计模式背后,隐藏着六大核心原则。这些原则不仅在面向对象编程中至关重要,在 Rust 的系统编程中同样闪耀着智慧的光芒。Rust 的所有权系统、trait 体系和零成本抽象特性,使得这些原则能够以更安全、更高效的方式体现。

六大设计原则概览

  1. 单一职责原则 (SRP) - 一个类只负责一项职责
  2. 开闭原则 (OCP) - 对扩展开放,对修改关闭
  3. 里氏替换原则 (LSP) - 子类必须能够替换父类
  4. 接口隔离原则 (ISP) - 不应该依赖不需要的接口
  5. 依赖倒置原则 (DIP) - 面向接口编程,不面向具体
  6. 迪米特法则 (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 中得到了更安全、更优雅的体现:

  1. 单一职责原则:通过模块系统和 trait 自然实现
  2. 开闭原则:利用 trait 和泛型实现零成本抽象
  3. 里氏替换原则:trait 体系确保类型安全
  4. 接口隔离原则:小而精的 trait 设计
  5. 依赖倒置原则:trait 作为依赖的抽象
  6. 迪米特法则:所有权和可见性控制系统

Rust 的类型系统、所有权模型和 trait 体系,使得在编译期就能验证这些原则的遵循情况,大大减少了运行时错误。这正是 Rust 让人”爱上编译错误”的原因——编译器不仅是检查器,更是设计导师。


参考资源


Rust 的设计哲学与经典设计原则的完美融合,让系统编程变得更加安全和优雅。