跳转至

适配器模式

分类:结构型模式。对应《Head First 设计模式》第 7 章;原来混在命令模式页面中的适配器摘记整理于此。

类结构

classDiagram
    direction TB
    class Client
    class Target {
        <<interface>>
        +request()
    }
    class Adapter {
        +request()
    }
    class Adaptee {
        +specificRequest()
    }
    Target <|.. Adapter
    Client ..> Target : 使用
    Adapter --> Adaptee : 适配

角色定义

角色 定义
Client(客户端) 依赖目标接口发起请求,无需了解被适配者的接口。
Target(目标接口) 定义客户端期望的操作及其行为约定。
Adapter(对象适配器) 实现目标接口,持有被适配者,将请求转换后委托给它。
Adaptee(被适配者) 提供已有功能,其接口与客户端期望不兼容,由适配器调用。

定义与用途

适配器模式将一个类的接口转换成客户期望的另一种接口,使原本因接口不兼容而无法合作的类能够一起工作。

OO 适配器和真实世界的转接头承担类似的职责。客户继续调用自己熟悉的接口,适配器在内部转换调用方式,并委托给已有对象。

例如,客户期望一个具有 quack()、fly() 的鸭子接口,已有火鸡对象提供 gobble() 和短距离飞行。适配器可以把 quack() 转成 gobble(),并通过多次短距离飞行来实现客户期望的 fly()。

参与对象

角色 职责
目标接口(Target) 定义客户需要调用的操作
被适配者(Adaptee) 提供已有功能,但接口与客户期望不一致
适配器(Adapter) 实现目标接口,并把调用转换、委托给被适配者

对象适配器通过组合持有被适配者。转换时除了方法名,还可能涉及参数、返回值和行为语义;需要确认转换后的行为符合目标接口的约定。

与其他模式的区别

  • 装饰者围绕原有接口增加职责;适配器的目标是解决接口不兼容。
  • 命令把请求封装成对象,便于排队、记录或撤销。

Head First 示例:让火鸡接受鸭子的调用方式

《Head First 设计模式》第 7 章的鸭子与火鸡示例中,客户端通过 Duck 调用 quack() 和 fly(),已有的 Turkey 却提供 gobble() 和短距离飞行。作者 2020 年第二版配套代码 adapter/ducks 用 TurkeyAdapter 把火鸡包装成客户端可以接收的鸭子类型。

适配不仅转换方法名:quack() 转交给火鸡的 gobble(),一次 fly() 则触发五次火鸡飞行。这是配套代码为示例设定的行为映射;MallardDuck 和包装后的火鸡都能传入同一个 testDuck(Duck duck)。

具体类名 对应角色 职责
Duck 目标接口 定义客户端期望的 quack() 和 fly()。
MallardDuck 目标接口的原生实现 直接实现鸭子的叫声和飞行动作,供入口对照调用。
Turkey 被适配者接口 定义已有的 gobble() 和 fly()。
WildTurkey 具体被适配者 实现火鸡叫声和短距离飞行。
TurkeyAdapter 对象适配器 实现 Duck,持有 Turkey turkey 并转换调用。
DuckTestDrive 客户端与示例入口 创建对象,通过 testDuck(Duck duck) 测试鸭子接口。

示例 UML

classDiagram
    direction TB
    class Duck {
        <<interface>>
        +quack()
        +fly()
    }
    class MallardDuck {
        +quack()
        +fly()
    }
    class Turkey {
        <<interface>>
        +gobble()
        +fly()
    }
    class WildTurkey {
        +gobble()
        +fly()
    }
    class TurkeyAdapter {
        Turkey turkey
        +TurkeyAdapter(Turkey turkey)
        +quack()
        +fly()
    }
    class DuckTestDrive {
        +main(String[] args)$
        testDuck(Duck duck)$
    }
    Duck <|.. MallardDuck
    Duck <|.. TurkeyAdapter
    Turkey <|.. WildTurkey
    TurkeyAdapter --> Turkey : turkey 委托
    DuckTestDrive ..> Duck : testDuck 接收
    DuckTestDrive ..> TurkeyAdapter : 创建适配器
    DuckTestDrive ..> WildTurkey : 创建被适配者

图中保留火鸡适配鸭子的方向,省略反向的 DuckAdapter 及入口中的无人机挑战代码。TurkeyAdapter 没有继承 WildTurkey,而是持有 Turkey 接口,因此属于对象适配器。

协作过程

  1. DuckTestDrive 创建 MallardDuck 和 WildTurkey,把后者传给 TurkeyAdapter 的构造器。
  2. 客户端先将普通鸭子、再将适配器传给 testDuck(Duck duck);两次都只使用 Duck 的方法。
  3. 测试方法调用适配器的 quack(),适配器通过字段 turkey 调用 gobble()。
  4. 测试方法调用适配器的 fly(),适配器循环五次调用 turkey.fly(),完成示例约定的飞行转换。

固定版本源码:

评论