设计模式-适配器模式
适配器顾名思义,就是将原本不兼容的东西适配在一起,例如 MacBook 只有 type-c 接口,而我们需要一个网口插网线,就会买个转接器,这其实就是适配器的思想,通过协议转换将原本不兼容的东西变得兼容。
适配器是一种思想,可大可小。上述的例子其实就很小,有一个这样的系统,A系统市场很大,根深蒂固,对外宣称接口定义不会根据你的需求进行更改,B系统是另一个领域的市场大哥,也不会按你的需求进行更改,现在AB系统需要通信,又不想定制修改,就可以由其他厂商开发一个新项目,起适配器的作用,转换 AB 之间的协议。
如果你对 Web 容器有了解,知道 Tomcat、Jetty,它们其实都是遵循 Servlet 规范设计的,而 Servlet 规范要求传入的请求都是 ServletRequest,因此 Tomcat 的连接器中有一个 Adapter 组件,就是将譬如 HttpRequest 等转为 ServletRequest。
适配器实现
适配器模式一般有两种实现方式,基于类的和基于对象的。类适配器基于继承实现,而对象适配器基于组合实现。
基于类的实现
java
public class Adaptee {
public void function1() {
System.out.println("fun1");
}
public void function2() {
System.out.println("fun2");
}
public void functionC() {
System.out.println("funC");
}
}
public class Adapter extends Adaptee implements ITarget {
@Override
public void functionA() {
super.function1();
}
@Override
public void functionB() {
super.function2();
}
}
public interface ITarget {
void functionA();
void functionB();
void functionC();
}
public class Main {
public static void main(String[] args) {
ITarget target = new Adapter();
target.functionA();
target.functionC();
}
}
Adapter 类继承 Adaptee 实现了 ITarget 接口,这样可以无需重写 functionC 方法,因为二者命名和实现是一致的,适合大部分方法还是兼容,只是少量需要重写的情况。
基于对象的实现
java
public class Adaptee {
public void function1() {
System.out.println("fun1");
}
public void function2() {
System.out.println("fun2");
}
public void function3() {
System.out.println("fun3");
}
}
public class Adapter implements ITarget {
private Adaptee adaptee;
@Override
public void functionA() {
adaptee.function1();
}
@Override
public void functionB() {
adaptee.function2();
}
@Override
public void functionC() {
adaptee.function3();
}
}
public interface ITarget {
void functionA();
void functionB();
void functionC();
}
基于组合的实现其实与我们说的用组合代替继承的思想一致,委托给真正的执行者执行。
至于基于对象和基于类的适配器该如何选择,取决于以下方面:
- 如果 Adaptee 接口并不多,那两种实现方式都可以
- 如果 Adaptee 接口数量很多且与 ITarget 不一致,都需要重写,那就选择基于对象的组合实现
- 如果 Adaptee 接口数量很多且与 ITarget 基本一致,那么就可以选择基于类的,但是要考虑接口的稳定性,如果频繁变更,那完全可以也选择组合实现
使用场景
- 封装有缺陷的设计,例如原来的方法有漏洞但是其他系统不影响,就可以针对这个系统单独再封装一次进行适配
- 统一多个类的接口设计,例如有三个过滤方法ABC,A的参数是 String,B的也是 String, C的是 String,String(第一个参数是要过滤的关键词,第二个是替换为什么,例如***),就可以使用适配器兼容
- 替代依赖的外部系统,B站有一个视频利用 etcd 代替 redis,使得 redisTemplate 操作的中间件无感切换为 etcd,设计模式-适配器模式
- 兼容老版本接口,老版本的接口依然保留,但实现切换为新版本的操作。JDK1.0 中包含一个遍历集合容器的类 Enumeration。JDK2.0 对这个类进行了重构,将它改名为 Iterator 类,并且对它的代码实现做了优化。为了做到兼容使用低版本 JDK 的老代码,暂时保留 Enumeration 类,并将其实现替换为直接调用 Itertor。