对于适配器相信不会陌生,生活中的例子比比皆是,像耳机转接线,充电器适配器,水管适配接口等等。通过类比很容易理解软件中的适配器模式。
客户端需要一个target(目标)接口,但是不能直接重用已经存在的adaptee(适配者)类,因为它的接口和target接口不一致,所以需要adapter(适配器)将adaptee转换为target接口。前提是target接口和已存在的适配者adaptee类所做的事情是相同或相似,只是接口不同且都不易修改。如果在设计之初,最好不要考虑这种设计模式。凡事都有例外,就是设计新系统的时候考虑使用第三方组件,因为没必要为了迎合第三方组件修改自己的软件设计风格,可以尝试使用适配器模式。
下面是一个非常典型的使用适配器模式的场景:
Sun公司在1996年公开了Java语言的数据库连接工具JDBC,JDBC使得Java语言程序能够与数据库连接,并使用SQL语言来查询和操作数据。JDBC给出一个客户端通用的抽象接口,每一个具体数据库厂商(如SQL Server、Oracle、MySQL等)的JDBC驱动软件都是一个介于JDBC接口和数据库引擎接口之间的适配器软件。抽象的JDBC接口和各个数据库引擎API之间都需要相应的适配器软件,这就是为各个不同数据库引擎准备的驱动程序。
另外一个比较典型的适配器场景J2EE规范与J2EE规范实现的服务器。SUN公司提供了一套J2EE规范,然后不同厂商根据自己的理解实现了不同的应用服务器。SUN公司提供了一套servlet api规范,然后实现这套规范的著名应用服务器有Apache Tomcat、Jetty、Oracle 的 Weblogic、IBM 的 WebSphere 等。
适配器模式的UML类图如下
从类图上看主要包含如下角色:
目标角色(target):这是客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口;
适配者角色(adaptee):已存在接口(可以理解是第三方提供的接口),但是和客户端期待的接口不兼容;
适配器角色(adapter):将已有接口转换成目标接口(可以理解我方需要的接口);
0x02:适配器模式的实现
- 类适配器模式(class adapter pattern)
通过继承进行适配(类间继承)。类适配器模式在编译时实现target(目标)接口。这种适配器模式使用了多个实现了期待的接口或者已经存在的接口的多态接口。比较典型的就是:target接口被创建为一个纯粹的接口,Java不支持多继承的语言。
Target:Target目标角色,该角色定义把其他类转换为何种接口,也就是期望接口,通常情况下是一个接口或一个抽象类,一般不会是实现类
代码语言:javascript复制public interface Target {
public void request();
}
Adaptee:Adaptee源角色,想把谁转换为目标角色,这个“谁”就是源角色,它是已经存在的、运行良好的类或对象
代码语言:javascript复制public class Adaptee {
public void specificRequest() {
System.out.println("我是已经存在的运行良好的第三方厂商");
}
}
Adapter:Adapter适配器角色,是适配器模式的核心角色,它的职责是通过继承或是类关联的方式把源角色转换为目标角色
代码语言:javascript复制public class Adapter extends Adaptee implements Target {
@Override
public void request() {
super.specificRequest();
}
}
ConcreteTarget:目标角色的实现类
代码语言:javascript复制public class ConcreteTarget implements Target {
@Override
public void request() {
System.out.println("没有增加适配器的我方普通实现逻辑");
}
}
类适配器模式测试代码
代码语言:javascript复制public class Client {
public static void main(String[] args) {
//原有无适配器的业务逻辑
Target target = new ConcreteTarget();
target.request();
//增加适配器后的业务逻辑
Target target2 = new Adapter();
target2.request();
}
}
- 对象适配器模式(object adapter pattern)
通过对象层次的关联关系进行委托(对象的合成关系/关联关系)。对象适配器模式在运行时实现target(目标)接口。在这种适配器模式中,适配器包装了一个类实例。在这种情况下,适配器调用包装对象实例的方法。
Target:客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口
代码语言:javascript复制public class Target {
public void request() {
System.out.println("没有适配器的普通请求");
}
}
Adaptee:需要适配的类
代码语言:javascript复制public class Adaptee {
public void specificRequest() {
System.out.println("适配器类实现的特殊请求");
}
}
Adapter:通过在内部包装一个Adaptee对象,把源接口转换成目标接口
代码语言:javascript复制public class Adapter extends Target {
private Adaptee adaptee = new Adaptee();
@Override
public void request() {
//替换原理的逻辑,调用适配类的逻辑
adaptee.specificRequest();
}
}
对象适配器模式测试代码
代码语言:javascript复制public class Client {
public static void main(String[] args) {
Target target = new Adapter();
target.request();
}
}
- 缺省适配器模式(default adapter pattern),也叫默认适配器模式、接口适配器模式
当不需要全部实现接口提供的方法时,可以设计一个适配器抽象类实现接口,并为接口中的每个方法提供默认方法实现或者空实现(如果大家做过GUI编程,就可以经常遇到这种实现,特别是各种控件的事件监听都提供了适配器类),抽象类的子类就可以有选择的覆盖父类的某些方法实现需求,它适用于一个接口不想使用所有的方法的情况。在java8后,接口中可以有default方法,就不需要这种缺省适配器模式了。接口中方法都设置为default,实现为空,这样同样同样可以达到缺省适配器模式同样的效果。
target:包含了很多没有实现的操作接口
代码语言:javascript复制public interface Target {
public abstract void operation1();
public abstract void operation2();
public abstract void operation3();
}
Adapter:默认实现了所有操作抽象类,只是所有的实现都是空实现
代码语言:javascript复制public abstract class DefaultAdapter implements Target{
@Override
public void operation1() {
}
@Override
public void operation2() {
}
@Override
public void operation3() {
}
}
测试缺省适配器模式需要用到的类(相当于GUI编程的一个组件,比如按钮Button)
代码语言:javascript复制public class Operator {
private Target target;
public void addOperation(Target target) {
this.target= target;
}
public void operation1() {
target.operation1();
}
public void operation2() {
target.operation2();
}
public void operation3() {
target.operation3();
}
}
缺省适配器模式测试代码
代码语言:javascript复制public class Client{
public static void main(String[] args) {
// 原来要实现所有操作类的操作
Operator operator1= new Operator();
operator1.addOperation(new Target() {
@Override
public void operation1() {}
@Override
public void operation2() {
System.out.println("invoke operation2");
}
@Override
public void operation3() {}
});
operator1.operation2();
// 2、使用缺省适配器只需要实现需要用到的接口方法
Operator operator2 = new Operator();
operator2.addOperation(new DefaultAdapter() {
@Override
public void operation2() {
System.out.println("invoke operation2");
}
});
operator2.operation2();
}
}
适配器模式本质上是现有的不兼容的接口转换为需要的接口。类适配器模式以继承现有类的方式转换;对象适配器模式以聚合对象实例的方式转换;接口适配器模式以实现接口的方式转换。适配器模式是在现有的类和系统都不易修改的情况下才使用,在系统设计之初慎用该设计模式。