适配器模式
今天这一讲,我们主要讲解最常用到的适配器模式。
在程序中,经常需要新的项目中需要对老代码进行适配才能用。适配器模式就是将旧代码和新程序的中间的转换角色。举个现实例子,比如我们的MAC电脑需要连接USB接口的键盘,但是MAC电脑只有typec接口,这时我们需要一个拓展坞,需要把typec接口转换成USB接口,给键盘使用,如图所示:
适配器模式类图
适配器模式的定义是:将类的接口转换为客户期望的另一个接口,适配器可以让不兼容的两个类一起协同工作。
我们以拓展坞作为适配器将typec接口转换成usb接口为例进行讲解,它的UML图如下所示:
从 UML 图中,我们可以看出适配器模式中包含三个关键角色:
目标类Target, 适配器类即将要进行适配的抽象类或接口,比如TypeC接口;
适配器类Adapter,是作为适配的中间类,它必须持有或者实现目标类和适配类的接口,比如拓展坞类实现了目标接口TypeC接口,持有适配者的类Keyboard;
需要被适配器转换的对象 Adaptee, 比如图中的键盘(实现了USB接口)。
代码实现
需要被适配的接口USB接口:
public interface IUsb {
void connect(int x, int y);
}
需要被适配的接口的实现类,比如案例中Keyboard:
public class Keyboard implements IUsb{
@Override
public void connect(int x, int y) {
System.out.println("keyborad 连上了usb接口");
}
}
目标接口ITypeC:
public interface ITypeC {
void connect(int x, int y,int z);
}
ExpansionDockAdapter(拓展坞适配器)实现了ITypeC的目标接口接口,ExpansionDockAdapter并持有需要被适配的IUsb接口,适配器只有同时实现或者持有目标接口和被适配的对象,才能进行适配工作:
public class ExpansionDockAdapter implements ITypeC{
private IUsb iUsb;
public ExpansionDockAdapter(IUsb iUsb) {
this.iUsb = iUsb;
}
@Override
public void connect(int x, int y, int z) {
System.out.println("拓展坞将Typec接口转换成USB接口");
iUsb.connect(x,y);
}
}
最后,可以调用客户端对它们调用:
public class McClient {
public static void main(String[] args) {
Keyboard keyboard = new Keyboard();
ExpansionDockAdapter adapter = new ExpansionDockAdapter(keyboard);
System.out.println("mac连接typec");
adapter.connect(1, 2, 3);
}
}
使用适配器模式有什么收益
可能有很多人比较疑惑,如果在目标类中,新写一个方法就可以将需要适配的类进行转换。那么为什么还需要使用适配器模式呢?
- 首先 ,是为了保持简单性,正如mac电脑一样,它只提供TypeC接口。由拓展坞去做USB或者是HDMI接口的转换。保证了MAC电脑对外接口的简单性。
- 单一职责,不同的角色做不同的事,没有必要将多个事情给一个角色做完,这样代码会非常的臃肿,难以维护。
- 可复用,将适配器这个角色进行高度抽象化,可以做到移植可复用
使用适配器模式有以下的优点:
将目标类和适配的类解耦,引入一个适配器类兼容现有目标类,拓展新的适配者类功能,很好的避免了现有类和适配者类的耦合。
单一职责,目标类和适配者类各司其职,互不干扰。
满足里氏替换原则。 目标类和适配者类是通过适配器进行交互的,适配器类只要不影响目标类的接口功能,适配者类无论出现什么新功能,都很方便替换。