0x0、引言
?周末戒游失败,王者新赛季,?沉迷云缨,啃节《设计模式之美》压压精,本文对应设计模式与范式:结构型(51),适配器模式
(Adapter Pattern)。
了解定义,适用场景、类适配器和对象适配器就差不多了~
Tips:二手知识加工难免有所纰漏,感兴趣有时间的可自行查阅原文,谢谢。
0x1、定义
适配器,很好理解,比如手边的电脑电源适配器,它的作用就是将 220V的家用交流电 转换成 20V的直流电,又比如苹果笔记本电脑,只有 雷电接口,如果想用 USB接口的键鼠/网线,需要一个 扩展坞/转接头。
将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能在一起工作的类可以一起工作,这就是适配器模式。
简单点说:
两个彼此间没有太大关联的类,想要交互完成某些事情,如果直接去改各自的接口,就显得有些繁琐了。可以加个 中间类,用它来协调两个类间的关系,完成相关业务。
适用场景
- 封装有缺陷的接口设计 (如依赖的外部系统在接口设计方面有缺陷,隔离缺陷,对接口进行二次封装);
- 统一多个类的接口设计 (某功能的实现依赖多个外部系统,接口适配为统一的接口定义,多态复用);
- 替换依赖的外部系统;
- 兼容老版本接口 (要废弃的接口不直接删除,暂时保留标注为的deprecated,并将内部实现逻辑委托为新接口实现);
- 适配不同格式的数据; (如Java中的Arrays.asList()将数组类型数据转换为集合类型);
- 不同接口协议转换;
0x2、类适配器
根据适配器类与适配器类的关系不同,分为 类适配器
与 对象适配器
两种,前者使用 继承
关系 实现,后者使用 组合
关系实现。
写个简单的翻译转换例子~
// 需要适配的接口
public class English {
void speakEnglish(String talk) {
System.out.println("【英语】" + talk);
}
}
// 目标接口
interface Chinese {
void speakChinese(String talk);
}
// 适配器角色
public class ClassTranslator extends English implements Chinese {
@Override
public void speakChinese(String talk) {
// 可调用父类方法或直接进行重写
super.speakEnglish(talk);
System.out.println("假装请求了翻译接口,输出翻译结果:【中文】玛卡巴卡");
}
}
// 测试用例
public class AdapterTest {
public static void main(String[] args) {
Chinese translator = new ClassTranslator();
translator.speakChinese("Ma ka ba ka");
}
}
复制代码
运行输出结果如下:
UML类图如下:
角色如下:
- Target (目标接口) → 客户所期待的接口,目标是接口;
- Adaptee (需要适配的类) → 又称适配者类;
- Adapter (适配器) → 并继承适配者类,实现目标接口,按需重写、调用方法;
0x3、对象适配器
还是上面的例子,改用对象适配器写一波,直接新建一个对象适配器类:
public class ObjectAdapter implements Chinese {
private English english;
public ObjectAdapter(English english) {
this.english = english;
}
@Override
public void speakChinese(String talk) {
english.speakEnglish(talk);
System.out.println("假装请求了翻译接口,输出翻译结果:【中文】玛卡巴卡");
}
}
// 测试用例
public static void main(String[] args) {
Chinese translator = new ObjectAdapter(new English());
translator.speakChinese("Ma ka ba ka");
}
复制代码
运行输出结果同上,从代码不难看出两者的区别:
对象适配器支持传入被适配器对象,可以灵活地做到多种被适配接口的适配,而类适配器直接继承,无法动态修改(Java不支持多继承),所以对象适配器平时用得多一些。
UML类图如下:
角色如下:
- Target (目标接口) → 客户所期待的接口,可以是具体或抽象的类,也可以是接口;
- Adaptee (需要适配的类) → 又称适配者类;
- Adapter (适配器) → 包装一个需要适配的对象,把原接口转换成目标接口;
0x4、单接口适配器模式
又称 缺省适配器模式,就是目标接口有多个方法,用一个抽象类实现接口,重写每个方法提供默认实现(空方法),子类按需重写父类方法。
比如上面的例子,中文也有很多类型啊,普通话、广州话、潮汕话、客家话、上海话等,但现在只需要转成普通话:
interface ChineseTarget {
void speakChinese(String talk); // 普通话
void speakCantonese(String talk); // 广州话
void speakChiuchow(String talk); // 潮汕话
void speakHakka(String talk); // 客家话
}
public abstract class BaseAdapter implements ChineseTarget {
@Override public void speakChinese(String talk) { }
@Override public void speakCantonese(String talk) { }
@Override public void speakChiuchow(String talk) { }
@Override public void speakHakka(String talk) { }
}
public class CantoneseTranslator extends BaseAdapter {
private English english;
public CantoneseTranslator(English english) { this.english = english; }
@Override public void speakCantonese(String talk) {
english.speakEnglish(talk);
System.out.println("假装请求了翻译接口,输出翻译结果:【广州话】玛卡巴卡");
}
}
// 测试用例
public static void main(String[] args) {
BaseAdapter translator = new CantoneseTranslator(new English());
translator.speakCantonese("Ma ka ba ka");
}
复制代码
运行输出结果如下:
最后说下适配器模式的有优缺点,先是优点:
- 目标类与适配者类解耦,引用适配器类来重用现有待适配者类,无需修改原有结构;
- 增加了类的透明性和复用性,将具体业务实现封装在适配器类中,适配者类改动只影响适配器类;
- 灵活性和扩展性非常好,可以方便地替换适配器类,可将多个不同的适配者类和子类匹配到同一个目标类上;
- 符合开闭(OCP)原则和里氏替换原则(LSP);
缺点:
- 过度嵌套会导致接口臃肿,一个目标类功能下先,会影响整条适配链;
- 目标接口依赖太多适配接口,修改目标接口会导致所有适配接口都要定制修改;
- Java中,类适配器目标抽象类只能为接口,且适配者类不能为最终类(final,不能继承);
0x4、加餐:四种结构性设计模式对比
这四种模式的代码结构非常相似,笼统地来说,它们都可以称为 Wrapper模式
,即通过Wrapper类二次封装原始类。但它们的用意完全不同(解决的问题、应用场景),下面简单地说下区别。
- 代理模式:不改变原始类接口条件下,为其定义一个代理类,主要目的是控制访问,而非加强,与装饰器模式最大的不同。
- 桥接模式:解决类继承因广度引起的类爆炸问题,将接口部分与实现部分分离,从而使得它们可以较为容易、相对独立的变化。
- 装饰器模式:解决类继承因深度引起的类爆炸问题,在不改变原始类接口的情况下,进行功能增强,且支持多个装饰器嵌套使用。
- 适配器模式:一种事后补救策略,实现不同接口功能间的转换。