适配器模式
适配器模式可以将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能在一起工作的那些类可以一起工作。
适配器模式是一种对象结构型模式,这里的接口不仅仅是java语言中的interface,更多是指一个类型所具有的方法特征集合,是一种逻辑上的抽象。
客户端需要一个target(目标)接口,但是不能直接重用已经存在的adaptee(适配者)类,因为它的接口和target接口不一致,所以需要adapter(适配器)将adaptee转换为target接口。前提是target接口和已存在的适配者adaptee类所做的事情是相同或相似,只是接口不同且都不易修改。如果在设计之初,最好不要考虑这种设计模式。凡事都有例外,就是设计新系统的时候考虑使用第三方组件,因为我们就没必要为了迎合它修改自己的设计风格,可以尝试使用适配器模式。
一个典型的例子是:
Sun公司在1996年公开了Java语言的数据库连接工具JDBC,JDBC使得Java语言程序能够与数据库连接,并使用SQL语言来查询和操作数据。JDBC给出一个客户端通用的抽象接口,每一个具体数据库引擎(如SQL Server、Oracle、MySQL等)的JDBC驱动软件都是一个介于JDBC接口和数据库引擎接口之间的适配器软件。抽象的JDBC接口和各个数据库引擎API之间都需要相应的适配器软件,这就是为各个不同数据库引擎准备的驱动程序。
与装饰器模式类似,适配器模式也是对原有代码进行包装,在符合开闭原则的条件下扩展其功能。不同的是,装饰器模式是单纯的对类进行扩展,而适配器模式是将实现 A 接口的类扩展为实现 B 接口的类,是原本不兼容的类可以一起工作。
适配器模式有三种角色:
目标角色(target):这是客户锁期待的接口。目标可以是具体的或抽象的类,也可以是接口
适配者角色(adaptee):已有接口,但是和客户器期待的接口不兼容。
适配器角色(adapter):将已有接口转换成目标接口。
调用方期待调用一个 Target 接口的实现类,但实际上需要使用 Adaptee 接口实现类的功能。我们通过 Adapter 对两者进行适配,基于 Adaptee 实现类的方法实现 Target,使客户端得以使用 Adaptee。
要做到这一点我们有三种方式,一是类适配器。Adapter 即实现 Target ,又继承 Adaptee 的实现类。通过继承调用 Adaptee 实现类的方法,通过 Target 中的具体方法暴露给调用方。
比如:
public interface Target { public void doWork(); }
public class Work { public void work() { System.out.println("i am working"); } }
public class WorkAdepter extends Work implements Target { @Override public void doWork() { System.out.println("before work"); super.work(); } }
这样我们将 Work 中的方法通过 WorkAdepter 暴露了出去,且符合 Target 接口的定义。
第二种方式是对象适配器,通过持有 Adeptee 实现类对象的引用进行方法调用:
public class WorkAdepter implements Target { private Work work; WorkAdepter(Work work) { this.work = work; } @Override public void doWork() { System.out.println("before work"); work.work(); } }
第三种方式是缺省类适配器,当不需要全部实现接口提供的方法时,可以设计一个适配器抽象类实现接口,并为接口中的每个方法提供默认方法,抽象类的子类就可以有选择的覆盖父类的某些方法实现需求,它适用于一个接口不想使用所有的方法的情况。在java8后,接口中可以有default方法,就不需要这种缺省适配器模式了。接口中方法都设置为default,实现为空,这样同样同样可以达到缺省适配器模式同样的效果。
比如我们的 Target 是这样的:
public interface Target { public default void doWork(){}; public default void method1(){}; public default void method2(){}; public default void method3(){}; public default void method4(){}; public default void method5(){}; }
我们适配时只想调用 doWork,其它方法与我们无关。将方法定义为 default 并添加空实现,我们只需要重写需要进行适配的方法即可,比如只重写 doWork:
public class WorkAdepter implements Target { private Work work; WorkAdepter(Work work) { this.work = work; } @Override public void doWork() { System.out.println("before work"); work.work(); } }