设计模式(5)-适配器模式

定义

适配器模式:将一个类的接口转换为调用方希望的另一个接口,使得原本不兼容的接口变得可兼容共同工作

举一个生活中的例子来解释适配器模式如下:

  • typeC的充电线不能给普通安卓机充电,因为接口不兼容,此时需要一个转接头适配器,typeC转安卓,即可实现给安卓手机充电
  • 用直流电的电子设备在使用中都需要一个电源适配器将插座上的交流电转变为直流电

角色和分类

适配器模式种分为3大角色

  • 目标接口:当前系统业务所期待的接口,抽象类或者接口
  • 适配者类:要被适配的类,原本不兼容的类
  • 适配器类:一个转换器,通过继承或引用适配者对象,把适配者接口转换成目标接口,使得客户按照目标接口的格式访问适配者

适配器模式分为3种:

  • 类适配器模式
  • 对象适配器模式
  • 接口适配器模式

下面分别对他们进行介绍

类适配器模式

手机充电器为例来介绍类适配器模式:充电器将220V交流电转换为5V直流电这一过程。其中的角色如下

  • 输出5v电压:目标接口,兼容手机充电
  • 电源插座:适配者类,要被适配,不适合手机直接充电
  • 手机充电器:适配器类,将220V不可用的电压转换为手机可用的充电电压

简易的类图结构如下:

代码实现

目标接口,定义一个将220转换为5v的接口,作为一个标准,提供给各个厂商的电源适配器使用

1
2
3
public interface IOutput5V {
int output5v();
}

被适配类,电源插座,提供220v的直流电,不能被手机直接使用

1
2
3
4
5
6
7
public class Output220V {
public int output220(){
int src = 220;
System.out.println("电源输出电压:220V");
return src;
}
}

适配器类,继承了被适配类,实现目标接口具体的适配转换逻辑

1
2
3
4
5
6
7
8
9
10
public class Adapter220To5 extends Output220V implements IOutput5V {
@Override
public int output5v() {
int src = output220();
//适配电压
int dts = src / 44;
System.out.println("充电器适配后电压:" + dts);
return dts;
}
}

以上便完成了主要的角色实现,编写手机的充电方法进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Phone {

public void charging(IOutput5V iOutput5V){
if (iOutput5V.output5v()==5){
System.out.println("电压5v,开始充电");
}else {
System.err.println("电压不符,无法充电");
}
}
public static void main(String[] args) {
new Phone().charging(new Adapter220To5());
}
}

运行后输出结果如下:

1
2
3
电源输出电压:220V
充电器适配后电压:5V
电压5v,开始充电

类适配器模式说明

适配器的实现过程中是继承了被适配类同时实现目标接口的方式,这样的原因是受Java单继承的限制,所以在类适配器模式下算是一个小小的缺点,使用继承大大的增加了适配器的复杂度。

对象适配器模式

基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承待适配类,而是持有待适配类的实例,以解决兼容性的问题。 即:持有待适配类,实现目标接口,完成兼容性适配

依然按照上文的场景和角色只有适配器类的变动,类图关系如下

从原本的继承被适配类转变为持有被适配类的实例。涉及到代码修改的只有适配器类:Adapter220To5.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Adapter220To5  implements IOutput5V {
private Output220V output220;

public Adapter220To5 setOutput220(Output220V output220) {
this.output220 = output220;
return this;
}

@Override
public int output5v() {
int src = output220.output220();
//适配电压
int dts = src / 44;
System.out.println("充电器适配后电压:" + dts);
return dts;
}
}

适配器类在需要持有被适配类的实例,所以在Phone充电方法中传入被适配类的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Phone {

public void charging(IOutput5V iOutput5V){
if (iOutput5V.output5v()==5){
System.out.println("电压5v,开始充电");
}else {
System.err.println("电压不符,无法充电");
}
}
public static void main(String[] args) {
//传入持有被适配类实例的适配器类
new Phone().charging(new Adapter220To5().setOutput220(new Output220V()));
}
}

对象适配器模式说明

  • 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。
  • 根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承被适配类的局限性问题,也不再要求目标接口角色必须是接口。

接口适配器模式

当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求

接口适配器模式适用于一个接口不想使用其所有的方法的情况

简单的类图结构如下:

代码实现

假定现在的目标接口定义了输出5v、输出20v、输出60v的方法

1
2
3
4
5
6
7
8
public interface IOutput {
//转换为5v输出
int output5();
//转换为20v输出
int output20();
//转换为60v输出
int output60();
}

定义抽象类默认实现目标接口,并持有待适配的对象实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract class AbsAdapter implements IOutput{
protected Output220V output220;

public AbsAdapter(Output220V output220V) {
this.output220 = output220V;
}

@Override
public int output5() {
return 0;
}

@Override
public int output20() {
return 0;
}

@Override
public int output60() {
return 0;
}
}

当需要使用到输出5v的转换时,或者需要使用输出10v转换时,使用匿名内部类的方式实现内部适配细节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Phone {

public void charging(IOutput iOutput){
if (iOutput.output5()==5){
System.out.println("电压5v,开始充电");
}else {
System.err.println("电压不符,无法充电");
}
}
public static void main(String[] args) {
AbsAdapter absAdapter = new AbsAdapter(new Output220V()) {
@Override
public int output5() {
int src = output220.output220();
//适配电压
int dts = src / 44;
System.out.println("充电器适配后电压:" + dts);
return dts;
}
};
new Phone().charging(absAdapter);
}
}

输出结果:

1
2
3
电源输出电压:220V
充电器适配后电压:5
电压5v,开始充电

适配器模式总结

主要优点:

  • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
  • 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
  • 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。

适用场景:

  • 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
  • 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

适配器模式在源码中的使用

---------- 😏本文结束  感谢您的阅读😏 ----------
评论