适配器模式(Adapter Pattern)的定义是:它一种结构型设计模式,它的核心作用是将一个类的接口转换成客户端期望的另一个接口,使得原本因接口不兼容而无法一起工作的类能够协同工作。

用一个现实生活中的例子来比喻,我们家里的插座是三项的,但是我们的插头是两项的,这时候可以用一个转换插座把三项的转换成两项的。又比如,我们家里的电源都是220V的,那我要给手机充电就需要一个充电头将220V转换成手机可以适应的电流。

适配器模式主要的应用场景有以下几个方面:

  • 解决接口不兼容的问题:比如当一个已有的类接口,由于参数数量或者参数类型等与需求不匹配而无法使用,那么我们可以通过适配器模式在不改变已有类的情况下满足接口需求。
  • 复用现有功能:比如我们要用一个第三方的API接口或者一个遗留系统的功能代码,可以通过适配器模式实现对现有功能的复用。

下面是适配器模式的UML类图表示

image.png
这个UML类图中提供了两种适配器模式的实现方案:对象适配器(ObjectAdapter)和类适配器(ClassAdapter)。对象适配器通过组合的方式,也就是在类内部持有被适配的对象(Adaptee)来实现,这种方式更灵活。而类适配器模式,是通过继承被适配对象(Adaptee)来实现的,这种一般需要编程语言的支持,通常面向对象编程语言都可以支持。

我们举一个工程中常见的例子。比如我们的系统中有一个日志工具,现在要继承第三方的日志库,但是我们发现现有日志工具的方法参数等和第三方日志库的不匹配,这时候可以通过适配器模式来解决接口不兼容的问题。

假设我们有下面这样一个第三方日志库的类,注意我们无法修改它

1
2
3
4
5
6
7
8
9
10
11
12
13
// 第三方日志库(适配者)
// 注意:这个类是我们无法修改的
public class ThirdPartyLogger {
public void logMessage(String level, String message) {
System.out.println("[" + level + "] " + message);
}

public void logException(String message, Throwable throwable) {
System.err.println("[ERROR] " + message);
throwable.printStackTrace();
}
}

首先我们创建一个日志接口,它充当目标接口,用于和客户端代码交互。

1
2
3
4
5
6
// 系统定义的日志接口(目标接口)
public interface Logger {
void info(String message);
void error(String message, Throwable throwable);
}

这个接口中我们定义了infoerror两个方法。

然后我们创建一个适配器类来实现这个接口并让它持有ThirdPartyLogger对象,这样就可以把它的功能进行适配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 日志适配器(将第三方日志库适配到系统日志接口)
public class LoggerAdapter implements Logger {
private ThirdPartyLogger thirdPartyLogger;

// 通过构造函数注入第三方日志实例
public LoggerAdapter(ThirdPartyLogger thirdPartyLogger) {
this.thirdPartyLogger = thirdPartyLogger;
}

@Override
public void info(String message) {
// 适配info级别日志
thirdPartyLogger.logMessage("INFO", message);
}

@Override
public void error(String message, Throwable throwable) {
// 适配error级别日志
thirdPartyLogger.logException(message, throwable);
}
}

如代码所示,我们把第三方日志库的logMessagelogException 方法适配成了我们自己要用的infoerror 这两个方法。
下面演示客户端的调用过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 客户端代码
public class Client {
public static void main(String[] args) {
// 创建第三方日志实例
ThirdPartyLogger thirdPartyLogger = new ThirdPartyLogger();

// 创建适配器
Logger logger = new LoggerAdapter(thirdPartyLogger);

// 客户端使用系统定义的接口,无需关心具体实现
logger.info("系统启动成功");

try {
int result = 1 / 0;
} catch (Exception e) {
logger.error("发生数学运算异常", e);
}
}
}

适配器模式的优势在于其在不修改现有代码的情况下实现了接口的兼容,这样可以让客户端使用统一的接口,能更好的提升代码的复用性并降低了系统间的耦合度。