适配器模式

2025-11-19 06:12:23

适配器模式

在开发中,我们有时会遇到这样的情况:我们重写了系统的某个模块,但它的接口与其他模块的接口不匹配;或者我们想使用一个第三方库,但它提供的接口不能直接满足我们的需求。

这时候,修改历史遗留代码或第三方库的源代码,通常是不可行或不明智的,我们应该怎么做呢?

设计模式中的适配器模式(Adapter Pattern) 就扮演着类似的角色,它允许接口不兼容的对象之间能够相互合作。

看一个例子就能直观地理解适配器模式的用法了。假设我们有一个 Chinese 类,它有一个 speakChinese 方法只能输出中文:

// 被适配者 (Adaptee),只会说中文

class Chinese {

public void speakChinese() {

System.out.println("你好世界!");

}

}但是我们的系统需要输出英文,怎么办呢?

很简单,请个翻译过来,让他帮忙把中文翻译成英文:

// 目标接口 (Target),期望输出英文

interface EnglishSpeaker {

void speakEnglish();

}

// 适配器 (Adapter),实现了目标接口,并持有一个被适配者的实例

class TranslatorAdapter implements EnglishSpeaker {

private Chinese adaptee;

// 通过构造函数传入被适配者对象

public TranslatorAdapter(Chinese adaptee) {

this.adaptee = adaptee;

}

@Override

public void speakEnglish() {

// 调用被适配者的方法

adaptee.speakChinese();

// 添加一些转换逻辑,满足目标接口的需求

System.out.println("Translated: Hello World!");

}

}

// 客户端代码

public class Main {

public static void main(String[] args) {

Chinese chinese = new Chinese();

EnglishSpeaker adapter = new TranslatorAdapter(chinese);

// 对于客户端来说,它只知道 EnglishSpeaker 接口,并调用 speakEnglish 方法

// 适配器正确地进行了接口转换,将中文翻译成了英文

adapter.speakEnglish();

// 你好世界!

// Translated: Hello World!

}

}适配器模式主要包含以下几个核心角色:

目标(Target):客户端代码所期望的接口。在上面的例子中,就是 EnglishSpeaker 接口。被适配者(Adaptee):需要被适配的类,它拥有客户端所需的功能,但接口与目标接口不兼容。在上面的例子中,就是 Chinese 类。适配器(Adapter):连接目标和被适配者的桥梁。它实现了目标接口,并持有一个被适配者类的实例,将目标接口的调用转换为对被适配者接口的调用。在上面的例子中,就是 TranslatorAdapter 类。客户端(Client):通过目标接口与适配器进行交互。上面展示的是「对象适配器」,适配器通过构造函数传入被适配者对象,从而能够调用被适配者的方法。

还有一种「类适配器」,适配器继承被适配者类,从而能够调用被适配者的方法,类似这样:

class TranslatorAdapter extends Chinese implements EnglishSpeaker {

@Override

public void speakEnglish() {

// 调用被适配者的方法

super.speakChinese();

System.out.println("Translated: Hello World!");

}

}可以看到,两种方法只是调用被适配者的方式不同,本质上是一样的。

由于现代软件开发推崇「组合优于继承」的设计理念,且像 Java 这样的语言不支持多重类继承,所以基于继承的「类适配器」不如基于组合的「对象适配器」常见。

更多场景电源适配器现实生活中,电源适配器就是适配器模式的经典例子。家用插座提供220V交流电,但手机、笔记本电脑等设备需要的是不同规格的直流电(如5V、9V、15V等)。电源适配器就是这个「翻译官」,它将220V交流电转换成设备所需的直流电。

让我们来模拟实现一个智能的电源适配器,能够根据设备的需求提供合适的电压。

首先定义电压的类型和数据结构:

// 枚举: 电压类型

public enum VoltageType {

AC, // 交流

DC // 直流

}

// 数据类: 封装电压值和类型

public record Voltage(int value, VoltageType type) {

@Override

public String toString() {

return String.format("%dV %s", value, type);

}

// 重写 equals 和 hashCode 以便在列表中正确比较

@Override

public boolean equals(Object o) {

if (this == o) return true;

if (o == null || getClass() != o.getClass()) return false;

Voltage voltage = (Voltage) o;

return value == voltage.value && type == voltage.type;

}

}然后定义被适配者(家用插座):

// Adaptee: 被适配者 - 家用插座

public class HouseholdSocket {

public Voltage supply() {

System.out.println("家用插座: 提供 -> 220V AC");

return new Voltage(220, VoltageType.AC);

}

}定义目标接口(智能充电协议):

// Target: 目标接口 - 智能充电协议

public interface SmartChargeTarget {

// 协商并提供电压

Voltage supplyVoltage(List deviceSupportedVoltages);

}实现适配器(智能电源适配器):

// Adapter: 智能电源适配器

public class SmartPowerAdapter implements SmartChargeTarget {

private final List adapterSupportedVoltages;

private final HouseholdSocket adaptee;

// 构造函数,允许定义不同规格的适配器

public SmartPowerAdapter(HouseholdSocket adaptee, List supportedVoltages) {

this.adaptee = adaptee;

this.adapterSupportedVoltages = supportedVoltages;

System.out.println("-> 适配器已创建,支持输出: " + this.adapterSupportedVoltages);

}

@Override

public Voltage supplyVoltage(List deviceSupportedVoltages) {

Voltage bestMatch = findBestVoltageMatch(deviceSupportedVoltages);

if (bestMatch != null) {

System.out.println("适配器: 协商成功,匹配到最佳电压: " + bestMatch);

return convert(bestMatch);

} else {

System.out.println("适配器: 协商失败,没有找到双方都支持的电压模式。");

return null;

}

}

// 协商逻辑: 寻找设备和适配器都支持的最高电压

private Voltage findBestVoltageMatch(List deviceVoltages) {

for (Voltage deviceVoltage : deviceVoltages) {

if (adapterSupportedVoltages.contains(deviceVoltage)) {

return deviceVoltage;

}

}

return null;

}

private Voltage convert(Voltage dcOutput) {

Voltage acInput = adaptee.supply();

System.out.printf("适配器: 将 %s 转换为 %s\n", acInput, dcOutput);

return dcOutput;

}

}客户端代码,各种电子设备 Device 通过适配器充电:

class Device {

private final String deviceName;

private final List supportedVoltages;

// 构造函数,允许定义不同型号的设备及其支持的电压

public Device(String deviceName, List supportedVoltages) {

this.deviceName = deviceName;

this.supportedVoltages = supportedVoltages;

}

public void startCharging(SmartChargeTarget charger) {

Voltage finalVoltage = charger.supplyVoltage(supportedVoltages);

if (finalVoltage != null) {

System.out.printf("%s: ✔ 当前充电电压为: %s%n", deviceName, finalVoltage);

} else {

System.out.printf("%s: ❌ 适配器不支持我的任何充电电压%n", deviceName);

}

}

}

public class Main {

public static void main(String[] args) {

// 220V 家用插座

HouseholdSocket socket = new HouseholdSocket();

// 创建一个适配器,支持 15V, 9V, 5V

SmartPowerAdapter powerfulAdapter = new SmartPowerAdapter(socket, List.of(

new Voltage(15, VoltageType.DC),

new Voltage(9, VoltageType.DC),

new Voltage(5, VoltageType.DC)

));

// 场景一: 现代手机使用全能适配器,协商到最佳电压

Device modernPhone = new Device("iPhone 17", List.of(

new Voltage(15, VoltageType.DC),

new Voltage(9, VoltageType.DC),

new Voltage(5, VoltageType.DC)

));

modernPhone.startCharging(powerfulAdapter);

// 适配器: 协商成功,匹配到最佳电压: 15V DC

// 家用插座: 提供 -> 220V AC

// 适配器: 将 220V AC 转换为 15V DC

// iPhone 17: ✔ 当前充电电压为: 15V DC

// 场景二: 老旧手机使用全能适配器,协商到兼容的低电压

Device oldPhone = new Device("Nokia", List.of(

// 只支持5V

new Voltage(5, VoltageType.DC)

));

oldPhone.startCharging(powerfulAdapter);

// 适配器: 协商成功,匹配到最佳电压: 5V DC

// 家用插座: 提供 -> 220V AC

// 适配器: 将 220V AC 转换为 5V DC

// Nokia: ✔ 充电协商成功!当前充电电压为: 5V DC

// 场景三: 无法适配,拒绝充电

// 创建一个只能输出5V的老旧适配器

SmartPowerAdapter oldAdapter = new SmartPowerAdapter(socket, List.of(

new Voltage(5, VoltageType.DC)

));

// 设备需要至少 15V 才能充电

Device gamingDevice = new Device("Gaming Device", List.of(

new Voltage(15, VoltageType.DC)

));

gamingDevice.startCharging(oldAdapter);

// 适配器: 协商失败,没有找到双方都支持的电压模式。

// Gaming Device: ❌ 充电协商失败,适配器不支持我的任何充电电压。

}

}这个例子完美地展示了适配器模式的核心思想:

目标(Target):SmartChargeTarget 接口,定义了智能充电的协商协议。被适配者(Adaptee):HouseholdSocket 类,只能提供 220V 交流电。适配器(Adapter):SmartPowerAdapter 类,将 220V 交流电转换为设备需要的直流电。客户端(Client):各种电子设备 Device 通过适配器获得电力供应。通过适配器,不同电压需求的设备都可以从同一个家用插座获得合适的电力供应。

字符流适配器程序读取文件时最初获取到的都是字节流,所以如果我们想读取一个文本文件,就需要一个适配器将字节流转换为人类可读的字符流。

在 Java 的 I/O 类库中,InputStream 类用于读取字节流(byte stream),而 InputStreamReader 类实现了 Readable 接口,将字节流转换为字符流(character stream)。

在这个场景中:

目标(Target):Readable 接口,期望按字符读取文本文件。被适配者(Adaptee):只能读取字节流的 InputStream 类。适配器(Adapter):InputStreamReader 类,实现了 Readable 接口,并且将 InputStream 返回的字节流转换为字符流。客户端(Client):程序员可以直接使用 Readable 接口按字符读取文件。// InputStream 读取 字节流 (Adaptee)

InputStream is = new FileInputStream("test.txt");

// InputStreamReader 是一个适配器,将字节流转换为字符流 (Target)

InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8);

// 现在我们可以用 Reader 的 API 来按字符读取文件

// read 方法返回的是一个字符的 ASCII 码

int character;

while ((character = reader.read()) != -1) {

System.out.print((char) character);

}

reader.close();总结适配器模式的主要优势:

增强代码的灵活性和可扩展性:客户端代码与具体的被适配者解耦,更换被适配者对客户端是透明的。符合单一职责原则:将接口转换的逻辑封装在适配器中,而不是散落在业务代码各处。适配器模式的主要缺点:

增加了代码的复杂性:需要额外编写适配器类和目标接口。如果可以的话,直接修改被适配者的接口会更简单。

总的来说,当你需要集成一个接口不兼容的现有组件时,适配器模式是一个非常强大且实用的工具。

最新发表
友情链接

Copyright © 2022 流光追月·网游特刊 All Rights Reserved.