意图
适配器模式(Adapter)是一种结构型设计模式,它能使接口不兼容的对象能够相互合作。
解决方案
你可以创建一个适配器。这是一个特殊的对象,能够转换对象接口,使其能与其他对象进行交互。
适配器模式通过封装对象将复杂的转换过程隐藏于幕后。被封装的对象甚至察觉不到适配器的存在。例如,你可以使用一个将所有数据转换为英制单位(如英尺和英里)的适配器封装运行于米和千米单位制中的对象。
适配器不仅可以转换不同格式的数据,其还有助于采用不同接口的对象之间的合作。它的运作方式如下:
- 适配器实现与其中一个现有对象兼容的接口。
- 现有对象可以使用该接口安全地调用适配器方法。
- 适配器方法被调用后将以另一个对象兼容的格式和顺序将请求传递给该对象。
有时你甚至可以创建一个双向适配器来实现双向转换调用。
适配器模式结构
对象适配器
实现时使用了构成原则:适配器实现了其中一个对象的接口,并对另一个对象进行封装。所有流行的编程语言都可以实现适配器。
客户端(Client)是包含当前程序业务逻辑的类。
客户端接口(Client Interface)描述了其他类与客户端代码合作时必须遵循的协议。
服务(Service)中有一些功能类(通常来自第三方或遗留系统)。客户端与其接口不兼容,因此无法直接调用其功能。
适配器(Adapter)是一个可以同时与客户端和服务交互的类:它在实现客户端接口的同时封装了服务对象。适配器接受客户端通过适配器接口发起的调用,并将其转换为适用于被封装服务对象的调用。
客户端代码只需通过接口与适配器交互即可,无需与具体的适配器类耦合。因此,你可以向程序中添加新类型的适配器而无需修改已有代码。这在服务类的接口被更改或替换时很有用:你无需修改客户端代码就可以创建新的适配器类。
类适配器
这一实现使用了继承机制:适配器同时继承两个对象的接口。请注意,这种方式仅能在支持多重继承的编程语言中实现, 例如 C++。
适配器模式适合应用场景
- 当你希望使用某个类,但是其接口与其他代码不兼容时,可以使用适配器类。
适配器模式允许你创建一个中间层类,其可作为代码与遗留类、第三方类或提供怪异接口的类之间的转换器。
- 如果您需要复用这样一些类,他们处于同一个继承体系,并且他们又有了额外的一些共同的方法,但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性。
你可以扩展每个子类,将缺少的功能添加到新的子类中。但是,你必须在所有新子类中重复添加这些代码,这样会使得代码有坏味道。
将缺失功能添加到一个适配器类中是一种优雅得多的解决方案。然后你可以将缺少功能的对象封装在适配器中,从而动态地获取所需功能。如要这一点正常运作,目标类必须要有通用接口,适配器的成员变量应当遵循该通用接口。这种方式同装饰模式非常相似。
适配器模式优缺点
优点:
- 单一职责原则。你可以将接口或数据转换代码从程序主要业务逻辑中分离。
- 开闭原则。只要客户端代码通过客户端接口与适配器进行交互,你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。
缺点:
- 代码整体复杂度增加,因为你需要新增一系列接口和类。有时直接更改服务类使其与其他代码兼容会更简单。
与其他模式的关系
-
桥接模式通常会于开发前期进行设计,使你能够将程序的各个部分独立开来以便开发。另一方面,适配器模式通常在已有程序中使用,让相互不兼容的类能很好地合作。
-
适配器可以对已有对象的接口进行修改,装饰模式则能在不改变对象接口的前提下强化对象功能。此外,装饰还支持递归组合,适配器则无法实现。
-
外观模式为现有对象定义了一个新接口,适配器则会试图运用已有的接口。适配器通常只封装一个对象,外观通常会作用于整个对象子系统上。
-
桥接、状态模式和策略模式(在某种程度上包括适配器)模式的接口非常相似。实际上,它们都基于组合模式——即将工作委派给其他对象,不过也各自解决了不同的问题。模式并不只是以特定方式组织代码的配方,你还可以使用它们来和其他开发者讨论模式所解决的问题。
在 Java 中使用模式
使用示例:适配器模式在 Java 代码中很常见。基于一些遗留代码的系统常常会使用该模式。在这种情况下,适配器让遗留代码与现代的类得以相互合作。
Java 核心程序库中有一些标准的适配器:
java.util.Arrays#asList()
java.util.Collections#list()
java.util.Collections#enumeration()
java.io.InputStreamReader(InputStream)
(返回Reader
对象)java.io.OutputStreamWriter(OutputStream)
(返回Writer
对象)javax.xml.bind.annotation.adapters.XmlAdapter#marshal()
和#unmarshal()
示例代码
客户端(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));
}
}