1.什么是适配器模式?
适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
举个现实生活中的例子,比如我们常见的电器插头,有三线的也有两线的,当遇到插头跟插座不匹配时我们一般需要通过一个转换头来解决,使原本不能正常使用的电器可以正常工作,java中的适配器模式与此类似.
2.为什么要使用适配器模式
在一些场景下,我们可能会需要一些已经存在的接口方法,来完成某项功能,但已经存在的接口并不符合当前要求,但又不能直接去修改该接口,不符合开闭原则,于是我们可以通过适配器模式来解决
我们还可以创建一个可以复用的类,使得该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作,这种情况比较少,也不太推荐,后面会讲原因.
3.实现
适配器模式的实现有两种方式,一种是类适配器,通过继承实现,另一种是对象适配器,通过组合实现,由于Java语言单继承的特性,更推荐使用第二种组合的方式来实现,记住多用组合,少用继承,组合优于继承这句话就对了…
3.1类适配器
如上图所示,类适配器主要由以下几个组件构成:
• Target
— 定义Client使用的与特定领域相关的接口
• Client
— 与符合Target接口的对象协同
• Adaptee
— 定义一个已经存在的接口,这个接口需要适配
• Adapter
— 对Adaptee的接口与Target接口进行适配
这里我们以现实中电脑为例来作实现,帮助理解,我有一台电脑,是三孔插头的,但我家里的插座是两线的,这时候就需要一个转换器来把三线插头转成两线插头,然后插到插座上去充电.代码实现:
/**
* 三线插头
*/
public interface Target {
void powerWithThreePlug();
}
/**
* 电脑-测试类-类适配器(继承实现)
*/
public class Client {
private Target target;
public Client(Target target) {
this.target = target;
}
public void charge() {
System.out.println("正在充电...");
target.powerWithThreePlug();
}
public static void main(String[] args) {
//使用原生插头充电
// Target power = new TargetImpl();
//使用适配器插头充电
Target power = new Adapter();
Client computer = new Client(power);
computer.charge();
}
}
/**
* 被适配类-两孔
*/
public class Adaptee {
public void powerWithTwoPlug(){
System.out.println("使用两孔插头...");
}
}
/**
* 适配器-继承实现
*/
public class Adapter extends Adaptee implements Target{
@Override
public void powerWithThreePlug() {
super.powerWithTwoPlug();
}
}
分别开启Client中使用原生插头和使用适配器插头测试,结果如下:
结果符合预期,使用转换器后,两孔插头也可以完成充电.
3.2对象适配器
如上图所示,对象适配器主要由以下几个组件构成:
• Target
— 定义Client使用的与特定领域相关的接口
• Client
— 与符合Target接口的对象协同
• Adaptee
— 定义一个已经存在的接口,这个接口需要适配
• Adapter
— 对Adaptee的接口与Target接口进行适配
乍看上去好像和使用类适配器没啥两样,其实是不一样的,类适配器是通过继承实现适配的,而对象适配器是通过聚合实现的,前面也有提到,从上图好像比较难看出来两者的差别,下面我直接上代码,注意观察:
/**
* 三线插头
*/
public interface Target {
void powerWithThreePlug();
}
/**
* 三线插头-实现类
*/
public class TargetImpl implements Target {
@Override
public void powerWithThreePlug() {
System.out.println("使用三孔插头...");
}
}
/**
* 电脑-测试类-对象适配器(组合实现)
*/
public class Client {
private Target target;
public Client(Target target) {
this.target = target;
}
public void charge() {
System.out.println("正在充电...");
target.powerWithThreePlug();
}
public static void main(String[] args) {
//使用原生插头充电
// Target power = new TargetImpl();
//使用适配器插头充电
Adaptee adaptee = new Adaptee();
Target power = new Adapter(adaptee);
Client computer = new Client(power);
computer.charge();
}
}
/**
* 被适配类
*/
public class Adaptee {
public void powerWithTwoPlug(){
System.out.println("使用两孔插头...");
}
}
/**
* 适配器-对象适配器
*/
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void powerWithThreePlug() {
adaptee.powerWithTwoPlug();
}
}
测试结果和上面使用类适配器的一毛一样,我就不贴出来了,两者的主要差别就是:
在适配器中,类适配器继承了被适配类Adaptee,然后通过调用父类Adaptee的方法来实现Target类中的方法,从而完成适配;
在适配器中,对象适配器私有化了被适配类对象Adaptee,然后在构造函数中将Adaptee作为参数传递进去,然后直接调用Adaptee中的方法实现Target类中的适配方法,从而完成适配.
前面多次说到对象适配器要比类适配器要好,推荐使用,那么到底好在哪里? 就拿本例来说,如果我们被适配的对象Adaptee它还有子类,而它子类中的方法也被需要适配,这时如果使用继承,有多少子类你就得写多少个适配器,这样会让你代码很臃肿不说,由于JAVA单继承的特性还让你的适配器无法再继承其它的类.
再来看看使用聚合,不管被适配对象有多少个,有多少子类,原则上你只需要写一个适配器就够用了,再多的对象,类,都可以将其私有化进适配器,然后分别调用其方法实现多个接口即可.
很好的体现了java单继承多实现的特点.
然鹅适配器模式在实际开发中用的并不多,一般不会主动把自己代码写成这种,会让你的代码可读性很差,非常不推荐.
能想到为数不多的使用场景是代码重构或者调用别人现有的接口时,可以采用,符合开闭原则.
关于适配器模式就先聊到这里,对设计模式感兴趣的可以继续关注本博,一共有10篇关于常用设计模式的讲解.