适配器模式(Adapter Pattern)是一种常见的设计模式,它属于结构型设计模式,用于将一个类的接口转换成客户端所期望的另一个接口,从而使两者能够协同工作。适配器模式主要用于解决接口不兼容的问题,它允许不同接口的类协同工作,而不需要修改它们的源代码。
结构
适配器模式通常涉及以下几个部分:
- 目标接口(Target Interface):客户端期望的接口,适配器会实现这个接口,以便与客户端一起工作。
- 适配器(Adapter):适配器类实现了目标接口,并包装了一个或多个需要适配的对象。它充当了客户端与被适配对象之间的桥梁。
- 被适配对象(Adaptee):被适配的类或接口,即客户端需要与之协同工作的对象,但其接口与目标接口不兼容。
适配器模式的主要目标是使客户端能够使用被适配对象,而无需修改客户端的代码。这通常在以下情况下使用:
- 当有一个已经存在的类,它的接口与现需求不匹配,但不能修改这个类的源代码时。
- 当想要复用一些现有的类,但它们的接口与现有代码不兼容时。
- 当希望将多个类的接口统一,以便它们可以一起工作。
下面是一个简单的示例,展示了适配器模式的使用:
// 目标接口
interface Target {
void request();
}
// 被适配对象
class Adaptee {
void specificRequest() {
System.out.println("Adaptee's specific request");
}
}
// 适配器
class Adapter implements Target {
private Adaptee adaptee;
Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
System.out.println("Adapter's request");
adaptee.specificRequest();
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target adapter = new Adapter(adaptee);
clientCode(adapter);
}
static void clientCode(Target target) {
target.request();
}
}
适配器模式有两种常见的变体:类适配器模式和对象适配器模式。它们都用于将一个类的接口转换成另一个接口,但它们实现的方式略有不同。
在很多情况下,对象适配器模式更常见,因为它更灵活,并且避免了一些多重继承可能引发的问题。类适配器模式在一些特定的情况下也可以派上用场,但需要小心使用,以避免潜在的继承冲突和复杂性,并且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少一些。
类适配器模式
类适配器模式(Class Adapter Pattern)是一种适配器模式的变体,它用于将一个类的接口适配成另一个类的接口。在类适配器模式中,适配器类通过多重继承来实现目标接口和被适配对象的接口,同时继承了目标接口和被适配对象的类。
类适配器模式的关键要素:
- 目标接口(Target Interface):客户端期望的接口,适配器将会实现这个接口。
- 适配器类(Adapter Class):适配器类继承了目标接口,同时持有一个被适配对象的实例。
- 被适配对象(Adaptee Class):被适配的类,它的接口与目标接口不兼容。
- 继承关系:适配器类通过多重继承,继承了目标接口和被适配对象的类,然后重写目标接口的方法,将调用委托给被适配对象的方法。
下面用代码实现类适配器的示例
// 目标接口
interface Target {
void request();
}
// 被适配对象
class Adaptee {
public void specificRequest() {
System.out.println("Adaptee's specific request");
}
}
// 适配器类,通过多重继承同时实现Target接口和继承Adaptee类
class Adapter extends Adaptee implements Target {
public void request() {
System.out.println("Adapter's request");
specificRequest(); // 调用被适配对象的方法
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Target target = new Adapter();
target.request(); // 调用适配器的request方法,实际上委派给Adaptee的specificRequest方法
}
}
从上面的代码中可以得知,类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之则不可用。并且如果适配器需要同时适配两个对象时,因为java是单继承的原因,而无法满足此需求。
对象适配器模式
对象适配器模式是一种设计模式,用于解决接口不兼容的问题。在对象适配器模式中,适配器类包含一个被适配对象的实例作为其属性,然后通过实现目标接口来将被适配对象的接口转换成客户端期望的接口。这种模式使用组合来实现适配,而不是使用多重继承。
以下是一个使用Java实现的对象适配器模式示例:
// 目标接口
interface Target {
void request();
}
// 被适配对象
class Adaptee {
public void specificRequest() {
System.out.println("Adaptee's specific request");
}
}
// 适配器类,通过组合被适配对象实例并实现目标接口来实现适配
class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
System.out.println("Adapter's request");
adaptee.specificRequest(); // 调用被适配对象的方法
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target target = new Adapter(adaptee);
target.request(); // 调用适配器的request方法,实际上委派给Adaptee的specificRequest方法
}
}
在上面示例中,Adapter
类包含一个Adaptee
对象的实例作为属性,然后实现了Target
接口。在Adapter
的request
方法中,它调用了Adaptee
的specificRequest
方法,实现了适配。
客户端代码首先创建一个Adaptee
对象,然后通过创建一个Adapter
对象并传递Adaptee
对象作为参数来实现适配。当客户端调用Adapter
的request
方法时,实际上调用了Adaptee
的方法,从而实现了适配。
运行客户端代码时,输出将会是:
Adapter's request
Adaptee's specific request
这表明对象适配器模式成功地将Adaptee
的接口适配成了Target
接口,使它们可以一起工作,而不需要修改Adaptee
的源代码。
注意:还有一个适配器模式是接口适配器模式。当不希望实现一个接口中所有的方法时,可以创建一个抽象类Adapter,实现所有方法。而此时只需要继承该抽象类即可。
使用场景
以下是适配器模式的一些常见应用场景:
- 集成新旧系统:当需要集成一个新的系统或组件到已有系统中时,新系统的接口可能与旧系统不兼容。适配器可以帮助新系统的接口适配成与旧系统兼容的接口,从而无缝地集成两者。
- 类库使用:当使用第三方类库或组件时,它们的接口可能不符合的需求。通过创建适配器,可以将第三方类库的接口转换成符合应用程序的接口,使得类库能够更容易地与应用程序协同工作。
- 多态适配:在面向对象编程中,不同的类可能具有不同的接口,但它们可能都有相似的功能。适配器可以用来创建一个统一的接口,使得这些不同的类可以以相同的方式被使用,从而实现多态性。
- 兼容不同版本:在软件开发中,有时候需要升级一个组件或库的版本,但新版本的接口可能与旧版本不兼容。使用适配器模式可以帮助在不破坏旧代码的情况下适应新版本的接口。
- 与外部系统交互:当的应用程序需要与外部系统或服务进行通信时,这些外部系统可能使用不同的协议或数据格式。适配器可以用来将的应用程序与外部系统之间的通信接口进行适配,以确保数据正确传输和解释。
- 测试模拟:在测试过程中,可能需要模拟一些外部组件或服务的行为。适配器可以帮助创建模拟对象,以便进行单元测试或集成测试,同时不会影响到真实的外部组件或服务。
源码解析
java.io.OutputStream
是一个抽象类,它定义了输出流的基本行为,而其子类可以根据需要实现特定的输出源。
以 FileOutputStream
为例,它是用于将数据写入文件的输出流。FileOutputStream
继承自 OutputStream
并实现了其中的抽象方法,同时提供了与文件相关的功能。下面是一个简单的示例,演示了如何使用 FileOutputStream
来写入数据到文件中:
import java.io.*;
public class FileOutputStreamExample {
public static void main(String[] args) {
String filename = "output.txt";
String data = "Hello, OutputStream example!";
try {
// 创建文件输出流
OutputStream outputStream = new FileOutputStream(filename);
// 将字符串数据转换为字节数组
byte[] bytes = data.getBytes();
// 使用输出流写入数据到文件
outputStream.write(bytes);
// 关闭输出流
outputStream.close();
System.out.println("Data has been written to " + filename);
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这个示例中,使用了 FileOutputStream
来写入数据到文件 “output.txt” 中。FileOutputStream
是 OutputStream
的子类,它实现了 OutputStream
中的 write
方法,这个方法用于将数据写入输出流。
这里的关键是 FileOutputStream
充当了适配器的角色。它适配了 OutputStream
的接口,使得开发人员可以使用统一的输出流接口来写入数据到文件,而不必关心底层文件操作的细节。这种方式使代码更具可移植性和可维护性,因为可以轻松地切换不同的输出流类型(如网络流、内存流等),而不必改变写入数据的方式。
在 FileOutputStream
内部,它通过适配 OutputStream
接口,将写入文件的操作转化为文件系统的底层操作,这就是适配器模式的应用之一(对象适配器模式)。