设计模式:适配器模式

意图

适配器模式(Adapter)是一种结构型设计模式它能使接口不兼容的对象能够相互合作

解决方案

你可以创建一个适配器这是一个特殊的对象能够转换对象接口使其能与其他对象进行交互

适配器模式通过封装对象将复杂的转换过程隐藏于幕后被封装的对象甚至察觉不到适配器的存在例如你可以使用一个将所有数据转换为英制单位如英尺和英里的适配器封装运行于米和千米单位制中的对象

适配器不仅可以转换不同格式的数据其还有助于采用不同接口的对象之间的合作它的运作方式如下

  1. 适配器实现与其中一个现有对象兼容的接口
  2. 现有对象可以使用该接口安全地调用适配器方法
  3. 适配器方法被调用后将以另一个对象兼容的格式和顺序将请求传递给该对象

有时你甚至可以创建一个双向适配器来实现双向转换调用

适配器模式结构

对象适配器

实现时使用了构成原则适配器实现了其中一个对象的接口并对另一个对象进行封装所有流行的编程语言都可以实现适配器

适配器设计模式的结构(对象适配器)

客户端Client)是包含当前程序业务逻辑的类。

客户端接口Client Interface)描述了其他类与客户端代码合作时必须遵循的协议。

服务Service)中有一些功能类通常来自第三方或遗留系统。客户端与其接口不兼容,因此无法直接调用其功能。

适配器Adapter)是一个可以同时与客户端和服务交互的类:它在实现客户端接口的同时封装了服务对象。适配器接受客户端通过适配器接口发起的调用,并将其转换为适用于被封装服务对象的调用。

客户端代码只需通过接口与适配器交互即可,无需与具体的适配器类耦合。因此,你可以向程序中添加新类型的适配器而无需修改已有代码。这在服务类的接口被更改或替换时很有用:你无需修改客户端代码就可以创建新的适配器类。

类适配器

这一实现使用了继承机制适配器同时继承两个对象的接口请注意这种方式仅能在支持多重继承的编程语言中实现 例如 C++

适配器设计模式(类适配器)
类适配器不需要封装任何对象因为它同时继承了客户端和服务的行为适配功能在重写的方法中完成最后生成的适配器可替代已有的客户端类进行使用

适配器模式适合应用场景

  • 当你希望使用某个类但是其接口与其他代码不兼容时可以使用适配器类

适配器模式允许你创建一个中间层类其可作为代码与遗留类第三方类或提供怪异接口的类之间的转换器

  • 如果您需要复用这样一些类他们处于同一个继承体系并且他们又有了额外的一些共同的方法但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性

你可以扩展每个子类将缺少的功能添加到新的子类中但是你必须在所有新子类中重复添加这些代码这样会使得代码有坏味道

将缺失功能添加到一个适配器类中是一种优雅得多的解决方案然后你可以将缺少功能的对象封装在适配器中从而动态地获取所需功能如要这一点正常运作目标类必须要有通用接口适配器的成员变量应当遵循该通用接口这种方式同装饰模式非常相似

适配器模式优缺点

优点:

  • 单一职责原则。你可以将接口或数据转换代码从程序主要业务逻辑中分离
  • 开闭原则只要客户端代码通过客户端接口与适配器进行交互你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器

缺点:

  • 代码整体复杂度增加,因为你需要新增一系列接口和类。有时直接更改服务类使其与其他代码兼容会更简单。

与其他模式的关系

  • 桥接模式通常会于开发前期进行设计使你能够将程序的各个部分独立开来以便开发另一方面适配器模式通常在已有程序中使用让相互不兼容的类能很好地合作

  • 适配器可以对已有对象的接口进行修改装饰模式则能在不改变对象接口的前提下强化对象功能此外装饰还支持递归组合适配器则无法实现

  • 适配器能为被封装对象提供不同的接口代理模式能为对象提供相同的接口装饰则能为对象提供加强的接口

  • 外观模式为现有对象定义了一个新接口适配器则会试图运用已有的接口适配器通常只封装一个对象外观通常会作用于整个对象子系统上

  • 桥接状态模式策略模式在某种程度上包括适配器模式的接口非常相似实际上它们都基于组合模式——即将工作委派给其他对象不过也各自解决了不同的问题模式并不只是以特定方式组织代码的配方你还可以使用它们来和其他开发者讨论模式所解决的问题

在 Java 中使用模式

使用示例适配器模式在 Java 代码中很常见基于一些遗留代码的系统常常会使用该模式在这种情况下适配器让遗留代码与现代的类得以相互合作

Java 核心程序库中有一些标准的适配器

 示例代码

客户端Client)

客户端依赖于Runnable接口。

public class Client {
  public void runThread(Runnable runnable) {
    Thread thread = new Thread(runnable);
    thread.start();
  }
}

客户端接口Client Interface)

Java自己的Runnable接口。

public interface Runnable {
    public abstract void run();
}

服务 Service

三方服务提供的Task实现了Callable接口,不能修改。

public class Task implements Callable<Long> {
  private final long count;

  public Task(long count) {
    this.count = count;
  }

  public Long call() {
    long result = 0;
    for (long n = 1; n <= this.count; n++) {
      result = result + n;
    }
    System.out.println("Result: " + result);
    return result;
  }
}

适配器 Adapter

public class RunnableAdapter implements Runnable {
  private final Callable<?> callable;

  public RunnableAdapter(Callable<?> callable) {
    this.callable = callable;
  }

  public void run() {
    try {
      callable.call();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}

Demo

public class Demo {
  public static void main(String[] args) {
    Client client = new Client();
    Task task = new Task(123450000L);
    client.runThread(new RunnableAdapter(task));
  }
}