持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第29天,点击查看活动详情
概念
适配器模式(Adapter)通常也被称为转换器,顾名思义,它一定是进行适应与匹配工作的物件。当一个对象或类的接口不能匹配用户所期待的接口时,适配器就充当中间转换的角色,以达到兼容用户接口的目的,同时适配器也实现了客户端与接口的解耦,提高了组件的可复用性。
众所周知,反复修改代码的代价是巨大的,因为所有依赖关系都要受到牵连,这不但会引入更多没有必要的重构与测试工作,而且其波及范围难以估量,可能会带来不可预知的风险,结果得不偿失。适配器模式让兼容性问题在不必修改任何代码的情况下得以解决。
实例展示
注:本文内容参考 《秒懂设计模式》一书,本文对其做了概括凝练,主要是为了自身学习使用,如果无法理解,建议查看原书。
生活中需要适配器的场景中还是比较多的,比如手机充电器、调制解调器、插座等等,我们就以插座为例,来介绍一下适配器。
1. 插头与插座(被适配者与目标接口)
假设我们买了一台电视机,其电源插头是两相的,不巧的是墙上的插孔却是三相的,这时电视机便无法通电使用。下面我们先来构造一下三相插孔接口:
public interface TriplePin {
//参数分别为火线、零线、地线
public void electrify(int l, int n, int e);
}
接着我们再来构造两相插孔接口:
public interface DualPin {
//这里没有地线
public void electrify(int l, int n);
}
可以看到两相插孔接口比三相插孔接口少了地线 e。
接下来我们再来定义一下电视机类,电视机类是实现的是二相接口:
public class TV implements DualPin {
@Override
public void electrify(int l, int n) {
System.out.print("火线通电:" + l + ",零线通电:" + n);
System.out.println("电视开机");
}
}
然后我们再来实现下客户端类,我们用三相接口类来实例化电视,结果应该是抛错的:
public class Client {
public static void main(String[] args) {
TriplePin triplePinDevice = new TV(); //接口不兼容,此处报错“类型不匹配”
}
}
2. 插座适配(对象适配)
为了能够让电视插上插座,我们需要做一个适配器来实现电源转换,适配器类实现的应该是三相插孔,这样才能兼容三相插孔。适配器中应该传入二相插孔参数,这样二相插孔的设备才能接入,接下来我们来看看适配器的实现:
public class Adapter implements TriplePin {
private DualPin dualPinDevice;
//创建适配器时,需要把两插设备接入进来
public Adapter(DualPin dualPinDevice) {
this.dualPinDevice = dualPinDevice;
}
//适配器实现的是目标接口
@Override
public void electrify(int l, int n, int e) {
//调用被适配设备的两插通电方法,忽略地线参数e
dualPinDevice.electrify(l, n);
}
}
相关客户端实现代码如下:
public class Client {
public static void main(String[] args) {
//TriplePin triplePinDevice = new TV(); //接口不兼容,此处报错“类型不匹配”
DualPin dualPinDevice = new TV();//构造两插电视机
TriplePin triplePinDevice = new Adapter(dualPinDevice);//适配器接驳两端
triplePinDevice.electrify(1, 0, -1);//此处调用的是三插通电标准
//输出结果:
//火线通电:1,零线通电:0
//电视开机
}
}
3. 专属适配(类适配)
除了上面介绍的“对象适配器”,我们还可以用“类适配器”实现接口的匹配,这是实现适配器模式的另一种方式。顾名思义,既然是类适配器,那么一定是属于某个类的“专属适配器”,也就是在编码阶段已经将被匹配的设备与目标接口进行对接了。
我们来看下电视机专属适配器类:
public class TVAdapter extends TV implements TriplePin{
@Override
public void electrify(int l, int n, int e) {
super.electrify(l, n);
}
}
这段代码实现起来其实很简单,电视机专属适配器类中并未包含被适配对象(如电视机)的引用,而是在开始定义类的时候就直接继承自电视机了,此外还一并实现了三相插孔接口。,并利用“super”关键字调用父类(电视机类 TV)定义的两插通电方法,以实现适配。
类适配器的使用如下:
public class Client {
public static void main(String[] args) {
//TriplePin triplePinDevice = new TV(); //此处接口无法兼容
TriplePin tvAdapter = new TVAdapter();//电视机专属三插适配器插入三相插孔
tvAdapter.electrify(1, 0, -1);//此处调用的是三插通电标准
//输出结果:
//火线通电:1,零线通电:0
//电视开机
}
}
从上面看,我们可以发现类适配器是有限制的,即 类适配器的继承关系使它固化为一种专属适配器,即无法适配其他设备。
当然,假如我们只需要匹配电视机这一种设备,并且未来也没有任何其他的设备扩展需求,那么类适配器使用起来可能更加简便。
总结
上面我们学习了两种适配器实现方式,分别为 对象适配器 和 类适配器,我们分别来看一下两种适配器的角色都有哪些。
对象适配器模式的各角色定义如下:
Target(目标接口):客户端要使用的目标接口标准,对应本章例程中的三相插孔接口 TriplePin。Adapter(适配器):实现了目标接口,负责适配(转换)被适配者的接口 specificRequest() 为目标接口 request(),对应本章例程中的电视机专属适配器类 TVAdapter。Adaptee(被适配者):被适配者的接口标准,目前不能兼容目标接口的问题接口,可以有多种实现类,对应本章例程中的两相插孔接口 DualPin。Client(客户端):目标接口的使用者。
类适配器模式的各角色定义如下:
Target(目标接口):客户端要使用的目标接口标准,对应本章例程中的三相插孔接口 TriplePin。Adapter(适配器):继承自被适配者类且实现了目标接口,负责适配(转换)被适配者的接口 specificRequest() 为目标接口 request()。Adaptee(被适配者):被适配者的类实现,目前不能兼容目标接口的问题类,对应本章例程中的电视机类 TV。Client(客户端):目标接口的使用者。
对象适配器模式与类适配器模式基本相同,二者的区别在于前者的 Adaptee(被适配者)以接口形式出现并被 Adapter(适配器)引用,而后者则以父类的角色出现并被 Adapter(适配器)继承,所以前者更加灵活,后者则更为简便。
参考文档
- 《秒懂设计模式》—— 刘韬