设计模式之适配器模式
前言
小六新买了一个小米6手机,它高高兴兴的拿到新手机,想要插上耳机听歌,但发现手机没有耳机孔,仔细查看说明书之后发现,小米6手机是充电孔耳机孔在一起,在插耳机时需要一个耳机转接器,才能插耳机。我们用程序员的眼观来看,这里相当于增加了一个转接器类用于适配耳机,这就类似于我们今天提到的设计模式—适配器模式。
一、适配器模式实现
适配器模式的类图如下所示:
其中:
- Client:客户端,调用自己需要的接口Target
- Target:定义客户端需要的跟特定需求相关的接口
- Apaptee:已存在接口,通常满足功能需求但与特定需求接口不一致
- Adapter:适配器,将Adaptee适配为Client需要的Target接口。
适配器模式的主要功能是进行转换匹配,用来复用已有的功能。适配器模式将某个类的接口转换成客户端期望的另一个接口,目的是消除由于接口不匹配所造成的类的兼容性问题。主要分为三类:类适配器模式、对象适配器模式、接口适配器模式。
三种适配器模式有各自的应用场景:
- 类的适配器模式:将一个类转换成满足另一个新接口的类,创建一个新类,继承原有的类,实现新的接口即可。
- 对象的适配器模式:将一个对象转换成满足另一个新接口的对象,可以创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法即可。
- 接口的适配器模式:当不希望实现一个接口中所有的方法时,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可。
下面针对三种情况进行代码实现:
1、类适配器模式
核心思想就是:有一个Source类,拥有一个方法,待适配,目标接口是Targetable,通过Adapter类,将Source的功能扩展到Targetable里,类图如下所示:
代码如下:
public class Source {
public void method1() {
System.out.println("this is original method!");
}
}
public interface Targetable {
/* 与原类中的方法相同 */
public void method1();
/* 新类的方法 */
public void method2();
}
Adapter类继承Source类,实现Targetable接口:
public class Adapter extends Source implements Targetable {
@Override
public void method2() {
System.out.println("this is the targetable method!");
}
}
这样Targetable接口的实现类就具有了Source类的功能。下面为测试类:
public class AdapterTest {
public static void main(String[] args) {
Targetable target = new Adapter();
target.method1();
target.method2();
}
}
2、对象适配器模式
基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承Source类,而是持有Source类的实例,以达到解决兼容性的问题。如图:
只需要修改Adapter类的源码即可:
public class Wrapper implements Targetable {
private Source source;
public Wrapper(Source source){
super();
this.source = source;
}
@Override
public void method2() {
System.out.println("this is the targetable method!");
}
@Override
public void method1() {
source.method1();
}
}
测试类:
public class AdapterTest {
public static void main(String[] args) {
Source source = new Source();
Targetable target = new Wrapper(source);
target.method1();
target.method2();
}
}
输出与第一种一样,只是适配的方法不同而已。
3、接口适配器模式
接口的适配器模式是当一个接口中有多个抽象方法,但只需要使用其中某些方法,可借助一个抽象类实现该接口的所有的方法,而我们只需写一个类继承该抽象类,重写需要的方法即可。类图如下:
示例代码如下:
接口Sourceable:
public interface Sourceable {
public void method1();
public void method2();
}
抽象类Wrapper2:
public abstract class Wrapper2 implements Sourceable{
public void method1(){}
public void method2(){}
}
实现类:
public class SourceSub1 extends Wrapper2 {
public void method1(){
System.out.println("the sourceable interface's first Sub1!");
}
}
public class SourceSub2 extends Wrapper2 {
public void method2(){
System.out.println("the sourceable interface's second Sub2!");
}
}
二、适配器模式说明
适配器模式的本质是:转换匹配,复用功能。适配器模式中被适配的接口Adaptee与适配的接口Target没有关系,他们中的方法可以相同,也可以完全不同,适配器模式的实现方式是通过组合对象的方式进行的,将功能委托给被适配的对象进行的。
适配器模式调用的序列图如下所示:
适配器模式的实现有以下几种:
-
常见适配:适配器类会实现接口,在实现过程中调用待适配的类中的方法
-
智能适配器:在适配器类中实现接口中定义的新方法,通常来说,适配器类中既可以通过借助继承类中的方法实现高层功能,也可以实现接口中定义的新方法,进行功能扩展。
-
缺省适配:即对接口的缺省实现,即接口适配器模式。
此外,在适配过程中,可能接口功能的实现需要多个待适配类中的方法交互才能满足需求,即同时适配多个类。适配实现的复杂度取决于待适配类与接口的相似度,相似程度越高,适配类的实现难度越低。
在实际项目过程中,通常会存在两个版本共存的情况,这就是需要使用到双向适配器。示例代码如下:
两个版本的实现代码:
public interface Targetable1 {
public void produce1();
}
public class Target1 implements Targetable1 {
@Override
public void produce1() {
System.out.println("Targetable1的produce1实现");
}
}
public interface Targetable2 {
public void produce2();
}
public class Target2 implements Targetable2 {
@Override
public void produce2() {
System.out.println("Targetable2的produce2实现");
}
}
适配器类的代码如下:
public class Adapter implements Targetable1, Targetable2 {
private Targetable1 target1;
private Targetable2 target2;
@Override
public void produce1() {
target1.produce1();
}
@Override
public void produce2() {
target2.produce2();
}
}
实际上,在使用适配器过程中存在一个问题:被适配的对象不兼容Adapter适配器类,这使得适配器类的适用范围受到限制。而双向适配器则解决了这样的问题,可以满足不同客户采用不同方式查看同一不同对象的需求。
三、适配器模式总结
1、应用场景
- 实现已实现接口的复用,利用已有的代码实现功能。
- 使用已存在的部分子类,可选用随想适配器,适配子类的父类。
2、优缺点
优点:
- 更好的复用性。适配器模式可复用已实现接口的兼容。
- 更好的扩展性。实现适配器的过程中可以调用自己开发的功能,实现系统的扩展。
缺点:
过多使用适配器,系统会比较混乱,不易理解
参考:文章主要参考《研磨设计模式》一书