来源:https://blog.csdn.net/ayunlong/article/details/130298340?spm=1001.2014.3001.5502

Java设计模式-适配器模式(Adapter Pattern)

适配器模式是一种“补救模式”,是系统开发完上线运行后需要扩展时使用,而不是系统设计时使用

什么是适配器模式

适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。–《JAVA与模式》中的定义

将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。–《设计模式之禅》中的定义

举个例子,早期的电脑、投影仪都是有VGA口的,但随着时代发展,都变成了HDMI口,这就导致新电脑(HDMI)在接入旧投影仪(VGA)时没法用,然后网上就出现了一种线,就是VGA转HDMI转接线,而这个线就是所谓的适配器。

转接线让本来无法在一起工作的电脑和投影仪能够在一起工作,所以转接线就是适配器

适配器模式的UML图如下

  • 目标(Target)角色:这就是所期待得到的接口
  • 源(Adapee)角色:现在需要适配的接口
  • 适配器(Adaper)角色:适配器类是本模式的核心。适配器把源接口转换成目标接口。

现在需要将Adaptee转为Target,但两者并未继承、实现关系,如何让源(Adapee)适配目标(Target)角色,就是通过适配器(Adaper)角色实现,使用的方式是成为两者共同的子类,就像父母通过孩子来产生血缘关系一样。这就联想到刚开始的定义:使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

我们通过UML图来看下VGA转HDMI的例子

HDMI支持数字信号(digitalSignal方法),VGA支持模拟信号(analogSignal方法),电脑现在只有HDMI接口,投影仪只有VGA接口,所以需要一个数字信号转模拟信号的适配器(VGA2HDMI类完成)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package org.Adapter.version1;

interface IHDMI{
public void digitalSignal();
}
class HDMI implements IHDMI{

@Override
public void digitalSignal() {
System.out.println("HDMI数字信号传输");
}
}

class VGA{
public void analogSignal(){
System.out.println("VGA模拟信号传输");
}
}

class VGA2HDMI extends VGA implements IHDMI {
@Override
public void digitalSignal() {
System.out.println("适配器将数字信号转为模拟信号");
super.analogSignal();
}
}

class Computer{
public void show(IHDMI hdmi){
if (hdmi == null){
throw new NullPointerException("hdmi is null");
}
System.out.println("电脑开始投屏");
hdmi.digitalSignal();
}
}

public class ClassAdapter {
public static void main(String[] args) {
IHDMI hdmi = new HDMI();
Computer computer = new Computer();
computer.show(hdmi);
System.out.println("------------------------------");
VGA2HDMI vga2HDMI = new VGA2HDMI();
computer.show(vga2HDMI);

}
}
// 输出结果
电脑开始投屏
HDMI数字信号传输
------------------------------
电脑开始投屏
适配器将数字信号转为模拟信号
VGA模拟信号传输

可以看到当Computer使用适配器时,最终调用了VGA实现了图像传输,完成HDMI转VGA的目的。需要注意的是Computer中接收的是目标(Target)角色,因为我们需要的是Target,整个过程也是将Adapee转为Target。

适配器模式的3种类型

类适配器模式

刚才讲的是第一种类型:类适配器模式,特点是把适配的类的API转换为目标类的API

对象适配器模式

第二种是对象适配器模式,作用同样是适配,但类适配器模式是通过继承实现,而对象适配器是通过委派,看下UML图

在Adaptee中没有sampleOperation2方法,需要Adapter去适配,该类持有Adaptee对象,从而实现对Adapteee的功能扩展,Adapter与Adaptee是委派关系,这就是对象适配器模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package org.Adapter.version2;

interface IHDMI{
public void digitalSignal();
}
class HDMI implements IHDMI {

@Override
public void digitalSignal() {
System.out.println("HDMI数字信号传输");
}
}

class VGA{
public void analogSignal(){
System.out.println("VGA模拟信号传输");
}
}

class VGA2HDMI implements IHDMI {
private VGA vga;
public VGA2HDMI(VGA vga){
this.vga = vga;
}
@Override
public void digitalSignal() {
System.out.println("适配器将数字信号转为模拟信号");
vga.analogSignal();
}
}

class Computer{
public void show(IHDMI hdmi){
if (hdmi == null){
throw new NullPointerException("hdmi is null");
}
System.out.println("电脑开始投屏");
hdmi.digitalSignal();
}
}

public class ObjectAdapter {
public static void main(String[] args) {
IHDMI hdmi = new HDMI();
Computer computer = new Computer();
computer.show(hdmi);
System.out.println("------------------------------");
VGA vga = new VGA();
VGA2HDMI vga2HDMI = new VGA2HDMI(vga);
computer.show(vga2HDMI);

}
}
// 输出结果
电脑开始投屏
HDMI数字信号传输
------------------------------
电脑开始投屏
适配器将数字信号转为模拟信号
VGA模拟信号传输

可以看出Adapter(VGA2HDMI)只实现了Target(HDMI)的接口,与VGA通过委派建立关系。

对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。对象适配器根据合成复用原则,使用聚合替代继承,所以它解决了类适配器必须继承 Adaptee(VGA) 的局限性问题,也不再要求Target(HDMI)必须是接口,对象适适配器模式的使用成本更低,更加灵活,甚至可以适配多个源(Adapee)角色,试想一下下面的场景

Adapter中持有不同类型充电口的对象即可完成适配。

缺省适配器模式

也称为接口适配器模式,但表达的都是同一个意思。前面的例子都是非常简单的类,Target只有一个方法,但是如果Target有10个方法呢?Adapter都要实现一遍吗?但Adapter只需要其中几个而已,全部都实现感觉违反了接口隔离原则(只是有点相似),我不需要为什么要实现?这导致Adapter中会有很多空方法,就很奇怪,所以缺省适配器模式就是为了解决这个问题。

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

还是用前面例子,HDMI有很多功能,例如音频传输、4K高清显示、普通画质显示、48Gbps的告诉传输,但我只需要普通画质显示,开个会而已,不是看电影,用不了那么高的需求,所以Adapter只需要实现普通画质显示就行了,但这会报语法错误,Java要求implements的接口必须全部实现,哪怕是空实现。所以要使用缺省适配器模式来解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package org.Adapter.version3;

interface IHDMI{
public void transmitVoice();
public void video1080();
// 我们只需要他的数字信号功能,至于能不能1080P播放,不关心
public void digitalSignal();
public void transmit48Gbps();
}
abstract class AHDMI implements IHDMI{
// 必须是空方法,如果不实现空方法,还会让子类去实现
public void transmitVoice(){};
public void video1080(){};

public void transmit48Gbps(){};
@Override
public void digitalSignal() {
System.out.println("HDMI数字信号传输");
}
}

class HDMI extends AHDMI {
@Override
public void digitalSignal() {
System.out.println("HDMI数字信号传输");
}
}

class VGA{
public void analogSignal(){
System.out.println("VGA模拟信号传输");
}
}

class VGA2HDMI extends AHDMI {
private VGA vga;
public VGA2HDMI(VGA vga){
this.vga = vga;
}
@Override
public void digitalSignal() {
System.out.println("适配器将数字信号转为模拟信号");
vga.analogSignal();
}
}

class Computer{
public void show(IHDMI hdmi){
if (hdmi == null){
throw new NullPointerException("hdmi is null");
}
System.out.println("电脑开始投屏");
hdmi.digitalSignal();
}
}

public class DefaultAdapter {
public static void main(String[] args) {
IHDMI hdmi = new HDMI();
Computer computer = new Computer();
computer.show(hdmi);
System.out.println("------------------------------");
VGA vga = new VGA();
VGA2HDMI vga2HDMI = new VGA2HDMI(vga);
computer.show(vga2HDMI);

}
}
// 运行结果
电脑开始投屏
HDMI数字信号传输
------------------------------
电脑开始投屏
适配器将数字信号转为模拟信号
VGA模拟信号传输

运行结果和前面的案例是一模一样的,只不过我们扩展了HDMI的功能,但只需要它的某个功能。

在任何时候,如果不准备实现一个接口的所有方法时,就可以使用“缺省适配模式”制造一个抽象类,给出所有方法的空实现。这样,从这个抽象类再继承下去的子类就不必实现所有的方法了。

看到这里会发现一个问题,缺省适配器模式是不是就是装饰模式?我们使用Adapter去装饰Adaptee,去增强它的功能,但又有点区别,缺省模式调用了Target的方法,而装饰模式没有,但装饰模式会调用其它类的方法,是不是有点晕?

其实缺省适配器模式和装饰模式的用的思想是一样的,都是包装模式(Wrapper)但它们的目的不一样

  • 适配器模式的意义是要将一个接口转变成另外一个接口,它的目的是通过改变接口来达到重复使用的目的。
  • 装饰器模式不是要改变被装饰对象的接口,而恰恰要保持原有的接口,但是增强原有对象的功能,或者改变原有对象的处理方法而提高性能。