一、概述
首先需要介绍一下适配器模式是干什么的
将一个类的接口转换成客户希望的另外一个接口。
简单的说,适配器模式就是为了使得我们可以通过一定的处理来使用原本不兼容的类进行交互,而这个处理的过程或者说进行处理的类就是我们所说的适配器,适配器的两端就是被适配者与要适配者
比较常见的例子就是我们的插座与充电器了,通常我们的插座电压都是额定电压220V,但是我们要给手机充电的话肯定不可能直接220V的,这时候我们就需要一个插头来将插座的220V转换成适合我们手机的电压,在这里面手机就要我们的被适配者,而插座就是要适配者,手机充电插头就充当着适配者的角色。
适配器模式也是很很多种的,类适配器、对象适配器、双向适配器……,各个模式的介绍和之间的差异就先不介绍了,放在总结里。
二、角色与职责
Target:
- 定义Client使用的与特定领域相关的接口
- 要被人适配他(被适配者)
Client
- 与符合Target接口的对象协同
- 调用被适配者
Adaptee
- 定义一个已经存在的接口,这个接口需要适配
- 需要适配别人(要适配者)
Adapter
- 对Adaptee的接口与Target接口进行适配
我们使用插座与手机充电器的的例子,那么我们220V额定电压的插座就是Adaptee
,而我们的手机充电插口就是Target
,要将220V的电压变成适合给手机充电的电压,那么充电器在这个环节中就是作为一个适配器(Adapter)来存在的。
三、类适配器
首先我们来看类适配器,UML图如下
由上图可以看到,类适配器模式是通过让Adapter
(适配器)实现Target
(被适配者)的抽象接口,然后继承Adaptee
(要适配者),具体适配过程是由我们的适配器的Resuest()
方法中对Adaptee
(要适配者)的SpecificRequest()
方法进行适配,使得适配器的Request()
方法返回我们需要的被适配者,供我们使用。
下面通过代码来实现一下类适配器模式,我们还是使用插座和充电器的例子
首先创建一个模仿插座的类,其中的方法返回一个220V的电压值
/**
* @author ZhongJing </p>
* @Description 插座(被适配的类) </p>
*/
public class Voltage220V {
public int output220V() {
int src = 220;
System.out.println("电压=" + src + "伏");
return src;
}
}
然后我们定义一个类来模拟手机需要的额定电压
/**
* @author ZhongJing </p>
* @Description 手机额定电压(适配接口) </p>
*/
public interface IVoltage5V {
public int output5V();
}
有了被Adaptee
和Target
之后,我们需要一个适配器Adapter
来将220V的电压转为手机支持的充电电压
/**
* @author ZhongJing </p>
* @Description 适配器 </p>
*/
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
// 获取到220V的电压
int srcV = output220V();
// 处理电压,转成5V
int dstV = srcV / 44;
return dstV;
}
}
创建一个手机类,其中一个方法为模拟手机充电,方法需要一个额定电压Target
的参数
/**
* @author ZhongJing </p>
* @Description </p>
*/
public class Phone {
public void charging(IVoltage5V voltage) {
if (voltage.output5V() == 5) {
System.out.println("电压为5V,可以充电~");
} else if (voltage.output5V() > 5) {
System.out.println("电压大于5V,不能充电~");
}
}
}
下面我们写一个main方法来跑一下我们编写的代码,验证是否正确
/**
* @author ZhongJing </p>
* @Description </p>
*/
public class Client {
public static void main(String[] args) {
System.out.println("类适配器模式");
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
}
运行结果如下:
类适配器模式
电压=220伏
电压为5V,可以充电~
四、对象适配器
对象适配器的UML图如下:
可以看到对象适配器并没有去继承Adaptee
,而是通过关联(成员属性)的方式来使用Adaptee
和类适配器唯一的区别就是适配器的写法,具体代码如下:
/**
* @author ZhongJing </p>
* @Description 适配器 </p>
*/
public class VoltageAdapter implements IVoltage5V {
private Voltage220V voltage220V;
public VoltageAdapter() {
}
public VoltageAdapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}
@Override
public int output5V() {
int dst = 0;
if (voltage220V != null) {
int src = voltage220V.output220V();
System.out.println("使用对象适配器进行适配");
dst = src / 44;
System.out.println("适配完成,输出电压为:" + dst);
}
return dst;
}
}
别的代码和类适配器都是一样的,就不多做赘述了,我们写一段测试代码来测试一下
/**
* @author ZhongJing </p>
* @Description </p>
*/
public class Client {
public static void main(String[] args) {
System.out.println("对象适配器模式");
Phone phone = new Phone();
phone.charging(new VoltageAdapter(new Voltage220V()));
}
}
运行结果如下:
对象适配器模式
电压=220伏
使用对象适配器进行适配
适配完成,输出电压为:5
电压为5V,可以充电~
对象适配器模式相比于类适配器模式会更加灵活一些,通过我们的测试代码可以发现,我们的Adaptee
是通过构造器传入的,这样可以更方便我们代码的扩展(多态),而且类适配器如果Target
不是一个接口,也是一个类的话,Java中是不支持多继承的,这是一个很严重的问题。
五、双向适配器
双向适配器其实算是对象适配器的一个变种
就是当适配器中同时拥有Target
和Adaptee
的引用时候,适配器定义针对二者的方法,既可以将Target
适配为Adaptee
,也可以将Adaptee
适配为Target
,不再区分谁是适配者谁是被适配者,二者皆可。
UML图如下所示:
代码大致如下
/**
* @author ZhongJing </p>
* @Description </p>
*/
public class Adapter implements Target, Adaptee {
private Target target;
private Adaptee adaptee;
public Adapter(Target target) {
this.target = target;
}
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void setTarget(Target target) {
this.target = target;
}
public void setAdaptee(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void specificRequest() {
// 适配代码……
target.request();
}
@Override
public void request() {
// 适配代码……
adaptee.specificRequest();
}
}
六、总结
本篇共介绍了3种适配器模式,分别为类适配器、对象适配器以及双向适配器
总的来说适配器模式的优点如下:
- 将目标类和适配者类解耦,通过一个中间的适配器来复用原有的类而不需要去改变原有代码
- 具有较高的灵活性和可扩展性
其中对象适配器和类适配器也各有各的优点以及缺点
类适配器优点就在于适配器类是继承了Adaptee
,那么我们就可以在适配器类中对Adaptee
的一些方法进行自定的修改与重写,灵活性更强
缺点也就是因为类适配器需要继承Adaptee
,当我们的Target
是一个类而不是接口的时候,类适配器就不适用于我们了。
对象适配器的优点在于其通过关联的关系将Adaptee
作为一个成员属性包含在内,那么我们就可以运行多态将Adaptee
以及他的子类都适配到Target
,当然,这是在Adaptee
和他的子类遵循“里氏替换原则”的前提下。