适配器模式(Adapter Pattern)充当两个不兼容接口之间的桥梁,属于结构型设计模式。它通过一个中间件(适配器)将一个类的接口转换成客户期望的另一个接口,使原本不能一起工作的类能够协同工作。
概述
适配器模式是一种软件设计模式,旨在解决不同接口之间的兼容性问题。
目的:将一个类的接口转换为另一个接口,使得原本不兼容的类可以协同工作,具体可分为类适配器、对象适配器和接口适配器
主要解决的问题:在软件系统中,需要将现有的对象放入新环境,而新环境要求的接口与现有对象不匹配。
使用场景
- 需要使用现有类,但其接口不符合系统需求。
- 希望创建一个可复用的类,与多个不相关的类(包括未来可能引入的类)一起工作,这些类可能没有统一的接口。
- 通过接口转换,将一个类集成到另一个类系中。
实现方式
- 继承或依赖:推荐使用依赖关系,而不是继承,以保持灵活性。
关键代码
适配器通过继承或依赖现有对象,并实现所需的目标接口。
使用建议
- 适配器模式应谨慎使用,特别是在详细设计阶段,它更多地用于解决现有系统的问题,用于系统上线后,引入新模块或第三方库时,用来把“不兼容接口”强行适配,属于补救性设计。
- 在考虑修改一个正常运行的系统接口时,适配器模式是一个合适的选择。
结构
适配器模式包含以下几个主要角色:
- 目标接口(Target) :定义客户需要的接口。
- 适配者类(Adaptee) :定义一个已经存在的接口,这个接口需要适配。
- 适配器类(Adapter) :实现目标接口,并通过组合或继承的方式调用适配者类中的方法,从而实现目标接口。
代码实现
一、类适配器(通过继承方式来实现)【不推荐】
场景描述:用户新买了一件三孔插座的电器,但是家里只有一个两孔插座,这个时候怎么办呢?可以再买一个插座适配器,来做转化,把适配器插到两孔插座上,用户直接把电器插到适配器即可使用
- 目标接口:NewThreeHoleSocket
- 适配者类:OldTwoHoleSocket
- 适配器类:HoleSocketAdapter
public interface NewThreeHoleSocket {
void threeHole();
}
public class OldTwoHoleSocket {
public void twoHole() {
System.out.println("插入现有的两孔插座");
}
}
public class HoleSocketAdapter extends OldTwoHoleSocket implements NewThreeHoleSocket {
@Override
public void threeHole() {
System.out.println("插入新增的三孔插座");
}
}
public class ApiTest {
public static void main(String[] args) {
// 适配器对象,既可以使用三孔插座,也可以使用两孔插座
HoleSocketAdapter holeSocketAdapter = new HoleSocketAdapter();
// 使用新的三孔插座
holeSocketAdapter.threeHole();
// 使用旧的两孔插座
holeSocketAdapter.twoHole();
}
}
二、对象适配器(通过依赖关系来实现)【推荐】
public interface NewThreeHoleSocket {
void threeHole();
}
@Component
public class OldTwoHoleSocket {
public void twoHole() {
System.out.println("插入现有的两孔插座");
}
}
@Component
@RequiredArgsConstructor
public class HoleSocketAdapter implements NewThreeHoleSocket {
// 依赖旧的二孔插座
private final OldTwoHoleSocket oldTwoHoleSocket;
@Override
public void threeHole() {
System.out.println("插入新增的三孔插座");
}
public void twoHole() {
oldTwoHoleSocket.twoHole();
}
}
@Slf4j
@RestController
@RequestMapping("/portal/test")
public class TestController {
@Resource
private HoleSocketAdapter holeSocketAdapter;
@GetMapping(value = "/get/adapter/test")
public CommonResult<String> getAdapterTest() {
// 使用二孔插座
holeSocketAdapter.twoHole();
// 使用三孔插座
holeSocketAdapter.threeHole();
return CommonResult.success();
}
}
三、接口适配器
(一) 核心思想
当一个接口中定义了多个方法,而某个类只需要其中一部分方法时;可以通过引入一个抽象适配器类(Adapter Class)来实现该接口,并为所有方法提供空实现(或默认实现)。这样,具体子类只需重写自己关心的方法即可,避免实现所有不必要的方法。
(二) 适用场景
- 接口方法过多,但实现类只关心其中部分方法;
- 避免强制实现类编写大量无用的空方法;
- 提高代码可维护性和可读性。
(三) 结构
接口适配器模式包含以下几个主要角色:
- 目标接口(Target) :定义客户需要的接口;
- 适配器抽象类(Adapter) :抽象类,实现 Target Interface,提供所有方法的默认/空实现;
- 适配器具体的子实现类(Concrete Class) :继承 Adapter,只重写需要的方法。
public interface HoleSocket {
// 二孔排插
void twoHole();
// 三孔排插
void threeHole();
void usbHole();
}
public abstract class BaseHoleSocket implements HoleSocket{
@Override
public void twoHole() {
}
@Override
public void threeHole() {
}
@Override
public void usbHole() {
}
}
@Component
public class HoleSocketAdapter extends BaseHoleSocket {
@Override
public void threeHole() {
System.out.println("插入新增的三孔插座");
}
}
@Slf4j
@RestController
@RequestMapping("/portal/test")
public class TestController {
@Resource
private HoleSocketAdapter holeSocketAdapter;
@GetMapping(value = "/get/adapter/test")
public CommonResult<String> getAdapterTest() {
// 使用三孔插座
holeSocketAdapter.threeHole();
return CommonResult.success();
}
}
(三)接口适配器在框架中的应用场景
| 系统/框架 | 适配器类 | 解决的问题 |
|---|---|---|
| Java AWT/Swing | WindowAdapter, MouseAdapter | 避免实现所有事件监听方法 |
| Spring (旧版) | HandlerInterceptorAdapter | 只处理拦截器中的部分阶段 |
| Netty | ChannelInboundHandlerAdapter | 只处理感兴趣的网络事件 |
| 自定义 GUI/事件系统 | 各类 XxxListenerAdapter | 用户只实现关心的回调 |
三种适配器对比
| 对比维度 | 类适配器 | 对象适配器 | 接口适配器 |
|---|---|---|---|
| 核心实现方式 | 通过多重继承(适配器实现目标接口 + 继承被适配类) | 通过组合(适配器实现目标接口 + 在适配器中引入被适配类对象) | 通过抽象类实现接口,提供空/默认实现 |
| 是否继承被适配类 | ✔ 是(直接继承) | ❌ 否(通过组合引用) | ❌ 不涉及被适配类,只对接口“瘦身” |
| 耦合度 | 🔥 高(编译期绑定具体被适配类) | ⚡ 低(运行时可替换被适配对象) | 🌱 极低(仅简化接口实现,不涉及外部类) |
| 灵活性 | 🌱 低(只能适配一个具体类) | 🔥 高(可适配任意子类或实现) | ⚡ 中(仅用于减少接口实现负担) |
| 典型代码结构 | class Adapter extends Adaptee implements Target | class Adapter implements Target {private Adaptee adaptee;} | abstract class Adapter implements Target {public void method1() {};......} |
| 能否适配多个被适配类 | ❌ 不能(受限于单继承) | ✔ 可以(通过更换内部引用) | ❌ 不适用(不处理被适配类) |
| 主要目的 | 将一个类的接口转换为客户期望的另一个接口 | 同左,但更灵活安全 | 避免实现接口中所有方法,只重写关心的方法 |
| 经典应用场景 | C++ 中集成旧类库(Java 中基本不用) | 1、集成第三方 SDK(如支付、地图)2、统一多数据源(CSV/JSON/API)3、Spring 的 HttpMessageConverter4、JDBC 驱动桥接 | 1、Java AWT/Swing 事件监听(WindowAdapter, MouseAdapter)2、自定义监听器只需处理部分事件 |
| 是否修改原有代码 | ❌ 不需要(但需能继承) | ❌ 不需要 | ❌ 不需要 |
| 是否符合开闭原则 | ★ 较弱(绑定具体类) | ★★★ 强(对扩展开放) | ★★★ 强(新增子类即可) |
| 在 Java 中的实用性 | ❌ 几乎不可用(无多重继承) | ✔ 最常用、最推荐 | ✔ 在 Java 8 之前广泛使用;Java 8+ 可用 default() 方法替代 |