概念
适配器模式(Adapter Pattern),是作为两个不兼容的接口之间的桥梁。该设计模式属于结构型模式,它结合了两个独立接口的功能。
其别名为包装器(Wrapper)模式,它既可以作为类结构型模式,也可作为对象结构型模式。在适配器模式中提及的接口是指广义的接口,它可表示一个方法或方法的集合。
这种模式涉及到一个单一的类(适配器类),该类负责整合独立的或不兼容的接口的功能。
实例:家用插座是220V电压,手机充电电压是5V,而充电器就充当了适配器的角色,将220V电压转换成5V电压。
主要解决:在软件系统中,常要将一些"现存的对象"放到新环境中,而新环境要求的功能是现有的对象不能满足的。
使用场景:有动机的修改一个正常运行的接口。系统需使用现有的类,而现有类的接口不完全符合系统需要。想要创建一个可重复使用的类,用于一些彼此之间没有太大关联的类,包括一些可能在将来的引进的类一起工作,这些源类不一定有一致的接口。
优点:让两个没有关联的类一起运行,提高了类的复用,增加了类的透明度,灵活性好。
缺点:过多使用适配器,会让系统非常杂乱,不易进行整体把控。比如,看到的是A接口,其实内部已被适配成了B接口的实现。若没有很大的必要,最好不使用适配器,直接对系统进行重构。
注:适配者不是在详细设计时添加的,而是解决正在服役的项目的问题。
适配器模式将类的接口转换成客户期望的另一个接口表示,主要目的是兼容性,让原本不能在一起工作的两个类能够协同工作。
适配器模式有三种:类适配器,对象适配器,接口适配器。
本文实例当中,需要被适配的类、接口或对象称为源src(source);
最终需要的输出,称为目标des(destination);
适配器称为Adapter。
一句话描述适配器模式就是:
src–>Adapter–>des,也就是src以某种形式给到Adapter里,最终转化为des。
适配就是"源"到"目标"的适配,而其中链接两者之间关系的就是适配器。它负责把"源"过渡到"目标",实现"源"中原本没有的功能,又不破坏"源"本身的结构。
下面就以手机充电为例分别介绍三种适配器模式。
类适配器模式
单一的为某一个类实现适配。
Adapter类,通过继承src类,实现des接口,完成src–>des的适配。
现有的src类Voltage220V
public class Voltage220V {
public int out220V() {
System.out.println("插座输出电压:220V");
return 220;
}
}
想要的des接口
public interface Voltage5V {
int out5V();
}
适配器类Adapter
public class Adapter extends Voltage220V implements Voltage5V {
@Override
public int out5V() {
int src = out220V();
//转换电压
return src/44;
}
}
Client类Mobile
public class Mobile {
//充电方法,需5V电压参数
public void charging(Voltage5V voltage5V) {
System.out.println("当前充电电压:" + voltage5V.out5V() + "V");
}
}
测试类AdaterTest
public class AdapterTest {
public static void main(String[] args) {
Mobile mobile = new Mobile();
mobile.charging(new Adapter());
}
}
打印结果
插座输出电压:220V
当前充电电压:5V
注:Java这种单继承机制,所有需要继承的都不建议优先使用。很显然,Adapter类继承了Voltage220V类,就不能再去继承其它类了,也就是说这个适配器只为Voltage220V这一个类服务,所以称之为类适配器模式。
类适配器要求des必须是接口,有一定局限性,且src中的方法在Adapter中都会暴露出来,增加了使用成本。但同样由于其继承了src类,所以它可根据需求重写src中的方法,增强了Adapter的灵活性。
对象适配器模式
对象适配器模式是把"源"作为一个对象聚合到适配器类中,把"源"作为一个构造函数参数传入适配器,然后执行接口所要求的方法。
这种模式可为多个"源"进行适配,弥补了类适配器模式的不足。
基本解决思路和类适配器相同,只是将Adapter类稍作修改,不让其继承src类,而是持有src类的实例,以解决兼容性的问题。
即:持有src实例,实现des接口,完成src–>des的适配。
根据"合成复用原则",在系统中尽量使用关联关系代替继承关系,因此大部分结构型模式都是对象结构型模式。
在上面实例的基础上修改Adapter类
public class Adapter implements Voltage5V {
private Voltage220V voltage220V;
public Adapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}
public int out5V() {
if (voltage220V != null) {
int src = voltage220V.out220V();
//转换电压
return src/44;
}
return 0;
}
}
测试类代码修改为
Adapter adapter = new Adapter(new Voltage220V());
Mobile mobile = new Mobile();
mobile.charging(adapter);
打印结果为
插座输出电压:220V
当前充电电压:5V
注:对象适配器和类适配器其实是同一种思想,只不过实现方式不同。
根据合成复用原则,组合大于继承,所以它解决了类适配器必须继承src的局限性问题,也不再强制要求des必须是接口。同样,对象适配器的使用成本更低,也更加灵活。
类适配器模式用于单一源的适配,由于源的单一,代码的实现不用写选择逻辑,结构比较清晰;
而对象的适配器模式可用于多源的适配,弥补了类适配器模式的不足,但也由于源的数据可能较多,所以具体实现的分支较多,结构不太清晰。
和装饰器模式的区别是:装饰者是对src的装饰,使用者对src的装饰毫无所觉(使用者用法不变)。这里的对象适配之后,使用者的用法会改变。
接口适配器模式
接口适配器模式也被称之为默认适配器模式或缺省适配器模式。
当想实现一个接口,同时又不想实现接口中的所有方法,只想实现一部分方法时,就会用到接口适配器模式。
这种模式是在接口和具体实现类之间加一个抽象类,用抽象类为该接口的每一个方法提供一个默认的实现(空方法),具体实现类(抽象类的子类)只需重写需要完成的方法即可,适用于不想使用一个接口中所有方法的情况。
示例代码
src接口
public interface Source {
public abstract void A();
public abstract void B();
public abstract void C();
public abstract void D();
public abstract void E();
public abstract void F();
}
抽象类(适配器)
public abstract class Adapter implements Source {
public void A() {}
public void B() {}
public void C() {}
public void D() {}
public void E() {}
public void F() {}
}
最终实现类
public class Destination extends Adapter {
//重写方法A
@Override
public void A() {
System.out.println("A方法。。。");
}
//重写方法E
@Override
public void E() {
System.out.println("E方法。。。");
}
}
总结
适配器就是一种适配中间件,存在于不匹配的二者之间,用于连接二者,将不匹配变得匹配。
三种命名方式可以这样理解:
以src以怎样的形式给到Adapter(在Adapter中的形式)来命名的
类适配器,以类给到,在Adapter中,将src作为一个类,被继承。
对象适配器,以对象给到,在Adapter中,将src作为一个对象,被持有。
接口适配器,以接口给到,在Adapter中,将src作为一个接口,被实现(间接实现)。
类适配器与对象适配器
想要使用一个已经存在的类,但它却不符合现有的接口规范,导致无法直接访问,创建一个适配器就能间接访问接口中的方法。
访问的方法不在合适的接口中。
需要一个统一的输出接口,而输入端类型不可预知。
接口适配器
想要使用接口中的部分方法,但接口中有过多的方法,使用时须实现接口并实现接口中的所有方法。这时可先用一个抽象类实现该接口,不实现该接口中的方法(实现方法置为空),再继承该抽象类,重写想用的方法,该抽象类就是适配器。