总结适配器设计模式——看Netty是如何转换的Callable和Runnable接口
目录
类的适配器
对象的适配器
使用场景
优缺点
实际开发中该如何选择使用哪一种适配器
适配器模式和外观模式区别
适配器模式和装饰模式的区别
适配器模式和代理模式的区别
JDK 中使用适配器的例子
Netty中使用适配器的例子
本篇文章大概3000字,阅读时间大约10分钟
本文重点总结了适配器设计模式的理论和用法,并且以Netty源码为契机,看看该模式是如何被使用的。
适配器模式的定义很简单,就是做一个中间转换,类似港版的iPad,iPhone 等电源插头,需要一个转换器才能使用大陆的插座。类似的,适配器模式就是做转换用的,是一个比较常用的结构型设计模式,它有两种实现方式,一个是类的适配器,一个是对象的适配器。
01
—
类的适配器
被适配的类——Adaptee ,可以看成是一个旧系统里的稳定的类:
public class Adaptee { // 被适配的类
public void request() {
System.out.println("被适配的类:request");
}
}
下面是新接口,或者说需要适配的目标接口ITarget:
public interface ITarget { // 新的接口
void doTargetRequest();
}
//////////////////////////////////////////////////////
public class NewTarget implements ITarget {
public void doTargetRequest() {
System.out.println("NewTarget的目标方法:doRequest");
}
}
类的适配器通过继承实现,下面是适配器类Adapter,通过继承被适配者——Adaptee,把被适配者的API—— request适配给目标接口ITarget的 API——doTargetRequest:
public class Adapter extends Adaptee
implements ITarget {
// 类的适配器:Adapter(通过继承实现)
public void doTargetRequest() {
// 这里可以写一些业务代码,如果有参数,就是对参数的转换过程
// 通过 Adapter,把被适配者的API——request,
// 适配给了目标接口——doTargetRequest。实现了对既有代码的复用
super.request();
// 这里可以写一些业务代码
}
}
客户端调用:
public class Main {
public static void main(String[] args) {
ITarget target = new NewTarget();
target.doTargetRequest(); // NewTarget的目标方法:doRequest
ITarget iTarget = new Adapter();
iTarget.doTargetRequest(); // 被适配的类:request
}
}
类图如下:
02
—
对象的适配器
类适配器通过继承被适配的类实现,而对象的适配器通过组合被适配类的引用实现。Itarget接口,Adaptee 类都不变,适配器类Adapter2如下:
public class Adapter2 implements ITarget {
private Adaptee adaptee; // 通过组合的方式
public Adapter2(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void doTargetRequest() {
// 这里可以写一些业务代码,如果有参数,就是对参数的转换过程
// 实现了对既有代码的复用,通过 Adapter,
// 把被适配者的API——request,适配给了目标接口——doTargetRequest
this.adaptee.request();
// 这里可以写一些业务代码
}
}
客户端调用:
public class Main2 {
public static void main(String[] args) {
ITarget target = new NewTarget();
target.doTargetRequest(); // NewTarget的目标方法:doRequest
Adaptee adaptee = new Adaptee();
ITarget iTarget = new Adapter2(adaptee);
iTarget.doTargetRequest(); // 被适配的类:request
}
}
类图如下:
03
—
适配器使用场景
可以将该模式看做是一个补偿机制,应用这种模式算是"无奈之举”,如果设计之初就能规避一些不兼容的问题,那么就不需要使用该模式,但是往往事与愿违,我们无法未卜先知,并且有时候还涉及到权限的问题,一些代码我们无法改变。主要场景如下:
1、增强老代码能力
如果一个类已经稳定存在,你不想(或者说没有权限,比如依赖库)修改它,但是还要为其增加新需求,且这个需求和类的现有接口不能匹配,那么就可以使用适配器模式实现接口的转换以便满足新需求
2、软件的升级
比如升级有缺陷的接口,且该接口已经提供给了用户,我们需要做到无感知升级
3、统一多个类的接口
比如某个功能的实现依赖多个外部系统(或者说类)。通过适配器模式将它们的接口适配为统一的接口,然后就可以使用多态的特性来复用代码逻辑。
4、替换依赖的外部系统
比如当把项目中依赖的一个外部系统替换为另一个外部系统的时候,利用适配器模式,可以减少对代码的改动
5、软件的废弃接口设计
一般我们会标记废弃接口为@Deprecated,此时可以将其内部实现逻辑委托为新的接口实现,让使用它的项目有个过渡期。比如,JDK1.0中遍历集合容器的类Enumeration。JDK2.0 对这个类进行了重构,将它改名为Iterator并对它做了优化。但考虑到如果将Enumeration直接从JDK2.0删除,那使用JDK1.0的项目如果切换到JDK2.0,就会编译不过。为了避免这种情况发生,JDK选择使用适配器模式对废弃接口进行适配。
6、适配不同格式的数据
适配器模式除了用于接口适配,还能用在不同格式的数据之间的适配。比如Java中的Arrays.asList()可以看作一种数据适配器,它将数组类型的数据转化为集合容器类型。
04
—
适配器优缺点
它的优点很直接,就是提高类的的稳定性,增强其复用性,在不改变现有类的前提下,拓展类的功能,且能解耦目标类和适配器类,符合了开闭原则。
缺点就是会增加系统复杂度,其实我个人觉得微不足道
05
—
实际开发中该如何选择使用哪一种适配器?
判断标准主要有两个,一个是Adaptee(被适配)接口的个数,另一个是Adaptee和ITarget(新接口)的契合程度,具体的:
1、如果Adaptee接口不多,两种实现方式都可以
2、如果Adaptee接口很多且Adaptee和ITarget接口定义大部分都相同,那推荐使用类适配器,因为Adaptor复用父类Adaptee的接口,比对象适配器的实现方式,代码要少一些
3、如果Adaptee接口很多且Adaptee和ITarget接口定义大部分都不相同,那推荐使用对象适配器,因为组合结构相对于继承更加灵活
06
—
适配器模式和外观模式的区别?
他俩都是结构型模式,外观模式是为旧接口(一般是比较复杂的接口)提供一个对外的简单接口,或者说更方便使用的访问入口。而适配器模式专注的是让两个已有的接口协同工作。
更一般的看,外观模式也是一种特殊的适配器,只不过它适配的是整个系统(粒度更大一些),为一个系统提供一个方便访问的对外接口,比如API网关就是典型的外观模式的应用场景。
07
—
适配器模式和装饰模式的区别?
适配器模式用来转换不同接口,使旧接口和新接口能无缝衔接,而装饰模式不会改变接口,只是给接口增加新功能,并且支持多个装饰器的嵌套使用,联想JDK的I/O包。
08
—
适配器模式和代理模式的区别?
代理模式是在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,屏蔽一些实现细节,而非加强功能,联想spring的AOP,以及RPC框架的动态代理。。。这是它跟装饰模式,以及适配器模式最大的不同
09
—
JDK中使用适配器的例子
最常见的有 I/O 包里的字符包装流,InputStreamReader、OutputStreamWriter 等,还有util包里的Arrays.asList() 方法。
10
—
Netty里使用对象适配器实现接口转换
文章:里,简单提到,Netty提交定时任务,支持带返回结果的Callable任务,也支持Runnable任务,但是底层确共用了一套定时任务保存和调度的逻辑,这其中就使用了适配器模式来让两个任务接口无缝衔接。
如下,只看核心的实现代码,最终Netty的NIO线程执行定时任务统一调用的是Callable的call方法,对于Runnable的run方法,使用了适配器进行适配,黄色1的Callable是新接口的角色,黄色3的Runnable是需要被适配的接口,即老接口的角色:
黄色2是适配器本身,显然,这是一个对象的适配器实现,通过组合的方式,在适配器里组合了需要被适配的Runnable,然后在新接口的API——call里,委托老接口的功能实现。
以上,就实现了适配功能,代码实现上确实不复杂。
END
点亮在看,你最好看
~
点阅读原文,获得更多精彩内容