1、前言
适配器模式是结构型设计模式的一种,用途主要是让两个没有关联的接口能够一起工作,连接这些不相关接口的对象称为适配器。
2、适配器模式
适配器模式是在无法直接连接的场景下,充当两个不兼容接口之间的连接器。此模式的主要目的是将现有接口转换为客户期望的另一个接口。
该模式的结构与装饰模式类似。然而,装饰模式通常是在考虑到扩展的情况下实现的。适配器通常在编写初始代码后实现,用于连接不兼容的接口。实现此模式有两种主要方法,让我们回顾一下它们。
2.1、对象适配器
此实现使用组合将逻辑委托给适配器。这是实现接口一致性的一种非常简单的方法:

在此场景下,Adapter 包含 Adaptee,并将 request() 方法委托给 Adaptee 中的 specificRequest() 方法。
2.2、类适配器
这个版本的适配器模式需要多重继承,如果我们不考虑具有默认方法的接口,这在 Java 中技术上是不可能的。主要思想是通过实现 Target接口 和 继承Adaptee 类来创建 Adapter。但是,当我们将 Target 作为接口时,我们更容易在 Java 中实现这一点,因为 Target 是我们可以控制的部分:

它看起来与对象适配器非常相似,但现在Adapter继承了Adaptee,而不是通过组合的方式包含它。这种方法的好处之一是Adapter可以在两种上下文中使用,既可以作为Target,也可以作为Adaptee。从技术上讲,我们创建了一个双向适配器,在某些情况下会非常方便。
2.3、好处和权衡
类适配器最适合在Target和Adaptee方法之间进行一对一映射。这样,我们就可以使用委托,而无需在适配器中进行额外的实现。但是,如果Target接口更复杂,则此方法可能需要在适配器中进行额外的工作。不过,我们可以通过委托来解决这个问题:

在这里,我们仅将 request() 方法委托给Adaptee。其余部分取自 ConcreteTarget。我们可以使用组合将这些接口方法委托给实现,以避免代码重复。同时,如果我们不需要双向适配器,我们可以使用对象适配器,这样结构就会简单很多:

因此,实现此模式的方法在很大程度上取决于代码库的初始状态,是否可以使用接口,以及是否需要提供适配器在两种上下文中工作的能力。
3、适配器模式例子
Java 有一个关于适配器模式的优秀示例,我们可以在这里回顾一下。Enumeration和Iterator是两个相关的接口,它们是适配器-被适配者关系的很好的例子。
3.1、Enumeration
这两个接口都非常简单,但让我们从Enumeration开始:
public interface Enumeration<E> {
boolean hasMoreElements();
E nextElement();
default Iterator<E> asIterator() {
return new Iterator<>() {
@Override public boolean hasNext() {
return hasMoreElements();
}
@Override public E next() {
return nextElement();
}
};
}
}
3.2、Iterator
Iterator接口的描述如下:
An iterator over a collection. Iterator takes the place of Enumeration in the Java Collections Framework. Iterators differ from enumerations in two ways:
Iterators allow the caller to remove elements from the underlying collection during the iteration with well-defined semantics.
Method names have been improved.
从技术上讲,Enumeration具有相同的接口,唯一的区别是方法名称:
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
3.3. Adapter实现
正如我们所看到的,这些接口是相似的并且具有相同的目标。默认的 asIterator() 方法是在 Java 9 中添加的,并且包含使用匿名类的 Adapter 模式的实现:
default Iterator<E> asIterator() {
return new Iterator<>() {
@Override public boolean hasNext() {
return hasMoreElements();
}
@Override public E next() {
return nextElement();
}
};
}
这个例子使用了组合,但在这种情况下并不明确。我们不将 Enumeration 实例传递给 Iterator,因为我们在 Enumeration 的上下文中创建 Iterator。这样,我们就可以直接访问Enumeration方法。这是一种非常强大的技术,它允许隐藏接口的一部分并使用委托给私有方法。前面的类适配器和对象适配器示例需要公共 API 来进行委托。
然而,只有当我们能够控制Adapter和Adaptee时,这种使用匿名类实现适配器模式的方法才是可能的,而这在大多数情况下是不可能的。让我们想象一下在 Java 9 之前我们如何实现相同的功能:
public class IteratorAdapter<E> implements Iterator<E> {
private Enumeration<E> enumeration;
public IteratorAdapter(Enumeration<E> enumeration) {
this.enumeration = enumeration;
}
@Override
public boolean hasNext() {
return enumeration.hasMoreElements();
}
@Override
public E next() {
return enumeration.nextElement();
}
}
此示例与上面的对象适配器示例相同。让我们使用类适配器实现相同的功能。我们将在本示例中使用 StringTokenizer,因为它实现了 Enumeration 接口:
public class StringTokenizerIteratorAdapter extends StringTokenizer implements Iterator<String> {
public StringTokenizerIteratorAdapter(final String str, final String delim, final boolean returnDelims) {
super(str, delim, returnDelims);
}
public StringTokenizerIteratorAdapter(final String str, final String delim) {
super(str, delim);
}
public StringTokenizerIteratorAdapter(final String str) {
super(str);
}
@Override
public boolean hasNext() {
return hasMoreTokens();
}
@Override
public String next() {
return nextToken();
}
}
我们创建了一个双向适配器,可以用作 Iterator 和 StringTokenizer。 Iterator 方法不直接委托给 Enumerator 中的方法,而是委托给 StringTokenizer 中更具体的方法。
4、结论
在本文中,我们研究了 Java 中的适配器设计模式。这是管理代码库复杂性和使用遗留系统的最重要的模式之一。此外,它还允许重用第三方库,而无需更改应用程序,并且始终能够轻松更改实现。