点赞再看,养成习惯,公众号搜一搜【一角钱技术】关注更多原创技术文章。本文 GitHub org_hejianhui/JavaStudy 已收录,有我的系列文章。
前言
- 23种设计模式速记
- 单例(singleton)模式
- 工厂方法(factory method)模式
- 抽象工厂(abstract factory)模式
- 建造者/构建器(builder)模式
- 原型(prototype)模式
- 享元(flyweight)模式
- 外观(facade)模式
- 持续更新中......
23种设计模式快速记忆的请看上面第一篇,本篇和大家一起来学习适配器模式,适配器模式包含类的适配器模式和对象的适配器模式。
模式定义
将一个类的接口转换成客户端希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式的形式分为:类的适配器模式 & 对象的适配器模式。
类的适配器模式
类的适配器模式是把适配的类的API转换成为目标类的API。
在上图中可以看出:
- 冲突:Target期待调用operation方法,而Adaptee并没有(这就是所谓的不兼容了)。
- 解决方案:为使Target能够使用Adaptee类里的SpecificOperation方法,故提供一个中间环节Adapter类(继承Adaptee & 实现Target接口),把Adaptee的API与Target的API衔接起来(适配)。
Adapter与Adaptee是继承关系,这决定了这个适配器模式是类的
使用步骤(代码解析)
步骤1: 创建Target接口;
interface Target {
//这是源类Adapteee没有的方法
void operation();
}
步骤2: 创建源类(Adaptee)
class Adaptee {
public void SpecificOperation() {
}
}
步骤3: 创建适配器类(Adapter)
//适配器Adapter继承自Adaptee,同时又实现了目标(Target)接口。
class Adapter extends Adaptee implements Target {
//目标接口要求调用operation()这个方法名,但源类Adaptee没有方法operation()
//因此适配器补充上这个方法名
//但实际上operation()只是调用源类Adaptee的SpecificOpertaion()方法的内容
//所以适配器只是将SpecificOpertaion()方法作了一层封装,封装成Target可以调用的operation()而已
@Override
public void operation() {
this.SpecificOperation();
}
}
步骤4:定义具体使用目标类,并通过Adapter类调用所需要的方法从而实现目标
public class AdapterPattern {
public static void main(String[] args) {
Target mAdapter = new Adapter();
mAdapter.operation();
}
}
对象的适配器模式
与类的适配器模式相同,对象的适配器模式也是把适配的类的API转换成为目标类的API。
与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用委派关系连接到Adaptee类。
在上图中可以看出:
- 冲突:Target期待调用operation方法,而Adaptee并没有(这就是所谓的不兼容了)。
- 解决方案:为使Target能够使用Adaptee类里的SpecificOperation方法,故提供一个中间环节Adapter类(包装了一个Adaptee的实例),把Adaptee的API与Target的API衔接起来(适配)。
Adapter与Adaptee是委派关系,这决定了适配器模式是对象的。
使用步骤(代码解析)
步骤1: 创建Target接口;
interface Target {
//这是源类Adapteee没有的方法
void operation();
}
步骤2: 创建源类(Adaptee)
class Adaptee {
public void SpecificOpertaion(){
}
}
步骤3: 创建适配器类(Adapter)(不适用继承而是委派)
class Adapter implements Target{
// 直接关联被适配类
private Adaptee adaptee;
// 可以通过构造函数传入具体需要适配的被适配类对象
public Adapter (Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void operation() {
// 这里是使用委托的方式完成特殊功能
this.adaptee.SpecificOpertaion();
}
}
步骤4:定义具体使用目标类,并通过Adapter类调用所需要的方法从而实现目标
public class AdapterPattern {
public static void main(String[] args) {
// 步骤4:定义具体使用目标类,并通过Adapter类调用所需要的方法从而实现目标
//需要先创建一个被适配类的对象作为参数
Target mAdapter = new Adapter(new Adaptee());
mAdapter.operation();
}
}
两种适配器比较
- 对象适配器: 使用组合的方式, 不仅能适配一个被适配者的类, 还可以适配它的任何一个子类;
- 类适配器: 只能适配一个特定的类, 但是它不需要重新实现整个被适配者的功能. 而且它还可以重写被适配者的行为;
- 对象适配器: 使用的是组合而不是继承, 通过多写几行代码把事情委托给了被适配者. 这样很灵活;
- 类适配器: 需要一个适配器和一个被适配者, 只需要一个类就行;
- 对象适配器: 对适配器添加的任何行为对被适配者和它的子类都起作用;
...
解决的问题
从模式的定义中,我们看到适配器模式就是用来转换接口,解决不兼容问题的。想想我们现实生活中的适配器,最常用的就是手机充电器了,也叫做电源适配器,它把家用交流强电转换为手机用的直流弱电。其中交流电就是被适配者,充电器是适配器,手机是用电客户。
原本由于接口不兼容而不能一起工作的那些类可以在一起工作。
模式组成
组成(角色) | 作用 |
---|---|
客户(Client) | 只能调用目标接口功能,不能直接使用被适配器,但可以通过适配器的接口转换间接使用被适配器。 |
目标接口(Target) | 客户看到的接口,适配器必须实现该接口才能被客户使用。 |
适配器(Adapter) | 适配器把被适配者接口转换为目标接口,提供给客户使用。 |
被适配者(Adaptee) | 被适配者接口与目标接口不兼容,需要适配器转换成目标接口子类,才能被客户使用。 |
实例说明
在这里使用类适配器模式进行举例,对象适配器模式只是在适配类实现时将“继承”改成“在内部委派Adaptee类”而已。
实例概况
- 背景:隔壁老王买了一个进口的电视机
- 冲突:进口电视机要求电压(110V)与国内插头标准输出电压(220V)不兼容
- 解决方案:设置一个适配器将插头输出的220V转变成110V
即适配器模式中的类的适配器模式
使用步骤
步骤1: 创建Target接口(期待得到的插头):能输出110V(将220V转换成110V)
interface Target {
//将220V转换输出110V(原有插头(Adaptee)没有的)
void convert_110v();
}
步骤2: 创建源类(原有的插头)
class PowerPort220V{
//原有插头只能输出220V
public void output_220v(){
}
}
步骤3:创建适配器类(Adapter)
class Adapter220V extends PowerPort220V implements Target{
//期待的插头要求调用convert_110v(),但原有插头没有
//因此适配器补充上这个方法名
//但实际上convert_110v()只是调用原有插头的output_220v()方法的内容
//所以适配器只是将output_220v()作了一层封装,封装成Target可以调用的convert_110v()而已
@Override
public void convert_110v(){
this.output_220v();
}
}
步骤4:定义具体使用目标类,并通过Adapter类调用所需要的方法从而实现目标(不需要通过原有插头)
//进口电视类
class ImportedMachine {
@Override
public void Work() {
System.out.println("进口电视正常运行");
}
}
//通过Adapter类从而调用所需要的方法
public class AdapterPattern {
public static void main(String[] args) {
Target mAdapter220V = new Adapter220V();
ImportedMachine mImportedMachine = new ImportedMachine();
//用户拿着进口电视插上适配器(调用Convert_110v()方法)
//再将适配器插上原有插头(Convert_110v()方法内部调用Output_220v()方法输出220V)
//适配器只是个外壳,对外提供110V,但本质还是220V进行供电
mAdapter220V.convert_110v();
mImportedMachine.Work();
}
}
输出结果
进口电视正常运行
优点
- 转换接口,适配器让不兼容的接口变成兼容。
- 让客户和实现的接口解耦。有了适配器,客户端每次调用不兼容的接口时,不用修改自己的代码,只要调用适合的适配器就可以了。
- 使用了对象组合设计原则。以组合的方式包装被适配者,被适配者的任何子类都可以搭配着同一个适配器使用。
- 体现了“开闭”原则。适配器模式把客户和接口绑定起来,而不是和具体实现绑定,我们可以使用多个配适器来转换多个后台类,也可以很容易地增加新的适配器。
缺点
- 每个被适配者都需要一个适配器,当适配器过多时会增加系统复杂度,降低运行时的性能。
- 实现一个适配器可能需要下一番功夫,增加开发的难度。
应用场景
- 当要使用的两个类所做的事情相同或者相似,但是具有不同的接口时考虑使用配适器模式。
- 当需要统一客户端调用接口的代码,而所调用的接口具有不兼容问题时使用适配器模式。这样客户端只有调用一个接口就行了,这样可以更简单、更直接、更紧凑。
建议尽量使用对象的适配器模式,多用合成/聚合、少用继承。
当然,具体问题具体分析,根据需要来选用合适的实现方式。
源码中的应用
#JDK
java.util.Arrays#asList()
java.util.Collections#list()
java.util.Collections#enumeration()
java.io.InputStreamReader(InputStream) (returns a Reader)
java.io.OutputStreamWriter(OutputStream) (returns a Writer)
java.util.collections#enumeration(),从Iterator到Enumeration的适配。
#Spring
org.springframework.context.event.GenericApplicationListenerAdapter
Arrays.asList()
使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
说明: asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList 体现的是适配器模式,只是转换接口,后台的数据仍是数组。
GenericApplicationListenerAdapter
spring架构体系中的事件模型,面向事件编程可以使你的应用扩展性更好,设计更优美,更有设计感,也是解耦最常用的方式,首先看下类图。
ApplicationListener 事件监听器接口,基于观察者模式实现。
GenericApplicationListener 处理基于通用的事件监听器接口,提供了一种基于事件类型的监测,如下:
boolean supportsEventType(ResolvableType eventType);
是SmartApplicationListener的改良版本。
SmartApplicationListener 基于事件的监听器接口,如下:
boolean supportsEventType(Class extends ApplicationEvent> eventType);
ApplicationListenerMethodAdapter GenericApplicationListener适配器实现,如下:
public class ApplicationListenerMethodAdapter implements GenericApplicationListener
可以看到是通过实现接口这种方式的适配器模式实现。
为什么实现接口这种方式比继承类这种实现扩展性更好,java是单继承,用实现接口这种方式可以间接的实现的多继承,扩展性更好。
SourceFilteringListener 基于GenericApplicationListener,SmartApplicationListener的装饰器模式实现,从指定的事件源筛选事件,调用它的委托侦听器来匹配应用程序事件对象。
GenericApplicationListenerAdapter GenericApplicationListener适配器模式实现。
PS:以上代码提交在 Github :https://github.com/Niuh-Study/niuh-designpatterns.git
文章持续更新,可以公众号搜一搜「 一角钱技术 」第一时间阅读,本文 GitHub org_hejianhui/JavaStudy 已经收录,欢迎 Star。