https://en.wikipedia.org/wiki/Adapter_pattern
https://www.baeldung.com/java-adapter-pattern
适配器模式(也称为包装器「wrapper」,与装饰器模式「decorator pattern」共享的另一种命名),它允许将现有类的接口用作另一个接口。它通常用于使现有类与其他类协同工作,而无需修改其源代码。
适配器模式描述了如何解决重复出现的「recurring」设计问题,以设计灵活和可重用的面向对象软件,即更容易实现、更改、测试和重用的对象。
解决了以下问题:
如何重用没有客户端所需接口的类?
具有不兼容「incompatible」接口的类如何协同工作?
如何为类提供替代接口「alternative interface」?
通常,一个(已经存在的)类不能被重用,只是因为它的接口不符合「conform to」客户端要求的接口。
如何解决这些问题:
定义一个单独的适配器类「adapter」,将类(adaptee)的(不兼容)接口转换为客户端所需的另一个接口(target)。
通过适配器「adapter」处理(重用)没有所需接口的类。
这种模式的关键思想是通过一个单独的适配器「adapter」来工作,该适配器在不更改的情况下调整(现有)类的接口。
客户端不知道他们是直接使用目标类,还是通过适配器使用没有目标接口的类。
另请参见下面的UML类图。
在上面的UML类图中,需要 target 接口的 client 类不能直接重用adaptee类,因为 adaptee 的接口不符合target 接口。相反,客户端通过一个 adapter 类工作,该类根据adaptee实现了target 接口:
对象适配器「object adapter
」方式通过在运行时委托给 adaptee 对象来实现 target 接口(adaptee.specificOperation())。
类适配器「class adapter」方式通过在编译时继承 adaptee 类来实现目标接口(specificOperation())。
Object adapter pattern
在这个适配器模式中,适配器包含它所包装的类的一个实例。在这种情况下,适配器会调用包装对象的实例。
下面这个图片描述了 Adapter 实现了 Target,包含一个 Adaptee 的引用。
Class adapter pattern
此适配器模式使用多个多态接口「polymorphic interfacesmultiple polymorphic interfaces」,实现或继承预期的接口和预先存在的接口。通常将预期的接口创建为纯接口类,特别是在Java(JDK 1.8之前)等不支持类多重继承的语言中。
这了这个没有展示出,预期的接口 ,只展示了 预先存在的接口 Adaptee1-N。
所以来看看下图的解释,实现了(绿色箭头)预期的接口 ,继承了(蓝色箭头)预先存在的接口
继承是因为不一定对 adaptee 有控制权。
Benefits and Trade-Offs
类适配器方法最适合Target和Adaptee方法之间的一对一映射。这样,我们就可以使用委托,而无需在Adapter中进行额外的实现。但是,如果Target接口更复杂,这种方法可能需要在Adapter中进行额外的工作。然而,我们可以通过委托「delegation」来解决这个问题:
在这里,我们只将request()方法委托给Adaptee。其余部分取自ConcreteTarget。我们可以使用组合将这些接口方法委托给实现,以避免代码重复。同时,如果我们不需要双边「two-way」适配器,我们可以使用对象适配器,这将使结构更简单:
因此,实现此模式的方式在很大程度上取决于代码库的初始状态,我们是否可以使用接口,以及我们是否需要为适配器提供在两种情况下工作的能力。
Adapter Pattern Example
Java有一个很好的适配器模式示例,我们可以在这里查看。Enumeration 和 Iterator 是两个相关的接口,是adapter-adaptee 关系的很好的例子。
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();
}
};
}
}
Iterator接口的描述包含以下内容:
iterator 用来遍历 collection。iterator在Java集合框架中取代了Enumeration。Iterators 与Enumerations 有两个不同之处:
Iterators 允许调用者在迭代过程中使用定义良好的语义从底层集合中删除元素。
方法名称已得到改进。
从技术上讲,枚举具有相同的接口,唯一的区别是方法名称:
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());
}
}
Adapter Implementations
正如我们所看到的,这些接口是相似的,具有相同的目标。默认的asIterator()方法是在Java 9中添加的,它包含使用匿名类实现Adapter模式:
default Iterator<E> asIterator() {
return new Iterator<>() {
@Override public boolean hasNext() {
return hasMoreElements();
}
@Override public E next() {
return nextElement();
}
};
}
这个例子使用了组合,但在这种情况下并不明确。我们不将枚举实例传递给迭代器,因为我们在枚举的上下文中创建了迭代器。这样,我们就可以直接访问Enumeration方法。这是一种非常强大的技术,它允许隐藏接口的一部分并使用委托给私有方法。前面的类和对象适配器示例需要公共API进行委派。
然而,只有当我们同时控制adapter 和 adaptee 时,才有可能使用匿名类实现Adapter模式,而这在大多数情况下是不可能的。让我们想象一下,在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,因为它实现了枚举接口:
public class StringTokenizer implements Enumeration<Object>
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();
}
}
我们创建了一个双向「 two-way」适配器(相当于实现了 Enumeration 和 Iterator 两个接口),可以用作迭代器和StringTokenizer。迭代器方法不直接委托给枚举器中的方法,而是委托给StringTokenizer中更具体的方法。