1. 定义
适配器模式(Adapter Pattern)是一种结构型设计模式,它允许将一个类的接口转换成客户端所期望的另一个接口。适配器模式通过“包装”原始接口来提供兼容性,使得原本因为接口不匹配而不能一起工作的类可以协同工作。
适配器模式通常用于以下情况:
- 系统中存在多个不同接口,但它们的功能是相似或有重叠的。
- 需要对一个已有的类进行复用,但该类的接口与现有代码不兼容。
- 系统中需要整合多个外部库,但它们的接口设计不一致。
2. 适用场景
- 接口不兼容的类之间需要协作:当需要让两个接口不兼容的类可以协同工作时,适配器模式是一个非常有效的解决方案。
- 系统希望通过一致的接口使用不相关的类:通过适配器模式,可以让系统通过统一的接口访问不同的类和对象。
- 重构遗留代码:在一些项目中,可能需要对旧的或第三方的代码库进行适配,适配器模式可以使得这些不兼容的接口得以协同工作。
3. 适配器模式的类型
-
类适配器模式(Class Adapter):
- 通过继承的方式实现适配。适配器类继承源类并实现目标接口。
- 适用于源类是单一继承的语言(如 Java),因为 Java 只允许一个类继承。
-
对象适配器模式(Object Adapter):
- 通过组合的方式实现适配。适配器类持有源类的对象实例,并实现目标接口。
- 适用于可以进行多重继承的场景,因为可以通过组合多个类来适配。
-
接口适配器模式(Interface Adapter):
- 通过实现目标接口,并为接口中的每个方法提供默认空实现。通常用于不希望实现接口中所有方法的情况。
4. 结构
适配器模式主要由以下几部分组成:
- Target(目标接口):定义客户端所期望的接口。
- Client(客户端):使用目标接口的对象。
- Adaptee(适配者):需要适配的类,它的接口与目标接口不兼容。
- Adapter(适配器):实现目标接口,包装适配者,并将目标接口的调用委托给适配者。
5. 结构图
+--------------+ +--------------+
| Client | | Target |
+--------------+ +--------------+
| ^
| |
| +--------------+
| | Adapter |
| +--------------+
| ^
| |
| +--------------+
| | Adaptee |
| +--------------+
+--------------------------------------+
使用适配器
6. 代码实现
1. 类适配器模式
类适配器通过继承 Adaptee
类来实现目标接口。
// 目标接口
interface Target {
void request();
}
// 适配者类
class Adaptee {
public void specificRequest() {
System.out.println("Adaptee specific request");
}
}
// 适配器类
class Adapter extends Adaptee implements Target {
@Override
public void request() {
// 调用适配者的 specificRequest 方法
specificRequest();
}
}
// 客户端代码
public class AdapterPatternExample {
public static void main(String[] args) {
Target target = new Adapter();
target.request(); // 调用适配器的 request 方法
}
}
输出:
Adaptee specific request
2. 对象适配器模式
对象适配器通过组合的方式实现适配,适配器类持有一个 Adaptee
对象的实例。
// 目标接口
interface Target {
void request();
}
// 适配者类
class Adaptee {
public void specificRequest() {
System.out.println("Adaptee specific request");
}
}
// 适配器类
class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
// 调用适配者的 specificRequest 方法
adaptee.specificRequest();
}
}
// 客户端代码
public class AdapterPatternExample {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target target = new Adapter(adaptee);
target.request(); // 调用适配器的 request 方法
}
}
输出:
Adaptee specific request
3. 接口适配器模式
接口适配器模式通常是通过抽象类来实现的,提供默认空实现,允许子类根据需要实现接口的方法。
// 目标接口
interface Target {
void methodA();
void methodB();
void methodC();
}
// 接口适配器
abstract class Adapter implements Target {
public void methodA() {}
public void methodB() {}
public void methodC() {}
}
// 子类只需要实现需要的方法
class ConcreteAdapter extends Adapter {
@Override
public void methodA() {
System.out.println("Method A implementation");
}
}
// 客户端代码
public class AdapterPatternExample {
public static void main(String[] args) {
Target target = new ConcreteAdapter();
target.methodA(); // 输出 Method A implementation
}
}
输出:
Method A implementation
7. 优缺点
优点:
- 解耦:适配器模式通过将接口转换的职责交给适配器类来实现,客户端不需要了解不同接口的实现细节,降低了客户端和适配者之间的耦合。
- 增强灵活性:可以将原本不兼容的接口通过适配器转换为兼容的接口,从而增强了系统的灵活性和可扩展性。
- 复用现有类:适配器模式可以通过封装现有的类来实现接口兼容,从而复用现有类的功能,而不需要修改它们。
缺点:
- 增加了类的数量:每一个适配器类都会增加系统中的类数量,可能会导致系统复杂度增加。
- 过多的适配器:如果适配器过多,可能会导致系统中有很多适配器类,增加了维护成本。
- 降低系统性能:由于每次调用方法时都需要通过适配器进行转发,会在一定程度上影响性能,尤其是在适配器层次过深时。
8. 哪些框架使用过适配器模式
-
Spring 框架:
- HandlerAdapter:Spring MVC 中的
HandlerAdapter
是一个典型的适配器模式应用,它将不同类型的控制器(如@Controller
、@RestController
)的请求处理方法适配成统一的请求处理流程。 - BeanPostProcessor:Spring 提供的
BeanPostProcessor
可以用来在 Bean 初始化前后进行适配和修改,允许对已有的 Bean 进行增强或适配。
- HandlerAdapter:Spring MVC 中的
-
Apache Commons IO:
- FileFilter:
FileFilter
接口提供了文件过滤功能,FilenameFilter
和FileFilter
就是通过适配器模式实现的。它们的接口不同,但功能类似,因此可以通过适配器实现统一接口访问。
- FileFilter:
-
Java IO:
- Java 的 IO 类库中也广泛使用了适配器模式,例如
InputStreamReader
就是将字节流InputStream
转换为字符流的适配器。
- Java 的 IO 类库中也广泛使用了适配器模式,例如
-
JDBC:
- 在 JDBC 中,
Connection
、Statement
等接口的实现也采用了适配器模式。例如,许多第三方数据库驱动程序在实现时,可能会提供适配器将自定义的数据库接口适配到 JDBC 的标准接口。
- 在 JDBC 中,
-
Java AWT 和 Swing:
- 在 Java GUI 编程中,
Adapter
类在MouseListener
、KeyListener
等接口中得到了应用。通过适配器类,可以仅重写感兴趣的方法,而不必实现接口中的所有方法。
- 在 Java GUI 编程中,
9. 总结
适配器模式是一种非常常见且重要的设计模式,它通过将不兼容的接口转化为兼容接口,解决了类之间接口不匹配的问题。它通常用于整合不同的类库、复用已有代码或与遗留代码进行集成。尽管适配器模式增加了系统的复杂性,但在需要兼容多个接口时,它是一个非常有效的解决方案。