本文中涉及到的完整代码存放于以下 GitHub 仓库中 LearningCode
1. 理论部分
适配器模式(Adapter Pattern):将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。
适配器模式又称包装器模式(Wrapper Pattern)。
1.1 结构与实现
适配器模式包含以下 3 个角色:
- Target(目标接口):
- 职责:定义客户所需的接口。
- 实现:Target 一般声明为抽象类或接口,也可以是具体类。
- Adapter(适配器类):
- 职责:对 Adaptee 的接口与 Target 接口进行适配。
- 实现:Adapter 通常声明为具体类。
- Adaptee(适配者):
- 职责:定义一个已经存在的接口,这个接口需要适配。
- 实现:Adaptee 通常声明为具体类。
适配器模式存在以下两种实现方式:
- 类适配器
- 对象适配器
1.1.1 类适配器
在类适配器中,Adapter 实现 Target 并继承 Adaptee 类,其 UML 类图如下所示: 当希望 Adapter 可以重定义 Adaptee 的部分行为时,推荐使用类适配器模式。
1.1.2 对象适配器
在对象适配器中,Adapter 通过继承 Target 并关联一个 Adaptee 对象,其 UML 类图如下所示: 当希望一个 Adapter 可以匹配 Adaptee及其子类时,推荐使用对象适配器模式。
在不支持多重继承的语言中,如果面临需要继承多个类的情况下,只能使用对象适配器模式。
1.2 扩展:缺省适配器
缺省适配器模式(Default Adapter Pattern):当不需要实现一个接口提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中的每个方法提供一个默认实现,那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。
缺省适配器模式包含以下 3 个角色:
- Target(目标接口):
- 职责:声明了大量的方法。
- 实现:Target 通常声明为一个接口。
- DefaultAdapter(缺省适配器)
- 职责:实现了 Target 接口,并为接口中的每个方法提供了默认实现。
- 实现:DefaultAdapter 通常声明为一个抽象类。
- ConcreteClass(具体业务类):
- 职责:是 DefaultAdapter 的子类,选择性地重写 DefaultAdapter 中的方法。
- 实现:ConcreteClass 通常声明为类。
缺省适配器的 UML 类图如下所示:
1.3 扩展:双向适配器
上面介绍的适配器是单向的,只能实现从 Adaptee 到 Target 的转换,无法反向适配;双向适配器则能够同时兼容两个方向,既可将 Adaptee 适配为 Target,也能将 Target 适配为 Adaptee,从而实现两个接口之间的双向透明通信。在双向适配器中,Adapter 实现了 Target 接口与 Adaptee 接口, 同时包含对 Target 和 Adaptee 的引用,Adaptee 可以通过它调用 Target 中的方法,Target 也可以通过它调用 Adaptee 中的方法。
在不支持多重继承的语言中,如果 Adaptee 与 Target 均为抽象类,则无法实现双向适配器。
双向适配器的 UML 类图如下所示:
1.4 扩展:参数化适配器
如果要适配多个无继承关系的 Adaptee,势必会创建很多的适配器子类。为了解决这个问题,需要设计一个 Adapter 能够适配多个无继承关系的 Adaptee,即将适配逻辑的实现委托给外部客户端,由客户端负责提供具体的适配行为。其 UML 类图如下所示:
为了便于理解,这里采用 Java 的函数式接口进行描述。
1.5 优缺点与适用场景
适配器模式具有以下优点:
- 无论是对象适配器模式还是类适配器模式,都将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
- 对于类适配器,可以在适配器类重写适配者的方法,使得适配器的灵活性更强。
- 对于对象适配器,可以对适配者及其子类进行适配。
适配器模式存在以下缺点:
- 对于类适配器,在不支持多重类继承的语言中,如果适配者和目标接口都是抽象类,那么类适配器模式将无法实现,有一定的局限性。
- 对于对象适配器,在适配器中置换适配者的某些方法比较麻烦。
适配器模式适用于以下场景:
- 需要使用一些已经存在类,而这些类的接口不符合系统的需要,甚至没有这些类的源代码。
- 想创建一个可以复用的类,用于和一些彼此之间没有太大关联的类一起工作。
2. 实现部分
以 Java 代码为例,演示适配器模式及其扩展的实现。
2.1 适配器模式的实现
案例介绍:
2.1.1 类适配器
编写目标接口 —— ISensitiveWordsFilter,代码如下所示:
public interface ISensitiveWordsFilter {
String filter(String text);
}
编写适配者,以 A 系统的过滤系统为例:
public class ASensitiveWordsFilter {
public String filterObsceneWords(String text) {
System.out.println("A 敏感词过滤系统:过滤淫秽词语");
return null;
}
public String filterPoliticalWords(String text) {
System.out.println("A 敏感词过滤系统:过滤涉政词语");
return null;
}
}
编写A系统的适配器 —— ASensitiveWordsAdapter:
public class ASensitiveWordsAdapter extends ASensitiveWordsFilter implements ISensitiveWordsFilter {
@Override
public String filter(String text) {
return filterObsceneWords(filterPoliticalWords(text));
}
}
客户端调用:
public class Main {
public static void main(String[] args) {
// 可以使用环境变量或配置文件来优化 ISensitiveWordsFilter 实例的获取
ISensitiveWordsFilter sensitiveWordsFilter = new ASensitiveWordsAdapter();
System.out.println(sensitiveWordsFilter.filter(""));
}
}
完整的 UML 类图如下所示:
2.1.2 对象适配器
编写A系统的适配器 —— ASensitiveWordsAdapter:
public class ASensitiveWordsAdapter implements ISensitiveWordsFilter {
private final ASensitiveWordsFilter sensitiveWordsFilter;
public ASensitiveWordsAdapter(ASensitiveWordsFilter sensitiveWordsFilter) {
this.sensitiveWordsFilter = sensitiveWordsFilter;
}
@Override
public String filter(String text) {
return sensitiveWordsFilter.filterObsceneWords(sensitiveWordsFilter.filterPoliticalWords(text));
}
}
客户端调用:
public class Main {
public static void main(String[] args) {
// 可以使用环境变量或配置文件来优化 ISensitiveWordsFilter 实例的获取
ISensitiveWordsFilter sensitiveWordsFilter = new ASensitiveWordsAdapter(
new ASensitiveWordsFilter()
);
System.out.println(sensitiveWordsFilter.filter(""));
}
}
完整的UML类图如下所示:
2.2 扩展:缺省适配器
案例介绍:某公司正在开发一款软件,软件在使用过程会经常播放动画。该软件的开发人员定义了 AnimationListener 用于定义软件在播放动画的过程中会遇到的各种多种动画状态事件,例如 onAnimationStart()(动画开始)、onAnimationEnd()(动画结束)、 onFrameUpdate(float progress)(每一帧更新时,传入播放进度)等。现在需要为不同的动画效果添加监听:
- 一个淡入淡出的 FadeEffect 只关心 onAnimationStart()(初始化透明度)和 onAnimationEnd()(确保最终透明度)
- 一个进度条动画 ProgressBar 必须监听 onFrameUpdate()(根据进度更新条的长度)
为了便于开发人员使用,请使用缺省适配器模式进行代码设计。
编写 AnimationListener,代码如下:
public interface AnimationListener {
void onAnimationStart();
void onAnimationEnd();
void onAnimationUpdate();
}
编写 AnimationAdapter,实现AnimationListener中声明的方法,代码如下:
public abstract class AnimationAdapter implements AnimationListener{
@Override
public void onAnimationStart() {
}
@Override
public void onAnimationEnd() {
}
@Override
public void onAnimationUpdate() {
}
}
以淡入淡出动画为例,继承AnimationAdapter,编写动画:
public class FadeEffectAnimation extends AnimationAdapter {
@Override
public void onAnimationStart() {
System.out.println("淡入淡出动画: 开始");
}
@Override
public void onAnimationEnd() {
System.out.println("淡入淡出动画: 结束");
}
}
客户端调用:
public class Main {
public static void main(String[] args) {
// 播放动画,假设动画总计 10 帧
AnimationListener animationListener = new FadeEffectAnimation();
animationListener.onAnimationStart();
for(int i = 0; i < 10; i++) {
animationListener.onAnimationUpdate();
}
animationListener.onAnimationEnd();
}
}
完整的 UML 类图如下所示:
2.3 扩展:双向适配器
暂时没有找到的合适案例,后面遇到了再编写。
如果各位大佬有比较合适的案例,请在评论区留言或者私信我,十分感谢。
2.4 扩展:参数化适配器
案例与2.1相同,参数适配器的目的是将适配逻辑由Adapter内部转交给了客户端,从而减少子类化。 编写 SensitiveWordsAdapter
import java.util.function.Function;
public class SensitiveWordsAdapter<T> implements ISensitiveWordsFilter{
private final T t;
private Function<T, Function<String, String>> filterDelegateGenerator;
public SensitiveWordsAdapter(T t) {
this.t = t;
}
public void setFilterDelegate(Function<T, Function<String, String>> filterDelegateGenerator) {
this.filterDelegateGenerator = filterDelegateGenerator;
}
@Override
public String filter(String text) {
return filterDelegateGenerator.apply(t).apply(text);
}
}
客户端调用:
public class Main {
public static void main(String[] args) {
SensitiveWordsAdapter<ASensitiveWordsFilter> sensitiveWordsAdapter = new SensitiveWordsAdapter<ASensitiveWordsFilter>(
new ASensitiveWordsFilter()
);
sensitiveWordsAdapter.setFilterDelegate(aSensitiveWordsFilter -> {
return text -> {
return aSensitiveWordsFilter.filterObsceneWords(aSensitiveWordsFilter.filterPoliticalWords(text));
};
});
System.out.println(sensitiveWordsAdapter.filter(""));
}
}
完整的 UML 类图如下所示:
3. 参考资料
学习视频:
学习读物:
电子文献:
评论前必须登录!
注册