简介
适配器模式(Adapter Pattern)又称为变压器模式,它是一种结构型设计模式。适配器模式的目的是将一个类的接口转换成客户端所期望的另一种接口,从而使原本因接口不匹配而不能一起工作的两个类能够一起工作。
适配器模式有两种形式:类适配器(Class Adapter)和对象适配器(Object Adapter)。类适配器通过继承实现适配器功能,让Adapter实现Target接口并且继承Adaptee,这样Adapter就具备Target和Adaptee的特性,可以将两者进行转化。对象适配器通过对象组合实现适配器功能,将一个对象组合到另一个对象中,以适配其接口。
结构
适配器模式(Adapter)包含以下主要角色:
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
案例实现
类适配器
实现方式:定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
【例】读卡器
现有一台电脑只能读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来。
类图如下:
代码如下:
//SD卡的接口
public interface SDCard {
//读取SD卡方法
String readSD();
//写入SD卡功能
void writeSD(String msg);
}
//SD卡实现类
public class SDCardImpl implements SDCard {
public String readSD() {
String msg = "sd card read a msg :hello word SD";
return msg;
}
public void writeSD(String msg) {
System.out.println("sd card write msg : " + msg);
}
}
//电脑类
public class Computer {
public String readSD(SDCard sdCard) {
if(sdCard == null) {
throw new NullPointerException("sd card null");
}
return sdCard.readSD();
}
}
//TF卡接口
public interface TFCard {
//读取TF卡方法
String readTF();
//写入TF卡功能
void writeTF(String msg);
}
//TF卡实现类
public class TFCardImpl implements TFCard {
public String readTF() {
String msg ="tf card read msg : hello word tf card";
return msg;
}
public void writeTF(String msg) {
System.out.println("tf card write a msg : " + msg);
}
}
//定义适配器类(SD兼容TF)
public class SDAdapterTF extends TFCardImpl implements SDCard {
public String readSD() {
System.out.println("adapter read tf card ");
return readTF();
}
public void writeSD(String msg) {
System.out.println("adapter write tf card");
writeTF(msg);
}
}
//测试类
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
SDCard sdCard = new SDCardImpl();
System.out.println(computer.readSD(sdCard));
System.out.println("------------");
SDAdapterTF adapter = new SDAdapterTF();
System.out.println(computer.readSD(adapter));
}
}
类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用。
对象适配器模式
实现方式:对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。
【例】读卡器
我们使用对象适配器模式将读卡器的案例进行改写。类图如下:
代码如下:
类适配器模式的代码,我们只需要修改适配器类(SDAdapterTF)和测试类。
//创建适配器对象(SD兼容TF)
public class SDAdapterTF implements SDCard {
private TFCard tfCard;
public SDAdapterTF(TFCard tfCard) {
this.tfCard = tfCard;
}
public String readSD() {
System.out.println("adapter read tf card ");
return tfCard.readTF();
}
public void writeSD(String msg) {
System.out.println("adapter write tf card");
tfCard.writeTF(msg);
}
}
//测试类
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
SDCard sdCard = new SDCardImpl();
System.out.println(computer.readSD(sdCard));
System.out.println("------------");
TFCard tfCard = new TFCardImpl();
SDAdapterTF adapter = new SDAdapterTF(tfCard);
System.out.println(computer.readSD(adapter));
}
}
注意:还有一个适配器模式是接口适配器模式。当不希望实现一个接口中所有的方法时,可以创建一个抽象类Adapter ,实现所有方法。而此时我们只需要继承该抽象类即可。
优缺点
适配器模式的优点包括:
- 扩展性:适配器模式可以用于将一个类的接口转换成客户端所期望的另一个接口,从而使得原本因接口不匹配而不能一起工作的两个类能够一起工作,这使得系统更加灵活和可扩展。
- 耦合度降低:通过适配器模式将不兼容的类进行适配,可以将原本耦合度较高的两个类解耦,降低系统的复杂性。
- 提高复用性:适配器模式可以将一个类的接口转换成客户端所期望的另一个接口,使得该类可以被更多的客户端使用,提高了代码的复用性。
然而,适配器模式也存在一些缺点:
- 增加中转层:适配器模式需要在原有系统基础上增加一个新的适配器层,这会增加系统的复杂性和额外的开销。
- 可能引入错误:由于适配器只是转换接口,而不修改原有类的实现,因此如果原有类的实现存在错误,适配器无法修正这些错误。
- 不易测试:由于适配器依赖于原有系统,因此对适配器的测试需要依赖于原有系统,增加了测试的难度。
应用场景
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
源码中的应用
JDK
Java I/O 库
- 在 Java I/O 库中,有很多适配器模式的应用。比如,
InputStreamReader
和OutputStreamWriter
类,它们将字节流适配为字符流,允许读写字符数据而不是字节数据。这些类就充当了适配器的角色,使得不同类型的流可以通过统一的接口进行操作。
Swing GUI 组件
- 在 Swing GUI 编程中,也有一些适配器模式的应用。例如,
WindowAdapter
、MouseAdapter
、KeyAdapter
等适配器类,它们实现了对应的监听器接口,但是提供了默认的空实现,使得开发者可以选择性地实现感兴趣的事件处理方法,而不必实现所有的方法。
Arrays 类
- JDK 中的
Arrays
类提供了很多静态方法来操作数组,其中一些方法就是适配器模式的应用。例如,asList()
方法可以将数组适配为 List 接口的实现类,这样就可以通过 List 的方式来操作数组。
java.util.Collections 类
Collections
类中也有一些适配器模式的应用。例如,enumeration()
方法可以将Enumeration
适配为Iterator
,使得旧的枚举类型可以通过 Iterator 的方式进行遍历。
JDBC(Java Database Connectivity)
- 在 JDBC 中,
ResultSet
对象提供了对数据库查询结果的操作。ResultSet
接口中有一系列的getXXX()
方法用于获取不同类型的数据,这些方法就是适配器模式的应用,将数据库中的数据适配为 Java 中的基本数据类型。
Spring
Spring MVC 中的适配器
- 在 Spring MVC 中,
HandlerAdapter
充当了适配器的角色。它负责将不同类型的处理器(handler)适配为HandlerInterceptor
或HandlerExecutionChain
,以便在请求处理流程中调用相应的处理器。
AOP(面向切面编程)中的适配器
- Spring AOP 使用代理模式来实现切面(Aspect)的横切关注点。在这个过程中,适配器模式也发挥了作用。Spring AOP 通过适配器来将切面(Aspect)适配为通知(Advice),以便在目标方法调用前后或发生异常时执行相应的逻辑。
JDBC 中的适配器
- 在 Spring 的 JDBC 模块中,
JdbcAdapter
类提供了一组适配器方法,用于将不同的数据库厂商提供的 JDBC API 适配为 Spring JDBC 模板中的通用方法。这些适配器方法使得开发者可以以统一的方式访问不同数据库的数据。
消息队列中的适配器
- 在 Spring Integration 框架中,适配器模式也被广泛应用。例如,
MessageAdapter
用于将消息适配为 Spring Integration 中的消息类型,从而与 Spring Integration 的通道和端点进行交互。
测试模块中的适配器
- 在 Spring Test 模块中,提供了一系列的适配器类,用于将不同的测试框架适配为 Spring Test 上下文中可用的测试类。例如,
TestContextManager
适配了 JUnit 和 TestNG 测试框架,使得它们能够在 Spring 测试上下文中进行集成测试。
.Net Core
接口适配器
- 接口适配器是指适配器类实现了一个接口,并将该接口的方法委托给另一个类的实例。在 .NET Core 中,这种模式常见于接口的默认实现。
- 例如,在 .NET Core 中的 ASP.NET Core 框架中,
IActionResult
接口定义了一种返回结果的标准,而ActionResult
类则是这个接口的适配器,提供了接口方法的默认实现。
public interface IActionResult
{
Task ExecuteResultAsync(ActionContext context);
}
public class ActionResult : IActionResult
{
public virtual Task ExecuteResultAsync(ActionContext context)
{
// 提供了接口方法的默认实现
return Task.CompletedTask;
}
}
类适配器
- 类适配器是指适配器类继承了另一个类,并实现了一个接口。在 .NET Core 中,类适配器常见于需要同时继承某个基类并实现某个接口的情况。
- 例如,Entity Framework Core 中的数据库上下文(
DbContext
)就是一个类适配器,继承自DbContextBase
并实现了IDbContext
接口。
public class DbContext : DbContextBase, IDbContext
{
// 实现接口方法
public void SaveChanges()
{
// ...
}
}
对象适配器
- 对象适配器是指适配器类持有另一个类的实例,并实现一个接口。在 .NET Core 中,对象适配器常见于需要通过组合而非继承来实现适配的情况。
- 例如,在 .NET Core 中的
HttpClient
类可以被视为一个对象适配器,它持有一个HttpMessageHandler
实例,并实现了IDisposable
接口。
public class HttpClient : IDisposable
{
private readonly HttpMessageHandler _handler;
public HttpClient(HttpMessageHandler handler)
{
_handler = handler;
}
// 实现接口方法
public void Dispose()
{
_handler.Dispose();
}
}