适配器模式
HandlerAdapter
在 Spring MVC 中使用了适配器模式。
适配器模式定义
适配器模式,也叫包装器模式
。将一个类的接口,转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以合作无间
。
简单来说就是目标类不能直接使用,通过一个新类进行包装一下,适配调用方法使用
。把两个不兼容的接口通过一定的方式使之兼容
。
比如下面两个接口,本身是不兼容的(参数类型不一样,参数个数不一样等等)。
可以通过适配器的方式,使之兼容
适配器模式角色
角色名称 | 描述 |
---|---|
Target | 目标接口(可以是抽象类或接口),客户希望直接用的接口。 |
Adaptee | 适配者,但是与 Target 不兼容。 |
Adapter | 适配器类,此模式的核心。通过继承或者引用适配者的对象,把适配者转为目标接口。 |
Client | 需要使用适配器的对象。 |
适配器模式的实现
1. 角色分析
在适配器模式中,通常有以下三个角色:
- 目标接口(Target Interface):客户端所期望的接口,客户端通过这个接口与适配器进行交互。
- 适配者类(Adaptee Class):已经存在的类,具有需要被适配的接口,但接口与目标接口不兼容。
- 适配器类(Adapter Class):将适配者类的接口转换为目标接口,使得客户端可以使用适配者类的功能。
场景:前面学习的slf4j
就使用了适配器模式
,slf4j
提供了一系列打印日志的 API
,底层调用的是 log4j
或者 logback
来打日志,我们作为调用者,只需要调用 slf4j 的 API 就行了
。
2. 代码实现
下面这段代码是一个典型的适配器模式(Adapter Pattern)的实现。
在适配器模式中,通常有以下三个主要角色:
- 客户端:使用适配器的代码,它调用适配器提供的接口。
- 适配器:实现了客户端期望的接口,并将调用转发到适配者。
- 适配者:现有的类,它提供了需要适配的功能,但接口与客户端不兼容。
客户端代码
客户端是使用适配器的代码,它调用适配器提供的接口。在你的代码中,客户端是 Client
类。
package com.bit.book.adapter;
public class Client {
public static void main(String[] args) {
Slf4jApi slf4jApi = new LogbackSlf4jAdapter(new Logback());
slf4jApi.log("我是日志");
}
}
目标接口
适配器接口是客户端期望的接口,它定义了客户端可以调用的方法。在你的代码中,适配器接口是 Slf4jApi
接口。
package com.bit.book.adapter;
public interface Slf4jApi {
void log(String log);
}
适配者
适配者是现有的类,它提供了需要适配的功能,但接口与客户端不兼容。在你的代码中,适配者是 Logback
和 Log4j
类。
Logback 适配者
package com.bit.book.adapter;
public class Logback {
void print(String log){
System.out.println("Logback print: " + log);
}
}
Log4j 适配者
package com.bit.book.adapter;
public class Log4j {
void log(String log){
System.out.println("Log4j log: " + log);
}
}
适配器
适配器实现了客户端期望的接口,并将调用转发到适配者。在你的代码中,适配器是 LogbackSlf4jAdapter
和 Log4jSlf4jAdapter
类。
LogbackSlf4jAdapter 适配器
package com.bit.book.adapter;
public class LogbackSlf4jAdapter implements Slf4jApi {
private Logback logback;
public LogbackSlf4jAdapter(Logback logback) {
this.logback = logback;
}
@Override
public void log(String log) {
logback.print("LogbackSlf4jAdapter: " + log);
}
}
Log4jSlf4jAdapter 适配器
package com.bit.book.adapter;
public class Log4jSlf4jAdapter implements Slf4jApi {
private Log4j log4j;
public Log4jSlf4jAdapter(Log4j log4j) {
this.log4j = log4j;
}
@Override
public void log(String log) {
log4j.log("Log4jSlf4jAdapter: " + log);
}
}
通过上述分类,代码的结构更加清晰:
- 客户端:
Client
类,使用适配器。 - 适配器接口:
Slf4jApi
接口,定义了客户端期望的接口。 - 适配者:
Logback
和Log4j
类,提供了需要适配的功能。 - 适配器:
LogbackSlf4jAdapter
和Log4jSlf4jAdapter
类,实现了适配器接口并将调用转发到适配者。
这种分类有助于理解适配器模式的各个角色及其职责。
适配器模式是一种结构型设计模式
,用于将一个类的接口转换成客户端所期望的另一个接口,从而使原本因接口不匹配而无法一起工作的两个类能够协同工作
。
下面我将详细解释代码中各个类和接口的交互过程。
3. 交互过程
3. 运行时交互过程
交互步骤
我们不需要改变 Logback
的 API,只需要通过适配器转换一下,就可以更换日志框架,保障系统的平稳运行:
通过适配器模式,代码实现了接口的适配,使得原本不兼容的类能够协同工作,同时保持了代码的灵活性和可扩展性
。
4. 适配器模式的优势
优势 | 说明 | 示例(Logback → Slf4j) |
---|---|---|
解耦客户端和适配者 | 客户端不直接依赖适配者类,而是通过目标接口(如 Slf4jApi )与适配器交互,降低耦合度。 | 客户端只需调用 Slf4jApi.log() ,无需关心底层是 Log4j 、Logback 或者其他类实现的实现。 |
复用适配者类 | 即使适配者类的接口不兼容(如方法名不同),也能通过适配器复用其功能。 | Slf4jLog4JAdapter 将 log() 调用转换为 Logback.print() ,复用 Logback 的日志功能。 |
扩展性 | 更换适配者时只需修改适配器,客户端代码无需变动,符合开闭原则(对扩展开放,对修改关闭)。 | 未来若切换为 Log4j ,只需新增 Slf4jLog4jAdapter ,客户端代码无需调整。 |
- 适配器模式的实现并不在 slf4j-core 中(只定义了 Logger),具体实现是在针对 log4j 的桥接器项目 slf4j-log4j12 中。
- 设计模式的使用非常灵活,一个项目中通常会含有多种设计模式。
门面模式与适配器模式的区别
门面模式(Facade Pattern)
和适配器模式(Adapter Pattern)
都是结构型
设计模式,但它们的核心目的和实现方式有显著区别:
1. 设计目的不同
门面模式
目标
:简化复杂子系统的调用,提供统一的高层接口。场景
:当系统有多个子模块或接口,客户端需要与这些模块交互时,门面模式通过一个“门面”类隐藏内部复杂性,让客户端只需与门面交互。关键点
:不改变原有接口,而是提供更友好的入口。
适配器模式
`
目标
:解决接口不兼容问题,使原本不兼容的类能协同工作。场景
:当需要将一个类的接口转换成客户端期望的另一种接口时(例如:旧系统改造、第三方库集成)。关键点
:转换接口形式,适配器会包装原有接口并提供新的接口。
2. 角色差异
- 门面模式
- 门面类:提供简化的入口,内部可能调用多个子系统功能。
- 子系统类:保持原有功能不变,门面不会修改其接口。
- 适配器模式
- 适配器类:实现目标接口,并包装一个适配者(Adaptee),在其方法中调用适配者的功能。
- 适配者:需要被适配的原有接口(可能已存在但不符合需求)。
3. 结构对比
- 门面模式的类图:
Client → Facade → [SubSystemA, SubSystemB, SubSystemC]
- 门面直接依赖多个子系统。
- 适配器模式的类图(对象适配器):
Client → TargetInterface ← Adapter → Adaptee
- 适配器实现目标接口,内部持有适配者的实例。
4. 本质区别
维度 | 门面模式 | 适配器模式 |
---|---|---|
目的 | 简化调用 | 转换接口 |
接口变化 | 提供新接口,不修改原有接口 | 将旧接口改造成新接口 |
调用关系 | 门面调用多个子系统 | 适配器调用被适配者 |
适用阶段 | 设计初期或重构复杂系统 | 集成已有组件/兼容旧代码 |
5. 总结
- 用 门面模式 当你想 隐藏复杂性,提供一个更简单的入口。
- 用 适配器模式 当你要 解决接口不匹配,让两个不兼容的接口能一起工作。
- 门面是“简化”,适配器是“转换”。