封面来源:碧蓝航线 破晓冰华 活动CG

本文参考:

Java 设计模式学习网站:Java设计模式:23种设计模式全面解析(超级详细)

菜鸟教程:适配器模式

1. 模式的定义与特点

适配器[1]一词我们在现实生活中就听说过,比方说新买的笔记本电脑需要一个电源适配器将交流电转换成电脑可用的直流电,又或者没有网线接口的笔记本电脑想要连接网线的话需要一个 USB 转 RJ45 接口才行。无论是电源适配器还是 USB 转 RJ45 接口,它们都扮演了一个适配器的角色,将我们拥有的但无法使用的对象转换成可用的对象。

而适配器模式的作用也是这样,使用这种模式可以将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。针对这个目标,可以使适配器继承或依赖(推荐)已有的对象,实现想要的目标接口即可。

适配器模式是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。

定义

适配器模式(Adapter Pattern)的定义:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为 类结构型模式对象结构型模式 两种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

优点

1、客户端通过适配器可以透明地调用目标接口。

2、复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。

3、将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。

4、让没有关联的两个类一起运行,灵活性好。

5、在很多业务场景中符合开闭原则。

缺点

1、适配器模式编写过程需要结合业务场景全面考虑,过多使用适配器会使系统代码变得凌乱,增加系统的复杂性,增加代码阅读难度,降低代码可读性。

2、由于 Java 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。

2. 适用场景

1、系统需要使用现有的类,而此类的接口不符合系统的需要。

2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。

3、通过接口转换,将一个类插入另一个类系中。

总结

也就是说,当有动机地修改一个正常运行的系统接口时,应该考虑使用适配器模式。

这句话还有一个意思:适配器不是在详细设计时添加的(别一上来就用适配器,那样就是为用而用了,没意义),而是解决正在服役的项目的问题时才使用。

3. 模式的结构

在介绍【适配器模式】的定义时,知道了它分为类结构型模式和对象结构型模式。

类适配器模式(类结构型模式)可以使用多继承的方式实现,当然这是针对 C++ 等支持多继承的语言,而 Java 不支持多继承,可以定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。

对象适配器模式(对象结构型模式)则可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。

【适配器模式】主要包含以下角色:

1、目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。

2、适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。

3、适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。

类适配器模式的结构图

类适配器模式的结构图

对象适配器模式的结构图

对象适配器模式的结构图

4. 代码实现

4.1 类适配器模式的实现

假设目标接口为 Target,其代码如下:

1
2
3
4
5
6
7
8
9
10
/**
* @author mofan
* @date 2021/7/18 18:14
*/
public interface Target {
/**
* 请求接口
*/
void request();
}

现有的适配者代码如下:

1
2
3
4
5
6
7
8
9
10
11
/**
* @author mofan
* @date 2021/7/18 18:15
*/
public class Adaptee {

public void specificRequest()
{
System.out.println("适配者中的业务代码被调用...");
}
}

我们现在只有 Adaptee,但是实际需要的是 Target,为了解决这个问题,需要引入一个适配器。这个适配器得实现目标接口,还得继承现有组件 Adaptee

1
2
3
4
5
6
7
8
9
10
/**
* @author mofan
* @date 2021/7/18 18:17
*/
public class ClassAdapter extends Adaptee implements Target {
@Override
public void request() {
this.specificRequest();
}
}

最后来测试一下:

1
2
3
4
5
@Test
public void testClassAdapter() {
Target target = new ClassAdapter();
target.request();
}

运行测试方法后,控制台打印出:

类适配器模式:
适配者中的业务代码被调用...

4.2 对象适配器模式的实现

类适配器模式的耦合度较高,咱们再编写一个对象适配器来完成需求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @author mofan
* @date 2021/7/18 18:35
*/
public class ObjectAdapter implements Target {

private Adaptee adaptee;

public ObjectAdapter(Adaptee adaptee)
{
this.adaptee=adaptee;
}

@Override
public void request() {
adaptee.specificRequest();
}
}

最后再测试一下:

1
2
3
4
5
6
7
@Test
public void testObjectAdapter() {
System.out.println("对象适配器模式");
Adaptee adaptee = new Adaptee();
Target target = new ObjectAdapter(adaptee);
target.request();
}

运行测试方法后,控制台打印出:

对象适配器模式
适配者中的业务代码被调用...

5. 模式的拓展

5.1 双向适配器模式

我们前面都是将适配者接口转换成目标接口,那可不可以再让目标接口转换成适配者接口呢?

【适配器模式】可扩展为【双向适配器模式】,双向适配器类既可以把适配者接口转换成目标接口,也可以把目标接口转换成适配者接口,其结构图如下:

双向适配器模式的结构图

5.2 代码实现

跟前面的代码实现一样,还是需要一个目标接口和适配者接口:

1
2
3
4
5
6
7
8
9
10
/**
* @author mofan
* @date 2021/7/18 19:10
*/
public interface TwoWayTarget {
/**
* 请求接口
*/
void request();
}
1
2
3
4
5
6
7
8
9
10
/**
* @author mofan
* @date 2021/7/18 19:12
*/
public interface TwoWayAdaptee {
/**
* 具体请求
*/
void specificRequest();
}

然后针对这两个接口有不同的实现:

1
2
3
4
5
6
7
8
9
10
/**
* @author mofan
* @date 2021/7/18 19:13
*/
public class TargetRealize implements TwoWayTarget {
@Override
public void request() {
System.out.println("目标代码被调用...");
}
}
1
2
3
4
5
6
7
8
9
10
/**
* @author mofan
* @date 2021/7/18 19:13
*/
public class AdapteeRealize implements TwoWayAdaptee {
@Override
public void specificRequest() {
System.out.println("适配者代码被调用...");
}
}

然后就需要编写一个双向适配器,可以将适配者接口转换成目标接口,还可以将目标接口转换成适配者接口:

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
/**
* @author mofan
* @date 2021/7/18 19:14
*/
public class TwoWayAdapter implements TwoWayAdaptee, TwoWayTarget {

private TwoWayTarget target;
private TwoWayAdaptee adaptee;

public TwoWayAdapter(TwoWayTarget target) {
this.target = target;
}

public TwoWayAdapter(TwoWayAdaptee adaptee) {
this.adaptee = adaptee;
}

@Override
public void specificRequest() {
target.request();
}

@Override
public void request() {
adaptee.specificRequest();
}
}

最后再测试一下:

1
2
3
4
5
6
7
8
9
10
11
@Test
public void testTwoWay() {
System.out.println("目标接口转换成适配者接口:");
TwoWayTarget target = new TargetRealize();
TwoWayAdaptee adaptee = new TwoWayAdapter(target);
adaptee.specificRequest();
System.out.println("适配者接口转换成目标接口:");
adaptee = new AdapteeRealize();
target = new TwoWayAdapter(adaptee);
target.request();
}

运行测试方法后,控制台打印出:

目标接口转换成适配者接口:
目标代码被调用...
适配者接口转换成目标接口:
适配者代码被调用...

初看【双向适配器模式】可能感觉到很厉害,其实通过上面的实现可以看出就是一个对象适配器模式的实现,只不过这个适配器同时实现了目标接口和适配者接口,并将具体的目标和具体的适配者都引入到适配器中,最终实现了目标接口和适配者接口的双向转换。


  1. 百度百科 - 适配器 ↩︎