Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

08_适配器模式 Adapter Pattern

1. 基本信息

中文名称: 适配器模式

英文名称: Adapter Pattern

模式类型: 结构型设计模式

Rust 中常见实现方式: trait 实现适配、包装结构体(wrapper)、From/Into trait、Deref trait

适配器模式的核心是:将一个接口转换成调用方期望的接口,使原本不兼容的接口能够协同工作。

在 Rust 中,适配器模式通常通过 包装结构体 + trait 实现,或者直接利用标准库的 From/Into trait 做类型转换。

代码示例


2. 模式核心思想

适配器模式的核心思想是:

通过包装或转换,把不兼容的接口变成调用方可以使用的接口,而无需修改原始类型。

换句话说:

  • 调用方使用统一接口
  • 被适配对象不需要修改
  • 适配器负责转换或适配行为

3. 这个模式解决什么问题

在实际开发中,适配器模式解决以下问题:

系统中存在老旧接口或第三方库接口,与当前调用方接口不兼容。
新增的模块接口与现有接口不同,需要兼容旧系统。
调用方无法修改被适配对象的代码。
希望统一调用接口,减少条件判断或重复包装代码。

例如:

  • 文件读写接口兼容不同库
  • 网络客户端统一接口
  • 外部 API 返回类型不符合内部业务接口
  • 第三方 crate 提供不同函数签名

适配器模式可以隐藏接口差异,使系统内部调用一致。


4. 不使用这个模式会怎样

如果不使用适配器模式,可能出现:

调用方需要直接修改或特殊处理老接口。
系统中出现大量 if/else 判断接口类型。
逻辑散落,维护成本高。
添加新类型或库时,调用方需要修改多处代码。

5. 传统面向对象中的实现思路

在传统 OOP 中,适配器模式通常有两种形式:

  1. 类适配器(通过继承)
    • Adapter 继承自被适配类,同时实现目标接口。
  2. 对象适配器(通过组合)
    • Adapter 包含一个被适配对象,并实现目标接口,调用内部对象的方法并做转换。

结构示意:

Target (调用方期望接口)
Adapter (实现 Target 接口, 包含 Adaptee)
Adaptee (原始接口)

调用方只依赖 Target 接口,Adapter 做转换。


6. Rust 中的实现思路

6.1 Rust 中通常怎么实现

在 Rust 中,适配器模式通常用 包装结构体 + trait 来实现。

// 被适配对象
struct OldPrinter;

impl OldPrinter {
    fn print_old(&self, msg: &str) {
        println!("旧打印机: {}", msg);
    }
}

// 目标接口
trait Printer {
    fn print(&self, msg: &str);
}

// 适配器
struct PrinterAdapter {
    adaptee: OldPrinter,
}

impl Printer for PrinterAdapter {
    fn print(&self, msg: &str) {
        // 调用旧接口并适配参数
        self.adaptee.print_old(msg);
    }
}

fn main() {
    let old_printer = OldPrinter;
    let adapter = PrinterAdapter { adaptee: old_printer };
    adapter.print("Hello Rust Adapter!");
}

另一种方式是利用 From/Into trait 做类型转换:

struct Celsius(f64);
struct Fahrenheit(f64);

impl From<Fahrenheit> for Celsius {
    fn from(f: Fahrenheit) -> Self {
        Celsius((f.0 - 32.0) * 5.0 / 9.0)
    }
}

fn main() {
    let f = Fahrenheit(212.0);
    let c: Celsius = f.into();
    println!("212°F = {:.2}°C", c.0);
}

6.2 和传统 OOP 写法相比有什么不同

  1. Rust 不依赖类继承体系,适配器通过 组合 + trait 实现接口适配,而不是继承。
  2. 类型转换可以利用标准库的 From/Into trait,无需显式 Adapter 类。
  3. Rust 静态类型检查保证调用方接口和适配器接口一致,减少运行时错误。
  4. 不需要虚函数或多态指针,静态分发即可实现零成本适配。

6.3 Rust 中是否有更自然的替代写法

  • 对简单参数或类型适配,可以直接用 From/Into 或结构体更新语法。
  • 对复杂行为适配,推荐包装结构体 + trait 实现。
  • 对闭包场景,可以直接传递闭包实现行为替换,减少显式 Adapter 结构体。

7. Rust 中涉及的语言特性

trait
impl
包装结构体(struct + 内部对象)
From / Into trait
泛型(可选)
所有权与借用
闭包(可选)

8. 性能与工程代价

  • 静态适配器(包装 + trait)几乎零成本,编译器可内联。
  • 使用 Box<dyn Trait> 动态分发时会有一次虚函数调用开销。
  • From/Into 转换非常轻量,适合类型转换场景。
  • 包装结构体过多可能增加代码层级,但不会增加运行时开销。

9. 典型应用场景

老旧接口与新系统接口不兼容,需要适配
第三方库返回类型与内部类型不一致
不同平台或模块的实现差异封装
类型或参数需要转换以统一接口
行为或方法调用需要中间适配处理

10. 和相似模式的区别

  • 装饰器模式:装饰器模式在原对象上增加功能,不改变接口;适配器模式改变接口使调用方能使用。
  • 代理模式:代理模式控制访问或增加逻辑;适配器模式关注接口兼容。
  • 工厂模式:工厂模式负责对象创建;适配器模式不创建对象,而是转换已有对象接口。
  • 策略模式:策略模式关注可替换算法或行为;适配器模式关注接口兼容和适配。

11. 使用该模式的优点

调用方无需修改即可使用不兼容接口
可复用现有代码,无需修改老接口
与 trait 结合可保持类型安全
支持静态或动态适配
易于扩展新适配器

12. 使用该模式的代价

包装层增加代码层级
动态适配(Box<dyn Trait>)有轻微虚函数开销
复杂适配逻辑可能增加维护成本

13. 什么时候不应该使用

原接口已经兼容调用方,无需适配
适配逻辑过于简单,直接转换即可
不需要复用现有对象或接口

14. 一个简单例子思路

场景: 老打印机接口与新打印系统不兼容

  • 老打印机提供 print_old(&str) 方法
  • 新系统需要 Printer::print(&str) trait 接口
  • 创建 Adapter 包装老打印机,impl Printer trait
  • 调用方通过 Adapter 调用统一接口

15. 总结一句话

适配器模式的本质是:将不兼容的接口转换成调用方期望的接口,使系统可以复用现有对象而无需修改,同时保持类型安全。在 Rust 中,包装结构体 + trait 或 From/Into 是最常用实现方式。