【设计模式】适配器模式 在java中的应用
文章目录
- 适配器模式简介
- 为什么需要适配器模式?
- 类比
- 适配器模式的结构与角色
- 目标接口(Target)
- 需要适配的类(Adaptee)
- 适配器(Adapter)
- 结构图示意
- 适配器模式的实现方式
- 类适配器实现(基于继承)
- 对象适配器实现(基于组合)
- 适配器模式的实际应用场景
- 集成第三方库
- 数据库驱动兼容
- 旧系统迁移/升级
- 前端开发中的兼容处理
- JDK 中的应用
- 适配器模式的优缺点分析
- 优点
- 缺点
适配器模式简介
适配器模式(Adapter Pattern),又称为包装器模式,是一种结构型设计模式。它的核心作用是**“将一个接口转换成客户希望的另一个接口”**,使原本因接口不兼容而无法工作的类可以协同工作。
为什么需要适配器模式?
在实际开发中,我们经常会遇到这样的问题:
我们手头有一个类A(比如老版本的接口),而现在需要用它来适配到一个新接口B(比如新系统的标准)。直接修改A的代码可能不现实(比如A是第三方库),这时候就可以用适配器模式来“桥接”它们。
类比
假设你买了一台美国进口的笔记本电脑,它的电源插头是美标的两扁头,而中国大陆的插座是国标的三孔插座。
你不能直接把美标插头插进国标插座,这时就需要一个插头转换器。
插头转换器的作用就是:一边兼容美标插头,一边兼容国标插座,让两个本来“不兼容”的东西可以协同工作。
适配器模式的结构与角色
适配器模式的结构其实很简单,主要包括以下几个角色:
目标接口(Target)
这是客户端期待的接口,也就是系统希望使用的标准接口。
举例:你家里的国标插座就是目标接口。
package com.example.adapter;public interface Target {void request();
}
需要适配的类(Adaptee)
这个类是已经存在的,接口不兼容但功能可用。
举例:美标插头的设备就是 Adaptee。
package com.example.adapter;public class Adaptee {public void specificRequest() {System.out.println("Adaptee: 处理特殊请求");}
}
适配器(Adapter)
适配器是连接目标接口和需要适配的类的“桥梁”。
它实现了目标接口,并在内部持有 Adaptee 的引用,通过转换让二者协同工作。
举例:插头转换器就是 Adapter。
package com.example.adapter;// 对象适配器实现方式
public class Adapter implements Target {private Adaptee adaptee;public Adapter(Adaptee adaptee) {this.adaptee = adaptee;}@Overridepublic void request() {// 在这里做转换,让 Adaptee 能被 Target 使用adaptee.specificRequest();}
}
结构图示意
+-----------+ +------------+ +-----------+ +-----------+
| Client | ---> | Target | <--- | Adapter | ---> | Adaptee |
+-----------+ +------------+ +-----------+ +-----------+
- Client 通过 Target 接口调用 Adapter
- Adapter 内部调用 Adaptee 的方法,实现接口转换
小结:
适配器模式的核心结构就是:目标接口(Target)+ 需要适配的类(Adaptee)+ 适配器(Adapter)。
适配器实现了目标接口,并通过组合或继承的方式调用 Adaptee 的功能,实现接口的兼容。
适配器模式的实现方式
在 Java 中,适配器模式有两种常见实现方式:类适配器和对象适配器。
这两种方式本质上都是让“不兼容”的类能用统一的接口,但实现细节略有不同。
类适配器实现(基于继承)
类适配器通过继承需要适配的类(Adaptee),并实现目标接口(Target)。
由于 Java 只支持单继承,所以类适配器模式有一定局限性。
结构示意:
package com.example.adapter;// 目标接口
public interface Target {void request();
}// 需要适配的类
public class Adaptee {public void specificRequest() {System.out.println("Adaptee: 处理特殊请求");}
}// 类适配器
public class ClassAdapter extends Adaptee implements Target {@Overridepublic void request() {// 直接调用父类方法specificRequest();}
}
使用示例:
package com.example.adapter;public class AdapterDemo {public static void main(String[] args) {Target target = new ClassAdapter();target.request(); // 输出:Adaptee: 处理特殊请求}
}
特点:
- 适配器通过继承 Adaptee 实现接口转换。
- 只能适配一个类(受限于 Java 的单继承)。
对象适配器实现(基于组合)
对象适配器通过组合的方式,在适配器内部持有 Adaptee 的实例,并实现目标接口(Target)。
结构示意:
package com.example.adapter;// 目标接口
public interface Target {void request();
}// 需要适配的类
public class Adaptee {public void specificRequest() {System.out.println("Adaptee: 处理特殊请求");}
}// 对象适配器
public class ObjectAdapter implements Target {private Adaptee adaptee;public ObjectAdapter(Adaptee adaptee) {this.adaptee = adaptee;}@Overridepublic void request() {adaptee.specificRequest();}
}
使用示例:
package com.example.adapter;public class AdapterDemo {public static void main(String[] args) {Adaptee adaptee = new Adaptee();Target target = new ObjectAdapter(adaptee);target.request(); // 输出:Adaptee: 处理特殊请求}
}
特点:
- 适配器通过组合 Adaptee 实例实现接口转换。
- 可以适配多个类(更灵活,推荐使用)。
小结:
- 类适配器:继承 Adaptee,适用于单继承场景,结构简单但灵活性较低。
- 对象适配器:组合 Adaptee,更常用,灵活性高,推荐实际开发中优先选择。
适配器模式的实际应用场景
适配器模式在实际开发中非常常见,尤其是在系统集成、旧接口兼容、新旧系统迁移等场景。下面用具体例子和类比,帮助你理解适配器模式的实际应用。
集成第三方库
场景说明:
假设你在项目中需要用到一个第三方的日志库(如 Log4j
),但你的系统统一使用了自定义的 Logger
接口。如果直接改动系统代码成本太高,可以为第三方库写一个适配器,让它实现你的 Logger
接口。
代码示例:
package com.example.adapter;// 系统要求的日志接口
public interface Logger {void log(String message);
}// 第三方日志库
import org.apache.log4j.Logger as Log4jLogger;public class Log4jAdapter implements Logger {private Log4jLogger log4jLogger;public Log4jAdapter(Log4jLogger log4jLogger) {this.log4jLogger = log4jLogger;}@Overridepublic void log(String message) {log4jLogger.info(message); // 转换到第三方库的方法}
}
数据库驱动兼容
场景说明:
假如你的系统原本用的是 MySQL 数据库,后来需要支持 PostgreSQL,但你的 DAO 层接口是统一的。可以写一个适配器,把 PostgreSQL 的驱动方法适配成你的 DAO 接口。
旧系统迁移/升级
场景说明:
公司老系统的接口和新系统不兼容,但又不能一下子全部重写。可以用适配器模式,将新系统的接口适配到老系统的实现,让二者能共同运行。
前端开发中的兼容处理
场景说明:
比如你用 Vue3 编写新组件,但项目里还有很多 Vue2 的代码,可以通过适配器模式包装 Vue2 组件,让它们可以在 Vue3 环境下运行。
JDK 中的应用
场景说明:
Java 标准库里也大量使用适配器模式。例如 java.io.InputStreamReader
适配了 InputStream
到 Reader
,让字节流可以作为字符流处理。
源码片段:
// InputStreamReader.java
public class InputStreamReader extends Reader {private final InputStream in;public int read(char[] cbuf, int offset, int length) throws IOException {// 实现将字节流转换为字符流}
}
适配器模式的优缺点分析
优点
提高代码复用性
可以复用已有的功能类(Adaptee),不需要修改原有代码,只需通过适配器进行接口转换。
增强系统灵活性和扩展性
适配器模式让系统能够兼容更多的第三方类或旧系统,便于后期扩展和集成。
解耦系统各模块
客户端只依赖目标接口(Target),不关心具体实现细节,有利于降低模块间耦合度。
满足“开放-封闭原则”
通过新增适配器类来适配不同接口,无需修改原有代码,符合 OCP 原则。
缺点
增加系统复杂度
引入适配器后,系统类的数量增加,结构变得更加复杂,理解和维护成本提升。
适配器编写成本
每当有新的 Adaptee 需要适配时,都要编写新的适配器类,增加开发工作量。
可能影响性能
适配器模式通常会增加一次方法调用(间接层),在高性能要求场景下可能略有影响。
只能适配“接口”不兼容,不能解决“业务”不兼容
适配器只能转换接口,无法解决业务逻辑上的差异,如果 Adaptee 的业务与 Target 差异过大,适配器模式就不适用了。