概述
适配器模式(Adapter Pattern)是结构型设计模式的典型代表,其核心意图在于将一个类的接口转换成客户端期望的另一个接口,使得原本由于接口不兼容而无法协同工作的类能够在一起工作。这个定义揭示了一个深刻的设计哲学:接口是契约,但契约并非一成不变,通过适配层可以实现契约的平滑转换。
在日常开发中,我们频繁遭遇接口不兼容的困境:复用遗留系统的成熟功能却发现方法签名与新架构格格不入;集成第三方SDK时其API风格与内部规范大相径庭;新旧系统对接时数据格式与调用方式南辕北辙。适配器模式正是破解这些难题的银弹,它通过引入一个转换层,在保持客户端调用一致性的前提下,优雅地弥合了接口差异。
本文将带领读者踏上一段由浅入深的技术旅程。我们首先从最原始的硬编码接口转换入手,直观感受无模式设计的痛点;继而深入探讨类适配器与对象适配器的两种经典实现,剖析继承与组合在这一场景下的设计权衡;最后将视野扩展至框架源码与分布式系统,揭示适配器模式在JDK、Spring、SLF4J等主流框架中的精妙运用,以及在多云存储、异构消息中间件等分布式场景下的架构价值。让我们一同揭开适配器模式从代码技巧到架构思想的全景图卷。
一、模式定义与结构
1.1 GoF标准定义
根据GoF《设计模式:可复用面向对象软件的基础》的经典定义:
适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
这一模式又名包装器(Wrapper),其设计动机源于解决接口失配问题,使得复用已有类成为可能,而不必修改其原始代码。
1.2 Mermaid UML类图
classDiagram
class Target {
<<interface>>
+request() void
}
class Adapter {
-adaptee: Adaptee
+request() void
}
class Adaptee {
+specificRequest() void
}
class Client {
+main() void
}
Target <|.. Adapter
Adapter --> Adaptee : 持有引用
Client --> Target : 依赖
note for Adapter "request() {\n adaptee.specificRequest();\n}"
1.3 两种结构变体详解
适配器模式存在两种经典的实现变体:类适配器与对象适配器。二者虽共享相同的设计目标,却在实现机制与适用边界上存在显著差异。
类适配器(Class Adapter) 基于继承机制实现。适配器类同时继承Adaptee并实现Target接口,在重写的request()方法中直接调用从父类继承的specificRequest()方法。这种方式的优势在于代码简洁,无需额外的委托对象,且可以重写Adaptee的部分行为以定制适配逻辑。然而其局限性同样明显:受制于Java单继承的约束,一个适配器只能适配一个Adaptee类及其子类,无法同时适配多个被适配者;此外,由于静态绑定关系,适配器与Adaptee形成强耦合,灵活性受限。
对象适配器(Object Adapter) 基于组合机制实现。适配器类实现Target接口,并通过成员变量持有Adaptee对象的引用,在request()方法中委托该引用执行specificRequest()。这种方式的优势在于灵活性极强:一个适配器可以适配多个不同的Adaptee对象(甚至可以在运行时动态替换),且由于适配器仅依赖Adaptee的接口而非具体实现,符合面向接口编程原则。此外,对象适配器不受单继承限制,可以同时实现多个目标接口。代价是需要额外的对象引用管理,代码稍显冗余。
在实际工程中,对象适配器因其组合带来的灵活性而被更为广泛地推荐使用,符合"优先使用组合而非继承"的设计原则。
1.4 角色职责解析
对照上述UML类图,适配器模式包含以下四类核心角色:
-
Target(目标接口):定义客户端所期望使用的接口规范,是客户端代码与适配器之间的契约。在本模式中表现为一个接口或抽象类,声明了客户端希望调用的业务方法。
-
Adaptee(被适配者):已存在的、需要被复用的类,其接口与Target不兼容。Adaptee封装了成熟稳定的业务逻辑,但因方法签名、参数类型等差异而无法被客户端直接调用。
-
Adapter(适配器):整个模式的核心枢纽,负责完成接口转换工作。它实现Target接口,同时通过继承或组合关联Adaptee,在Target接口方法的实现中将调用请求翻译为Adaptee能够理解的格式并转发。
-
Client(客户端):面向Target接口编程的调用方。Client仅依赖Target接口,无需感知Adaptee的存在,从而实现了客户端与具体被适配类的解耦。
二、代码演进与实现
2.1 原始代码:接口不兼容的困境
/**
* 场景:音频播放器系统
* 现有高级播放器接口,但第三方音频解码库的接口不兼容
*/
// 第三方音频解码库(模拟被适配者)
class ThirdPartyAudioDecoder {
public void decodeMp3File(String filePath) {
System.out.println("第三方解码器:解码MP3文件 - " + filePath);
}
public void decodeWavFile(String filePath) {
System.out.println("第三方解码器:解码WAV文件 - " + filePath);
}
}
// 客户端期望的高级播放器接口
interface AdvancedPlayer {
void play(String audioType, String fileName);
}
// 原始客户端代码 - 硬编码依赖第三方库
class NaiveClient {
private ThirdPartyAudioDecoder decoder = new ThirdPartyAudioDecoder();
public void playAudio(String audioType, String fileName) {
// 问题:客户端必须感知第三方库的方法名差异
if ("mp3".equalsIgnoreCase(audioType)) {
decoder.decodeMp3File(fileName); // 接口不匹配
} else if ("wav".equalsIgnoreCase(audioType)) {
decoder.decodeWavFile(fileName); // 接口不匹配
} else {
System.out.println("不支持的音频格式:" + audioType);
}
// 弊端:每增加一种格式,客户端代码必须修改,违反开闭原则
}
}
public class OriginalDemo {
public static void main(String[] args) {
NaiveClient client = new NaiveClient();
client.playAudio("mp3", "song.mp3");
client.playAudio("wav", "recording.wav");
// 问题分析:
// 1. 客户端与第三方库强耦合
// 2. 扩展新格式需要修改客户端代码
// 3. 无法面向统一接口编程
// 4. 第三方库更换时需要大量改动
}
}
2.2 类适配器模式重构
/**
* 类适配器实现
* 基于继承机制,适配器同时继承Adaptee并实现Target
*/
// 目标接口:客户端期望的统一播放接口
interface MediaPlayer {
void play(String audioType, String fileName);
}
// 被适配者:第三方音频解码库
class AdvancedAudioDecoder {
public void decodeMp3(String file) {
System.out.println("[高级解码器] 解码MP3音频流: " + file);
}
public void decodeWav(String file) {
System.out.println("[高级解码器] 解码WAV音频流: " + file);
}
}
// 类适配器:通过继承获得Adaptee能力,同时实现Target接口
class ClassMediaAdapter extends AdvancedAudioDecoder implements MediaPlayer {
@Override
public void play(String audioType, String fileName) {
// 接口转换核心:将客户端的play调用转换为Adaptee的具体解码方法
if ("mp3".equalsIgnoreCase(audioType)) {
// 调用从父类继承的方法
super.decodeMp3(fileName);
} else if ("wav".equalsIgnoreCase(audioType)) {
super.decodeWav(fileName);
} else {
System.out.println("类适配器:不支持的格式 - " + audioType);
}
}
}
// 客户端:面向Target接口编程
class AudioPlayer {
private MediaPlayer mediaPlayer;
public AudioPlayer(MediaPlayer mediaPlayer) {
this.mediaPlayer = mediaPlayer;
}
public void startPlay(String audioType, String fileName) {
System.out.println("客户端:请求播放 " + audioType + " 格式文件");
mediaPlayer.play(audioType, fileName);
}
}
public class ClassAdapterDemo {
public static void main(String[] args) {
// 客户端仅依赖MediaPlayer接口
MediaPlayer adapter = new ClassMediaAdapter();
AudioPlayer player = new AudioPlayer(adapter);
player.startPlay("mp3", "music/summer.mp3");
player.startPlay("wav", "music/vocal.wav");
// 类适配器优势:
// 1. 代码简洁,无需额外的委托对象
// 2. 可以直接重写Adaptee的方法
// 类适配器局限:
// 1. Java单继承限制,无法适配多个Adaptee
// 2. 适配器与Adaptee静态绑定,灵活性差
// 3. 暴露了Adaptee的所有public方法,破坏封装
}
}
2.3 对象适配器模式重构
/**
* 对象适配器实现
* 基于组合机制,适配器持有Adaptee引用并实现Target接口
*/
// 目标接口(同前)
interface Player {
void play(String audioType, String fileName);
}
// 被适配者:第三方音频解码库(同前)
class AudioDecoder {
public void decodeMp3(String file) {
System.out.println("[音频解码器] 解码MP3: " + file);
}
public void decodeWav(String file) {
System.out.println("[音频解码器] 解码WAV: " + file);
}
}
// 对象适配器:实现Target接口,通过组合持有Adaptee
class ObjectMediaAdapter implements Player {
// 持有被适配者的引用(可以通过构造函数注入)
private AudioDecoder audioDecoder;
public ObjectMediaAdapter(AudioDecoder decoder) {
this.audioDecoder = decoder;
}
@Override
public void play(String audioType, String fileName) {
// 委托给Adaptee执行实际工作
if ("mp3".equalsIgnoreCase(audioType)) {
audioDecoder.decodeMp3(fileName); // 委托调用
} else if ("wav".equalsIgnoreCase(audioType)) {
audioDecoder.decodeWav(fileName); // 委托调用
} else {
System.out.println("对象适配器:格式不支持 - " + audioType);
}
}
}
// 增强版:支持动态切换被适配者
class FlexibleMediaAdapter implements Player {
private AudioDecoder decoder;
public void setDecoder(AudioDecoder decoder) {
this.decoder = decoder; // 运行时动态替换Adaptee
}
public FlexibleMediaAdapter(AudioDecoder decoder) {
this.decoder = decoder;
}
@Override
public void play(String audioType, String fileName) {
if (decoder == null) {
System.out.println("错误:未设置解码器");
return;
}
if ("mp3".equalsIgnoreCase(audioType)) {
decoder.decodeMp3(fileName);
} else if ("wav".equalsIgnoreCase(audioType)) {
decoder.decodeWav(fileName);
} else {
System.out.println("格式不支持:" + audioType);
}
}
}
public class ObjectAdapterDemo {
public static void main(String[] args) {
// 创建被适配者实例
AudioDecoder decoder = new AudioDecoder();
// 通过构造函数注入Adaptee
Player adapter = new ObjectMediaAdapter(decoder);
// 客户端代码与类适配器完全一致
adapter.play("mp3", "music/rock.mp3");
adapter.play("wav", "music/jazz.wav");
// 演示运行时动态切换Adaptee
FlexibleMediaAdapter flexibleAdapter = new FlexibleMediaAdapter(new AudioDecoder());
flexibleAdapter.play("mp3", "song1.mp3");
// 可以随时替换解码器实例(例如使用不同的第三方库)
flexibleAdapter.setDecoder(new AudioDecoder()); // 实际应用中可能是不同的实现类
flexibleAdapter.play("wav", "song2.wav");
// 对象适配器优势:
// 1. 符合"组合优于继承"原则,灵活性高
// 2. 一个适配器可以适配多个Adaptee(通过多个引用)
// 3. 可以在运行时动态替换Adaptee
// 4. 不暴露Adaptee的接口,封装性好
// 对象适配器劣势:
// 1. 代码稍复杂,需要管理Adaptee引用
// 2. 无法直接重写Adaptee的行为(需要通过包装)
}
}
2.4 双向适配器实现
/**
* 双向适配器
* 使两个不兼容的接口能够互相调用
*/
// 接口A:新系统的播放器接口
interface NewPlayer {
void playModern(String format, String file);
String getPlayerInfo();
}
// 接口B:旧系统的播放器接口
interface LegacyPlayer {
void playLegacy(String filePath, int fileType);
String getLegacyVersion();
}
// 新系统播放器实现
class ModernAudioPlayer implements NewPlayer {
@Override
public void playModern(String format, String file) {
System.out.println("[新播放器] 播放 " + format + " 文件: " + file);
}
@Override
public String getPlayerInfo() {
return "Modern Player v2.0";
}
}
// 旧系统播放器实现
class OldAudioPlayer implements LegacyPlayer {
@Override
public void playLegacy(String filePath, int fileType) {
String typeStr = fileType == 1 ? "MP3" : "WAV";
System.out.println("[旧播放器] 播放 " + typeStr + " 文件: " + filePath);
}
@Override
public String getLegacyVersion() {
return "Legacy Player v1.0";
}
}
// 双向适配器:同时实现两个接口,使新旧系统可以互调
class TwoWayPlayerAdapter implements NewPlayer, LegacyPlayer {
private NewPlayer newPlayer;
private LegacyPlayer legacyPlayer;
// 构造函数:可以传入任一方向的实例
public TwoWayPlayerAdapter(NewPlayer newPlayer) {
this.newPlayer = newPlayer;
}
public TwoWayPlayerAdapter(LegacyPlayer legacyPlayer) {
this.legacyPlayer = legacyPlayer;
}
// 实现NewPlayer接口 - 适配到LegacyPlayer
@Override
public void playModern(String format, String file) {
if (legacyPlayer != null) {
// 将新接口调用转换为旧接口调用
int typeCode = "MP3".equalsIgnoreCase(format) ? 1 : 2;
legacyPlayer.playLegacy(file, typeCode);
System.out.println(" [双向适配] New->Legacy 转换完成");
} else if (newPlayer != null) {
newPlayer.playModern(format, file);
}
}
@Override
public String getPlayerInfo() {
if (legacyPlayer != null) {
return "Adapted: " + legacyPlayer.getLegacyVersion();
}
return newPlayer.getPlayerInfo();
}
// 实现LegacyPlayer接口 - 适配到NewPlayer
@Override
public void playLegacy(String filePath, int fileType) {
if (newPlayer != null) {
// 将旧接口调用转换为新接口调用
String format = fileType == 1 ? "MP3" : "WAV";
newPlayer.playModern(format, filePath);
System.out.println(" [双向适配] Legacy->New 转换完成");
} else if (legacyPlayer != null) {
legacyPlayer.playLegacy(filePath, fileType);
}
}
@Override
public String getLegacyVersion() {
if (newPlayer != null) {
return "Adapted: " + newPlayer.getPlayerInfo();
}
return legacyPlayer.getLegacyVersion();
}
}
public class TwoWayAdapterDemo {
public static void main(String[] args) {
// 创建新旧两个播放器
NewPlayer modernPlayer = new ModernAudioPlayer();
LegacyPlayer oldPlayer = new OldAudioPlayer();
System.out.println("=== 场景1:新系统调用旧播放器 ===");
// 创建适配器,包装旧播放器供新系统使用
TwoWayPlayerAdapter adapterForNew = new TwoWayPlayerAdapter(oldPlayer);
// 新系统通过NewPlayer接口调用,实际执行旧播放器逻辑
adapterForNew.playModern("MP3", "old_song.mp3");
System.out.println("播放器信息: " + adapterForNew.getPlayerInfo());
System.out.println("\n=== 场景2:旧系统调用新播放器 ===");
// 创建适配器,包装新播放器供旧系统使用
TwoWayPlayerAdapter adapterForLegacy = new TwoWayPlayerAdapter(modernPlayer);
// 旧系统通过LegacyPlayer接口调用,实际执行新播放器逻辑
adapterForLegacy.playLegacy("new_song.wav", 2);
System.out.println("播放器版本: " + adapterForLegacy.getLegacyVersion());
// 双向适配器应用场景:
// 1. 系统迁移过渡期,新旧系统需要双向互调
// 2. 第三方库升级,需要同时支持新旧API
// 3. 跨平台开发,不同平台的API需要相互转换
}
}
2.5 调用时序图
sequenceDiagram
participant Client as 客户端
participant Target as Target接口
participant Adapter as 适配器
participant Adaptee as 被适配者
Client->>Target: play(audioType, fileName)
Note over Client,Target: 客户端面向Target接口编程
Target->>Adapter: play(audioType, fileName)
Note over Adapter: 适配器接收请求<br/>进行参数转换
alt audioType == "mp3"
Adapter->>Adaptee: decodeMp3(fileName)
Note over Adaptee: 执行被适配者的<br/>实际业务逻辑
Adaptee-->>Adapter: 解码完成
else audioType == "wav"
Adapter->>Adaptee: decodeWav(fileName)
Adaptee-->>Adapter: 解码完成
else 其他格式
Adapter-->>Client: 格式不支持异常
end
Adapter-->>Target: 执行结果
Target-->>Client: 返回结果
Note over Client,Adaptee: 客户端完全感知不到Adaptee的存在<br/>适配器完成了透明的接口转换
时序图详解:
上图完整呈现了适配器模式的动态调用流程,整个交互过程分为四个清晰的阶段:
第一阶段——接口调用:客户端持有Target接口的引用,调用其定义的play()方法。此时客户端完全不知道背后是哪个具体实现在服务,这正是面向接口编程的价值体现。
第二阶段——适配器介入:调用请求到达适配器实例。适配器作为Target的实现类,开始履行其核心职责——协议转换。它解析客户端传入的参数(audioType和fileName),判断需要调用Adaptee的哪个具体方法。这一过程可能涉及参数重组、类型转换、默认值填充等复杂逻辑。
第三阶段——委托执行:适配器确定目标方法后,将请求委托给Adaptee对象(在类适配器中是调用继承的方法,在对象适配器中是调用持有的引用)。Adaptee执行其既有的业务逻辑,这些逻辑可能是经过长期验证的成熟算法,或是第三方库封装的复杂功能。注意Adaptee完全无需知道适配器的存在,保持了自身独立性。
第四阶段——结果返回:Adaptee将执行结果返回给适配器,适配器可能对结果进行格式转换(如异常类型转换、返回值映射),最终将处理后的结果通过Target接口返回给客户端。整个过程中,客户端与Adaptee之间没有任何直接耦合,适配器充当了透明的转换桥梁。
三、源码级应用分析
3.1 JDK中的适配器模式
3.1.1 Arrays.asList() —— 数组到List的适配
import java.util.Arrays;
import java.util.List;
/**
* java.util.Arrays#asList() 源码分析
* 这是典型的适配器模式应用,将数组适配为List接口
*/
public class ArraysAsListAdapterDemo {
public static void main(String[] args) {
// 数组(被适配者)
String[] array = {"Java", "Python", "Go", "Rust"};
// asList()返回一个适配器——Arrays$ArrayList
// 该类是Arrays的内部类,实现了List接口
List<String> list = Arrays.asList(array);
// 客户端通过List接口操作,实际内部委托给数组
System.out.println("List大小: " + list.size());
System.out.println("第二个元素: " + list.get(1));
// 源码剖析(简化版):
// public static <T> List<T> asList(T... a) {
// return new ArrayList<>(a); // 此ArrayList是Arrays的内部类
// }
//
// private static class ArrayList<E> extends AbstractList<E> {
// private final E[] a; // 持有数组引用
//
// ArrayList(E[] array) {
// a = Objects.requireNonNull(array);
// }
//
// @Override
// public E get(int index) {
// return a[index]; // 委托给数组
// }
//
// @Override
// public int size() {
// return a.length; // 委托给数组
// }
// }
// 适配器特性体现:修改数组会影响List,反之亦然
array[0] = "Kotlin";
System.out.println("修改数组后List首元素: " + list.get(0));
list.set(1, "Scala");
System.out.println("修改List后数组第二元素: " + array[1]);
}
}
3.1.2 Collections包装类 —— 包装适配器
import java.util.*;
/**
* java.util.Collections的包装方法
* synchronizedList、unmodifiableList等是包装适配器模式的典型应用
*/
public class CollectionsWrapperAdapterDemo {
public static void main(String[] args) {
// 原始ArrayList(被适配者)
List<String> originalList = new ArrayList<>();
originalList.add("Item1");
originalList.add("Item2");
// 同步包装适配器:将非线程安全的List适配为线程安全的List
List<String> syncList = Collections.synchronizedList(originalList);
// 源码剖析:
// public static <T> List<T> synchronizedList(List<T> list) {
// return new SynchronizedList<>(list); // 返回适配器
// }
//
// static class SynchronizedList<E> implements List<E> {
// final List<E> list; // 持有原始List引用
// final Object mutex; // 同步锁对象
//
// SynchronizedList(List<E> list) {
// super(list);
// this.list = list;
// this.mutex = this;
// }
//
// public E get(int index) {
// synchronized (mutex) { return list.get(index); } // 加锁委托
// }
//
// public boolean add(E e) {
// synchronized (mutex) { return list.add(e); } // 加锁委托
// }
// }
// 不可变包装适配器:将可变List适配为只读List
List<String> unmodifiableList = Collections.unmodifiableList(originalList);
// 客户端可以透明使用
System.out.println("同步List大小: " + syncList.size());
System.out.println("不可变List内容: " + unmodifiableList);
try {
unmodifiableList.add("Item3"); // 抛出UnsupportedOperationException
} catch (UnsupportedOperationException e) {
System.out.println("不可变List拒绝修改操作");
}
// 包装适配器模式特点:
// 1. 与被适配者实现相同接口
// 2. 通过组合持有被适配者引用
// 3. 在委托前后增加额外功能(同步、只读控制等)
}
}
3.1.3 InputStreamReader —— 字节流到字符流的适配
import java.io.*;
/**
* java.io.InputStreamReader与OutputStreamWriter
* 将字节流适配为字符流,是适配器模式的典范
*/
public class StreamAdapterDemo {
public static void main(String[] args) {
String testData = "Hello, 适配器模式!";
// 字节数组输出流(被适配者)
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
// OutputStreamWriter:字符流到字节流的适配器
// 将字符输出流适配为字节输出流接口
try (Writer writer = new OutputStreamWriter(byteOut)) {
writer.write(testData); // 客户端使用字符流接口
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
// 源码剖析(简化版):
// public class OutputStreamWriter extends Writer {
// private final StreamEncoder se; // 实际的编码适配器
//
// public OutputStreamWriter(OutputStream out) {
// se = StreamEncoder.forOutputStreamWriter(out, this, ...);
// }
//
// public void write(char[] cbuf, int off, int len) {
// se.write(cbuf, off, len); // 委托给StreamEncoder
// }
// }
// 反向适配:字节流 -> 字符流
byte[] bytes = byteOut.toByteArray();
ByteArrayInputStream byteIn = new ByteArrayInputStream(bytes);
// InputStreamReader:字节流到字符流的适配器
try (Reader reader = new InputStreamReader(byteIn)) {
char[] buffer = new char[1024];
int len = reader.read(buffer);
String result = new String(buffer, 0, len);
System.out.println("读取结果: " + result);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("原始数据: " + testData);
System.out.println("字节长度: " + bytes.length);
}
}
3.1.4 MouseAdapter —— 缺省适配器模式
import java.awt.*;
import java.awt.event.*;
/**
* java.awt.event.MouseAdapter
* 这是适配器模式的一个变体——缺省适配器(Default Adapter)
* 为接口的所有方法提供空实现,让子类只重写需要的方法
*/
public class MouseAdapterDemo extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("鼠标点击事件:只需重写这一个方法");
System.out.println("坐标: (" + e.getX() + ", " + e.getY() + ")");
}
// 无需实现mousePressed、mouseReleased等其他5个方法
// MouseAdapter已提供空实现
public static void main(String[] args) {
// 源码剖析:
// public interface MouseListener extends EventListener {
// void mouseClicked(MouseEvent e);
// void mousePressed(MouseEvent e);
// void mouseReleased(MouseEvent e);
// void mouseEntered(MouseEvent e);
// void mouseExited(MouseEvent e);
// }
//
// public abstract class MouseAdapter implements MouseListener {
// public void mouseClicked(MouseEvent e) {} // 空实现
// public void mousePressed(MouseEvent e) {} // 空实现
// public void mouseReleased(MouseEvent e) {} // 空实现
// public void mouseEntered(MouseEvent e) {} // 空实现
// public void mouseExited(MouseEvent e) {} // 空实现
// }
System.out.println("缺省适配器模式的价值:");
System.out.println("1. 避免实现接口时必须实现所有方法的冗余");
System.out.println("2. 子类只需重写关心的少数方法");
System.out.println("3. 简化客户端代码,提高可读性");
// 模拟事件(实际应用中由AWT事件循环触发)
MouseAdapterDemo demo = new MouseAdapterDemo();
demo.mouseClicked(new MouseEvent(
new Button(),
MouseEvent.MOUSE_CLICKED,
System.currentTimeMillis(),
0, 100, 200, 1, false
));
}
}
3.2 Spring框架深度剖析
3.2.1 AdvisorAdapter —— 通知适配器
/**
* Spring AOP中的AdvisorAdapter体系
* 将不同类型的Advice适配为统一的MethodInterceptor
*/
// 模拟Spring AOP的核心类结构
// 目标接口:Spring AOP的方法拦截器(统一接口)
interface MethodInterceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
// 不同的Advice类型(被适配者)
class BeforeAdvice {
public void before(Method method, Object[] args, Object target) {
System.out.println("[前置通知] 方法执行前: " + method.getName());
}
}
class AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) {
System.out.println("[后置通知] 方法返回后: " + method.getName() + ", 返回值: " + returnValue);
}
}
class ThrowsAdvice {
public void afterThrowing(Throwable ex, Method method, Object[] args, Object target) {
System.out.println("[异常通知] 方法抛异常: " + method.getName() + ", 异常: " + ex.getMessage());
}
}
// 适配器接口
interface AdvisorAdapter {
boolean supportsAdvice(Object advice);
MethodInterceptor getInterceptor(Object advice);
}
// 前置通知适配器
class BeforeAdviceAdapter implements AdvisorAdapter {
@Override
public boolean supportsAdvice(Object advice) {
return advice instanceof BeforeAdvice;
}
@Override
public MethodInterceptor getInterceptor(Object advice) {
BeforeAdvice beforeAdvice = (BeforeAdvice) advice;
return invocation -> {
beforeAdvice.before(invocation.getMethod(), invocation.getArguments(), invocation.getThis());
return invocation.proceed(); // 执行目标方法
};
}
}
// 后置通知适配器
class AfterReturningAdviceAdapter implements AdvisorAdapter {
@Override
public boolean supportsAdvice(Object advice) {
return advice instanceof AfterReturningAdvice;
}
@Override
public MethodInterceptor getInterceptor(Object advice) {
AfterReturningAdvice afterAdvice = (AfterReturningAdvice) advice;
return invocation -> {
Object retVal = invocation.proceed();
afterAdvice.afterReturning(retVal, invocation.getMethod(),
invocation.getArguments(), invocation.getThis());
return retVal;
};
}
}
// 适配器注册表(Spring中为DefaultAdvisorAdapterRegistry)
class AdvisorAdapterRegistry {
private final List<AdvisorAdapter> adapters = new ArrayList<>();
public AdvisorAdapterRegistry() {
// 注册所有适配器
adapters.add(new BeforeAdviceAdapter());
adapters.add(new AfterReturningAdviceAdapter());
}
public MethodInterceptor getInterceptors(Object advice) {
for (AdvisorAdapter adapter : adapters) {
if (adapter.supportsAdvice(advice)) {
return adapter.getInterceptor(advice);
}
}
throw new IllegalArgumentException("Unknown advice type: " + advice.getClass());
}
}
// 模拟方法调用
class MethodInvocation {
private Object target;
private Method method;
private Object[] args;
public MethodInvocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
public Object proceed() throws Throwable {
System.out.println(" [执行目标方法] " + method.getName());
return method.invoke(target, args);
}
public Method getMethod() { return method; }
public Object[] getArguments() { return args; }
public Object getThis() { return target; }
}
// 测试类
class UserService {
public String getUser(String id) {
return "User_" + id;
}
}
public class SpringAdvisorAdapterDemo {
public static void main(String[] args) throws Throwable {
// 创建不同类型的Advice
BeforeAdvice beforeAdvice = new BeforeAdvice();
AfterReturningAdvice afterAdvice = new AfterReturningAdvice();
// 适配器注册表
AdvisorAdapterRegistry registry = new AdvisorAdapterRegistry();
// 将不同类型的Advice适配为统一的MethodInterceptor
MethodInterceptor beforeInterceptor = registry.getInterceptors(beforeAdvice);
MethodInterceptor afterInterceptor = registry.getInterceptors(afterAdvice);
// 目标对象
UserService userService = new UserService();
Method method = UserService.class.getMethod("getUser", String.class);
MethodInvocation invocation = new MethodInvocation(userService, method, new Object[]{"123"});
// 统一调用
System.out.println("=== 执行前置通知适配器 ===");
beforeInterceptor.invoke(invocation);
System.out.println("\n=== 执行后置通知适配器 ===");
afterInterceptor.invoke(invocation);
System.out.println("\n设计价值分析:");
System.out.println("1. 统一接口:将多种Advice类型适配为MethodInterceptor");
System.out.println("2. 可扩展:新增Advice类型只需添加对应的Adapter");
System.out.println("3. 透明性:AOP框架只需处理MethodInterceptor接口");
}
}
3.2.2 HandlerAdapter —— Spring MVC处理器适配器
/**
* Spring MVC的HandlerAdapter体系
* 适配不同类型的Controller实现
*/
// 模拟Spring MVC核心类
// 统一的处理器适配器接口
interface HandlerAdapter {
boolean supports(Object handler);
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler);
}
// 不同类型的Handler(被适配者)
class ControllerImpl implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse res) {
System.out.println("[传统Controller] 处理请求: " + req.getRequestURI());
return new ModelAndView("success");
}
}
class HttpRequestHandlerImpl implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest req, HttpServletResponse res) {
System.out.println("[HttpRequestHandler] 处理请求: " + req.getRequestURI());
}
}
class AnnotationHandler {
@RequestMapping("/user")
public String getUser(String id) {
System.out.println("[注解Controller] 查询用户: " + id);
return "userView";
}
}
// 视图模型(简化)
class ModelAndView {
private String viewName;
public ModelAndView(String viewName) { this.viewName = viewName; }
}
// 请求响应模拟
class HttpServletRequest {
public String getRequestURI() { return "/user/123"; }
}
class HttpServletResponse {}
// 接口定义
interface Controller {
ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse res);
}
interface HttpRequestHandler {
void handleRequest(HttpServletRequest req, HttpServletResponse res);
}
@interface RequestMapping {
String value();
}
// 适配器实现
class SimpleControllerHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof Controller;
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
return ((Controller) handler).handleRequest(request, response);
}
}
class HttpRequestHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof HttpRequestHandler;
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
((HttpRequestHandler) handler).handleRequest(request, response);
return null; // 无视图返回
}
}
class RequestMappingHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler.getClass().isAnnotationPresent(RequestMapping.class)
|| handler instanceof AnnotationHandler;
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 实际Spring会解析@RequestMapping注解,这里简化处理
System.out.println("[RequestMappingHandlerAdapter] 适配注解Controller");
String viewName = ((AnnotationHandler) handler).getUser("123");
return new ModelAndView(viewName);
}
}
// DispatcherServlet(简化版)
class DispatcherServlet {
private List<HandlerAdapter> handlerAdapters = new ArrayList<>();
public DispatcherServlet() {
// 初始化所有适配器
handlerAdapters.add(new SimpleControllerHandlerAdapter());
handlerAdapters.add(new HttpRequestHandlerAdapter());
handlerAdapters.add(new RequestMappingHandlerAdapter());
}
public void doDispatch(Object handler) {
HttpServletRequest request = new HttpServletRequest();
HttpServletResponse response = new HttpServletResponse();
// 找到支持该handler的适配器
for (HandlerAdapter adapter : handlerAdapters) {
if (adapter.supports(handler)) {
ModelAndView mv = adapter.handle(request, response, handler);
System.out.println("视图名称: " + (mv != null ? mv.viewName : "无视图"));
return;
}
}
throw new IllegalArgumentException("No adapter for handler: " + handler);
}
}
public class SpringHandlerAdapterDemo {
public static void main(String[] args) {
DispatcherServlet dispatcher = new DispatcherServlet();
System.out.println("=== 传统Controller ===");
dispatcher.doDispatch(new ControllerImpl());
System.out.println("\n=== HttpRequestHandler ===");
dispatcher.doDispatch(new HttpRequestHandlerImpl());
System.out.println("\n=== 注解Controller ===");
dispatcher.doDispatch(new AnnotationHandler());
System.out.println("\nHandlerAdapter设计精髓:");
System.out.println("1. 将多种Controller类型适配为统一处理流程");
System.out.println("2. DispatcherServlet无需感知具体Handler类型");
System.out.println("3. 符合开闭原则,新增Handler类型只需增加Adapter");
}
}
3.3 MyBatis日志适配器
/**
* MyBatis的日志适配器模式
* 将内部日志接口适配到Log4j、Slf4j等多种日志框架
*/
// MyBatis内部日志接口(Target)
interface Log {
void debug(String message);
void error(String message, Throwable t);
}
// 各种第三方日志框架(Adaptee)
class Log4jImpl {
private org.apache.log4j.Logger logger;
public Log4jImpl(String name) {
logger = org.apache.log4j.Logger.getLogger(name);
}
public void debug(Object message) {
logger.debug(message);
}
public void error(Object message, Throwable t) {
logger.error(message, t);
}
}
class Slf4jImpl {
private org.slf4j.Logger logger;
public Slf4jImpl(String name) {
logger = org.slf4j.LoggerFactory.getLogger(name);
}
public void debug(String message) {
logger.debug(message);
}
public void error(String message, Throwable t) {
logger.error(message, t);
}
}
// 适配器实现
class Log4jAdapter implements Log {
private Log4jImpl log4j;
public Log4jAdapter(String name) {
this.log4j = new Log4jImpl(name);
}
@Override
public void debug(String message) {
log4j.debug(message); // 委托调用
}
@Override
public void error(String message, Throwable t) {
log4j.error(message, t);
}
}
class Slf4jAdapter implements Log {
private Slf4jImpl slf4j;
public Slf4jAdapter(String name) {
this.slf4j = new Slf4jImpl(name);
}
@Override
public void debug(String message) {
slf4j.debug(message);
}
@Override
public void error(String message, Throwable t) {
slf4j.error(message, t);
}
}
// 日志工厂(根据类路径自动选择合适的适配器)
class LogFactory {
private static Class<? extends Log> logClass;
static {
try {
// 尝试加载Slf4j
Class.forName("org.slf4j.Logger");
logClass = Slf4jAdapter.class;
System.out.println("使用Slf4j日志适配器");
} catch (ClassNotFoundException e) {
try {
// 尝试加载Log4j
Class.forName("org.apache.log4j.Logger");
logClass = Log4jAdapter.class;
System.out.println("使用Log4j日志适配器");
} catch (ClassNotFoundException ex) {
System.out.println("无可用日志框架");
}
}
}
public static Log getLog(String name) {
try {
return logClass.getConstructor(String.class).newInstance(name);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public class MyBatisLogAdapterDemo {
public static void main(String[] args) {
// MyBatis内部统一使用Log接口
Log log = LogFactory.getLog(MyBatisLogAdapterDemo.class.getName());
log.debug("这是一条调试日志");
log.error("发生错误", new RuntimeException("测试异常"));
System.out.println("\nMyBatis日志适配的价值:");
System.out.println("1. 框架内部只依赖Log接口,与具体日志实现解耦");
System.out.println("2. 运行时根据类路径自动选择适配器");
System.out.println("3. 用户可以在多种日志框架间自由切换");
}
}
3.4 SLF4J日志门面
/**
* SLF4J的门面+适配器模式
* 这是适配器模式在日志领域的巅峰应用
*/
// SLF4J门面接口(Target)
interface Slf4jLogger {
void info(String msg);
void warn(String msg);
void error(String msg, Throwable t);
}
// 静态Logger工厂
class Slf4jLoggerFactory {
public static Slf4jLogger getLogger(String name) {
// 静态绑定机制:在编译时确定绑定哪个实现
// 实际SLF4J通过StaticLoggerBinder类实现
return new StaticLoggerBinder().getLogger(name);
}
}
// 静态绑定器(模拟slf4j的绑定机制)
class StaticLoggerBinder {
private static final String BINDING_CLASS;
static {
// 模拟查找类路径中的绑定实现
String binding = "logback"; // 可以是logback、log4j等
if (isClassPresent("ch.qos.logback.classic.Logger")) {
binding = "logback";
} else if (isClassPresent("org.slf4j.impl.Log4jLoggerAdapter")) {
binding = "log4j";
}
BINDING_CLASS = binding;
System.out.println("SLF4J绑定到: " + BINDING_CLASS);
}
private static boolean isClassPresent(String className) {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
public Slf4jLogger getLogger(String name) {
switch (BINDING_CLASS) {
case "logback":
return new LogbackAdapter(name);
case "log4j":
return new Log4j2Adapter(name);
default:
return new NOPLogger();
}
}
}
// Logback适配器
class LogbackAdapter implements Slf4jLogger {
private ch.qos.logback.classic.Logger logger;
public LogbackAdapter(String name) {
logger = (ch.qos.logback.classic.Logger)
org.slf4j.LoggerFactory.getLogger(name);
}
@Override
public void info(String msg) {
logger.info(msg);
}
@Override
public void warn(String msg) {
logger.warn(msg);
}
@Override
public void error(String msg, Throwable t) {
logger.error(msg, t);
}
}
// Log4j2适配器
class Log4j2Adapter implements Slf4jLogger {
private org.apache.logging.log4j.Logger logger;
public Log4j2Adapter(String name) {
logger = org.apache.logging.log4j.LogManager.getLogger(name);
}
@Override
public void info(String msg) {
logger.info(msg);
}
@Override
public void warn(String msg) {
logger.warn(msg);
}
@Override
public void error(String msg, Throwable t) {
logger.error(msg, t);
}
}
// 空操作日志(无绑定时的默认实现)
class NOPLogger implements Slf4jLogger {
@Override
public void info(String msg) {}
@Override
public void warn(String msg) {}
@Override
public void error(String msg, Throwable t) {}
}
public class Slf4jAdapterDemo {
public static void main(String[] args) {
// 业务代码只依赖Slf4j接口
Slf4jLogger logger = Slf4jLoggerFactory.getLogger(Slf4jAdapterDemo.class.getName());
logger.info("SLF4J门面模式示例");
logger.warn("这是一条警告");
logger.error("错误日志", new Exception("测试"));
System.out.println("\nSLF4J的设计精髓:");
System.out.println("1. 门面模式:提供统一API,隔离具体实现");
System.out.println("2. 适配器模式:通过适配器桥接到各日志框架");
System.out.println("3. 静态绑定:编译时确定绑定,运行时零开销");
System.out.println("4. 无感知切换:更换日志框架只需替换jar包");
}
}
四、分布式环境下的适配器模式
4.1 多云存储SDK统一适配
/**
* 多云存储适配器
* 统一阿里云OSS、AWS S3、腾讯云COS接口
*/
// 统一云存储接口(Target)
interface CloudStorage {
String uploadFile(String bucket, String key, byte[] content);
byte[] downloadFile(String bucket, String key);
boolean deleteFile(String bucket, String key);
boolean doesObjectExist(String bucket, String key);
}
// 文件元数据
class StorageFile {
private String bucket;
private String key;
private byte[] content;
private String contentType;
public StorageFile(String bucket, String key, byte[] content) {
this.bucket = bucket;
this.key = key;
this.content = content;
}
// getters...
public String getBucket() { return bucket; }
public String getKey() { return key; }
public byte[] getContent() { return content; }
}
// 阿里云OSS SDK(被适配者)
class AliyunOSSSDK {
public String putObject(String bucketName, String objectName, byte[] data) {
System.out.println("[阿里云OSS] 上传文件: " + bucketName + "/" + objectName);
return "https://" + bucketName + ".oss-cn-hangzhou.aliyuncs.com/" + objectName;
}
public byte[] getObject(String bucketName, String objectName) {
System.out.println("[阿里云OSS] 下载文件: " + bucketName + "/" + objectName);
return ("Aliyun_" + objectName).getBytes();
}
public void deleteObject(String bucketName, String objectName) {
System.out.println("[阿里云OSS] 删除文件: " + bucketName + "/" + objectName);
}
public boolean doesObjectExist(String bucketName, String objectName) {
System.out.println("[阿里云OSS] 检查文件是否存在: " + bucketName + "/" + objectName);
return true;
}
}
// AWS S3 SDK(被适配者)
class AWSS3SDK {
public String upload(String bucket, String key, byte[] data) {
System.out.println("[AWS S3] 上传: " + bucket + "/" + key);
return "https://" + bucket + ".s3.amazonaws.com/" + key;
}
public byte[] download(String bucket, String key) {
System.out.println("[AWS S3] 下载: " + bucket + "/" + key);
return ("AWS_" + key).getBytes();
}
public void remove(String bucket, String key) {
System.out.println("[AWS S3] 删除: " + bucket + "/" + key);
}
public boolean exists(String bucket, String key) {
System.out.println("[AWS S3] 检查存在: " + bucket + "/" + key);
return true;
}
}
// 腾讯云COS SDK(被适配者)
class TencentCOSSDK {
public String uploadFile(String bucket, String cosPath, byte[] fileContent) {
System.out.println("[腾讯云COS] 上传: " + bucket + "/" + cosPath);
return "https://" + bucket + ".cos.ap-guangzhou.myqcloud.com/" + cosPath;
}
public byte[] downloadFile(String bucket, String cosPath) {
System.out.println("[腾讯云COS] 下载: " + bucket + "/" + cosPath);
return ("Tencent_" + cosPath).getBytes();
}
public void deleteFile(String bucket, String cosPath) {
System.out.println("[腾讯云COS] 删除: " + bucket + "/" + cosPath);
}
public boolean hasObject(String bucket, String cosPath) {
System.out.println("[腾讯云COS] 检查存在: " + bucket + "/" + cosPath);
return true;
}
}
// 阿里云适配器
class AliyunStorageAdapter implements CloudStorage {
private AliyunOSSSDK aliyunSDK;
public AliyunStorageAdapter(AliyunOSSSDK sdk) {
this.aliyunSDK = sdk;
}
@Override
public String uploadFile(String bucket, String key, byte[] content) {
return aliyunSDK.putObject(bucket, key, content);
}
@Override
public byte[] downloadFile(String bucket, String key) {
return aliyunSDK.getObject(bucket, key);
}
@Override
public boolean deleteFile(String bucket, String key) {
aliyunSDK.deleteObject(bucket, key);
return true;
}
@Override
public boolean doesObjectExist(String bucket, String key) {
return aliyunSDK.doesObjectExist(bucket, key);
}
}
// AWS适配器
class AWSStorageAdapter implements CloudStorage {
private AWSS3SDK awsSDK;
public AWSStorageAdapter(AWSS3SDK sdk) {
this.awsSDK = sdk;
}
@Override
public String uploadFile(String bucket, String key, byte[] content) {
return awsSDK.upload(bucket, key, content);
}
@Override
public byte[] downloadFile(String bucket, String key) {
return awsSDK.download(bucket, key);
}
@Override
public boolean deleteFile(String bucket, String key) {
awsSDK.remove(bucket, key);
return true;
}
@Override
public boolean doesObjectExist(String bucket, String key) {
return awsSDK.exists(bucket, key);
}
}
// 腾讯云适配器
class TencentStorageAdapter implements CloudStorage {
private TencentCOSSDK tencentSDK;
public TencentStorageAdapter(TencentCOSSDK sdk) {
this.tencentSDK = sdk;
}
@Override
public String uploadFile(String bucket, String key, byte[] content) {
return tencentSDK.uploadFile(bucket, key, content);
}
@Override
public byte[] downloadFile(String bucket, String key) {
return tencentSDK.downloadFile(bucket, key);
}
@Override
public boolean deleteFile(String bucket, String key) {
tencentSDK.deleteFile(bucket, key);
return true;
}
@Override
public boolean doesObjectExist(String bucket, String key) {
return tencentSDK.hasObject(bucket, key);
}
}
// 存储策略枚举
enum CloudVendor {
ALIYUN, AWS, TENCENT
}
// 存储适配器工厂
class CloudStorageFactory {
public static CloudStorage createStorage(CloudVendor vendor) {
switch (vendor) {
case ALIYUN:
return new AliyunStorageAdapter(new AliyunOSSSDK());
case AWS:
return new AWSStorageAdapter(new AWSS3SDK());
case TENCENT:
return new TencentStorageAdapter(new TencentCOSSDK());
default:
throw new IllegalArgumentException("Unknown vendor: " + vendor);
}
}
}
// 业务服务
class FileService {
private CloudStorage storage;
public FileService(CloudStorage storage) {
this.storage = storage;
}
public void saveFile(String bucket, String key, String content) {
String url = storage.uploadFile(bucket, key, content.getBytes());
System.out.println("文件保存成功,URL: " + url);
}
public String readFile(String bucket, String key) {
byte[] data = storage.downloadFile(bucket, key);
return new String(data);
}
}
public class MultiCloudStorageDemo {
public static void main(String[] args) {
String bucket = "my-bucket";
String key = "test.txt";
String content = "Hello, Multi-Cloud!";
System.out.println("=== 使用阿里云OSS ===");
CloudStorage aliyunStorage = CloudStorageFactory.createStorage(CloudVendor.ALIYUN);
FileService aliyunService = new FileService(aliyunStorage);
aliyunService.saveFile(bucket, key, content);
System.out.println("读取文件: " + aliyunService.readFile(bucket, key));
System.out.println("\n=== 切换到AWS S3 ===");
CloudStorage awsStorage = CloudStorageFactory.createStorage(CloudVendor.AWS);
FileService awsService = new FileService(awsStorage);
awsService.saveFile(bucket, key, content);
System.out.println("\n=== 切换到腾讯云COS ===");
CloudStorage tencentStorage = CloudStorageFactory.createStorage(CloudVendor.TENCENT);
FileService tencentService = new FileService(tencentStorage);
tencentService.saveFile(bucket, key, content);
System.out.println("\n多云适配的价值:");
System.out.println("1. 统一接口:业务代码无需关心底层云厂商差异");
System.out.println("2. 灵活切换:更换云厂商只需修改配置");
System.out.println("3. 多云部署:可同时支持多个云厂商");
System.out.println("4. 成本优化:可根据价格动态选择云服务商");
}
}
4.2 分布式多云适配架构图
flowchart TB
subgraph BusinessLayer["业务层"]
FS[文件服务]
MS[媒体服务]
BS[备份服务]
end
subgraph InterfaceLayer["统一接口层"]
CSI[CloudStorage接口]
end
subgraph AdapterLayer["适配器层"]
AA[阿里云适配器]
WA[AWS适配器]
TA[腾讯云适配器]
OA[其他云适配器]
end
subgraph SDKlayer["云厂商SDK层"]
OSS[阿里云OSS SDK]
S3[AWS S3 SDK]
COS[腾讯云COS SDK]
OTHER[其他云SDK]
end
subgraph CloudLayer["云服务层"]
Aliyun[阿里云OSS]
AWSCloud[AWS S3]
Tencent[腾讯云COS]
OtherCloud[其他云服务]
end
FS --> CSI
MS --> CSI
BS --> CSI
CSI --> AA
CSI --> WA
CSI --> TA
CSI --> OA
AA --> OSS
WA --> S3
TA --> COS
OA --> OTHER
OSS --> Aliyun
S3 --> AWSCloud
COS --> Tencent
OTHER --> OtherCloud
style CSI fill:#ff9999
style AA fill:#99ccff
style WA fill:#99ccff
style TA fill:#99ccff
架构图详解:
上图展示了基于适配器模式构建的多云存储统一访问架构,整个系统分为五个清晰的层次:
业务层:位于架构最顶层,包含文件服务、媒体服务、备份服务等多个业务模块。这些服务完全依赖CloudStorage接口进行存储操作,对底层云厂商的差异零感知。当需要切换云服务商时,业务代码无需任何修改,真正实现了业务与基础设施的解耦。
统一接口层:CloudStorage接口定义了标准的存储操作契约,包括uploadFile、downloadFile、deleteFile等方法。这一层是适配器模式的目标接口,为上层业务提供了一致性的编程模型。
适配器层:这是整个架构的核心枢纽。每个云厂商对应一个独立的适配器实现,负责将CloudStorage的标准接口调用翻译为特定厂商SDK的API调用。适配器内部封装了参数转换、结果映射、异常处理等适配逻辑。新增云厂商支持只需增加对应的适配器实现,完全符合开闭原则。
云厂商SDK层:各云服务商官方提供的SDK,其API设计各具特色,存在方法命名、参数顺序、返回值类型等方面的差异。适配器层正是为了弥合这些差异而存在。
云服务层:最终的存储服务提供者,位于架构底层。
这种分层架构带来的收益是多维度的:技术上实现了接口兼容性,运维上支持了多云切换能力,商业上避免了厂商锁定,是适配器模式在分布式系统中的经典应用范式。
五、对比辨析
5.1 适配器模式 vs 装饰器模式
| 对比维度 | 适配器模式 | 装饰器模式 |
|---|---|---|
| 核心意图 | 接口转换,使不兼容的接口能够协作 | 功能增强,在不修改原类基础上添加新职责 |
| 关注点 | 解决"接口不匹配"问题 | 解决"功能扩展"问题 |
| 接口变化 | 改变接口(Target ≠ Adaptee接口) | 保持接口不变(Component = Decorator接口) |
| 调用关系 | 单向适配,适配器调用被适配者 | 嵌套装饰,装饰器包裹被装饰对象 |
| 组合方式 | 通常一对一适配 | 可多层装饰,链式组合 |
// 对比示例
// 适配器:接口转换
interface MediaPlayer { void play(String file); }
class AudioDecoder { void decodeMp3(String file); }
class MediaAdapter implements MediaPlayer { // 改变接口
private AudioDecoder decoder;
public void play(String file) { decoder.decodeMp3(file); }
}
// 装饰器:功能增强(接口相同)
interface Reader { String read(); }
class FileReader implements Reader { public String read() { return "data"; } }
class BufferedReadDecorator implements Reader { // 接口不变
private Reader reader;
public String read() {
String data = reader.read();
return "[BUFFERED]" + data; // 增强功能
}
}
5.2 适配器模式 vs 门面模式
| 对比维度 | 适配器模式 | 门面模式 |
|---|---|---|
| 设计动机 | 解决接口不兼容,使现有类适配新接口 | 简化复杂子系统,提供统一高层接口 |
| 接口定义 | 接口已存在,适配器去适配它 | 接口是新定义的,用于简化调用 |
| 调用方向 | 单向适配(旧→新)或双向适配 | 单向调用(客户端→门面→子系统) |
| 复杂度管理 | 解决接口差异的复杂度 | 隐藏子系统内部的复杂度 |
5.3 适配器模式 vs 代理模式
| 对比维度 | 适配器模式 | 代理模式 |
|---|---|---|
| 接口一致性 | 适配器与被适配者接口不同 | 代理与真实对象接口完全相同 |
| 设计目的 | 接口转换,兼容性 | 访问控制,延迟加载,远程调用 |
| 客户端感知 | 客户端通过新接口调用 | 客户端无感知,以为在使用真实对象 |
5.4 适配器模式 vs 桥接模式
| 对比维度 | 适配器模式 | 桥接模式 |
|---|---|---|
| 设计时机 | 事后补救,已有类接口不匹配 | 事前设计,分离抽象与实现 |
| 变化维度 | 一维变化(接口转换) | 二维变化(抽象+实现独立扩展) |
| 结构相似性 | 都涉及接口与实现的组合 | 但意图和时机完全不同 |
5.5 类适配器 vs 对象适配器
| 对比维度 | 类适配器 | 对象适配器 |
|---|---|---|
| 实现机制 | 继承Adaptee,实现Target | 组合Adaptee,实现Target |
| 代码复杂度 | 简洁 | 稍复杂(需管理引用) |
| 灵活性 | 静态绑定,不能适配子类 | 动态绑定,可适配任何子类 |
| 适配范围 | 只能适配一个Adaptee | 可适配多个Adaptee |
| 方法重写 | 可直接重写Adaptee行为 | 不能直接重写,需包装 |
| 封装性 | 暴露Adaptee的所有public方法 | 仅暴露Target接口方法 |
| 推荐度 | 特定场景(需重写行为) | 优先推荐(组合优于继承) |
六、适用场景分析(重点强化)
场景一:第三方支付接口整合
/**
* 支付接口统一适配
* 整合支付宝、微信支付、银联支付
*/
// 统一支付接口(Target)
interface PaymentService {
PaymentResult pay(PaymentRequest request);
PaymentResult refund(RefundRequest request);
PaymentStatus queryStatus(String orderId);
}
// 支付请求
class PaymentRequest {
private String orderId;
private double amount;
private String currency;
private String description;
public PaymentRequest(String orderId, double amount) {
this.orderId = orderId;
this.amount = amount;
this.currency = "CNY";
}
public String getOrderId() { return orderId; }
public double getAmount() { return amount; }
public String getCurrency() { return currency; }
public String getDescription() { return description; }
}
// 支付结果
class PaymentResult {
private boolean success;
private String transactionId;
private String message;
public PaymentResult(boolean success, String transactionId, String message) {
this.success = success;
this.transactionId = transactionId;
this.message = message;
}
@Override
public String toString() {
return String.format("PaymentResult[success=%s, txId=%s, msg=%s]",
success, transactionId, message);
}
}
class RefundRequest {
private String orderId;
private double amount;
public RefundRequest(String orderId, double amount) {
this.orderId = orderId;
this.amount = amount;
}
}
enum PaymentStatus { SUCCESS, FAILED, PROCESSING }
// 支付宝SDK(被适配者)
class AlipaySDK {
public String createPayment(String outTradeNo, double totalAmount, String subject) {
System.out.println("[支付宝] 创建支付订单: " + outTradeNo + ", 金额: " + totalAmount);
return "ALIPAY_" + System.currentTimeMillis();
}
public String refund(String outTradeNo, double refundAmount) {
System.out.println("[支付宝] 退款: " + outTradeNo + ", 金额: " + refundAmount);
return "ALIPAY_REFUND_" + System.currentTimeMillis();
}
public String queryOrder(String outTradeNo) {
System.out.println("[支付宝] 查询订单: " + outTradeNo);
return "SUCCESS";
}
}
// 微信支付SDK
class WechatPaySDK {
public WechatPayResponse unifiedOrder(String orderId, int totalFee, String body) {
System.out.println("[微信支付] 统一下单: " + orderId + ", 金额(分): " + totalFee);
WechatPayResponse resp = new WechatPayResponse();
resp.setReturnCode("SUCCESS");
resp.setTransactionId("WX_" + System.currentTimeMillis());
return resp;
}
public WechatPayResponse refund(String orderId, int refundFee) {
System.out.println("[微信支付] 申请退款: " + orderId + ", 金额(分): " + refundFee);
return new WechatPayResponse();
}
public WechatPayResponse orderQuery(String orderId) {
System.out.println("[微信支付] 订单查询: " + orderId);
WechatPayResponse resp = new WechatPayResponse();
resp.setTradeState("SUCCESS");
return resp;
}
}
class WechatPayResponse {
private String returnCode;
private String transactionId;
private String tradeState;
public void setReturnCode(String code) { this.returnCode = code; }
public void setTransactionId(String id) { this.transactionId = id; }
public void setTradeState(String state) { this.tradeState = state; }
public String getReturnCode() { return returnCode; }
public String getTransactionId() { return transactionId; }
public String getTradeState() { return tradeState; }
}
// 支付宝适配器
class AlipayPaymentAdapter implements PaymentService {
private AlipaySDK alipaySDK;
public AlipayPaymentAdapter(AlipaySDK sdk) {
this.alipaySDK = sdk;
}
@Override
public PaymentResult pay(PaymentRequest request) {
String txId = alipaySDK.createPayment(
request.getOrderId(),
request.getAmount(),
request.getDescription()
);
return new PaymentResult(true, txId, "支付成功");
}
@Override
public PaymentResult refund(RefundRequest request) {
String refundId = alipaySDK.refund(request.getOrderId(), request.getAmount());
return new PaymentResult(true, refundId, "退款成功");
}
@Override
public PaymentStatus queryStatus(String orderId) {
String status = alipaySDK.queryOrder(orderId);
return "SUCCESS".equals(status) ? PaymentStatus.SUCCESS : PaymentStatus.PROCESSING;
}
}
// 微信支付适配器
class WechatPaymentAdapter implements PaymentService {
private WechatPaySDK wechatSDK;
public WechatPaymentAdapter(WechatPaySDK sdk) {
this.wechatSDK = sdk;
}
@Override
public PaymentResult pay(PaymentRequest request) {
// 金额转换:元 -> 分
int amountInFen = (int) (request.getAmount() * 100);
WechatPayResponse resp = wechatSDK.unifiedOrder(
request.getOrderId(),
amountInFen,
request.getDescription()
);
boolean success = "SUCCESS".equals(resp.getReturnCode());
return new PaymentResult(success, resp.getTransactionId(),
success ? "支付成功" : "支付失败");
}
@Override
public PaymentResult refund(RefundRequest request) {
int amountInFen = (int) (request.getAmount() * 100);
WechatPayResponse resp = wechatSDK.refund(request.getOrderId(), amountInFen);
return new PaymentResult(true, resp.getTransactionId(), "退款成功");
}
@Override
public PaymentStatus queryStatus(String orderId) {
WechatPayResponse resp = wechatSDK.orderQuery(orderId);
return "SUCCESS".equals(resp.getTradeState()) ?
PaymentStatus.SUCCESS : PaymentStatus.PROCESSING;
}
}
// 支付渠道枚举
enum PaymentChannel {
ALIPAY, WECHAT, UNIONPAY
}
// 支付工厂
class PaymentServiceFactory {
public static PaymentService createService(PaymentChannel channel) {
switch (channel) {
case ALIPAY:
return new AlipayPaymentAdapter(new AlipaySDK());
case WECHAT:
return new WechatPaymentAdapter(new WechatPaySDK());
default:
throw new IllegalArgumentException("Unsupported channel: " + channel);
}
}
}
public class PaymentAdapterDemo {
public static void main(String[] args) {
PaymentRequest request = new PaymentRequest("ORDER_2024_001", 99.99);
System.out.println("=== 使用支付宝支付 ===");
PaymentService alipayService = PaymentServiceFactory.createService(PaymentChannel.ALIPAY);
PaymentResult alipayResult = alipayService.pay(request);
System.out.println("支付结果: " + alipayResult);
System.out.println("订单状态: " + alipayService.queryStatus(request.getOrderId()));
System.out.println("\n=== 使用微信支付 ===");
PaymentService wechatService = PaymentServiceFactory.createService(PaymentChannel.WECHAT);
PaymentResult wechatResult = wechatService.pay(request);
System.out.println("支付结果: " + wechatResult);
System.out.println("\n支付整合的价值:");
System.out.println("1. 统一接口:业务层无需关心支付渠道差异");
System.out.println("2. 扩展方便:新增支付渠道只需添加适配器");
System.out.println("3. 灰度发布:可根据配置动态切换支付渠道");
System.out.println("4. 降级容错:某渠道故障时可快速切换到备用渠道");
}
}
场景一类图
classDiagram
class PaymentService {
<<interface>>
+pay(PaymentRequest) PaymentResult
+refund(RefundRequest) PaymentResult
+queryStatus(String) PaymentStatus
}
class AlipayPaymentAdapter {
-alipaySDK: AlipaySDK
+pay(PaymentRequest) PaymentResult
+refund(RefundRequest) PaymentResult
+queryStatus(String) PaymentStatus
}
class WechatPaymentAdapter {
-wechatSDK: WechatPaySDK
+pay(PaymentRequest) PaymentResult
+refund(RefundRequest) PaymentResult
+queryStatus(String) PaymentStatus
}
class AlipaySDK {
+createPayment(String, double, String) String
+refund(String, double) String
+queryOrder(String) String
}
class WechatPaySDK {
+unifiedOrder(String, int, String) WechatPayResponse
+refund(String, int) WechatPayResponse
+orderQuery(String) WechatPayResponse
}
class PaymentServiceFactory {
+createService(PaymentChannel) PaymentService
}
PaymentService <|.. AlipayPaymentAdapter
PaymentService <|.. WechatPaymentAdapter
AlipayPaymentAdapter --> AlipaySDK
WechatPaymentAdapter --> WechatPaySDK
PaymentServiceFactory ..> PaymentService
PaymentServiceFactory ..> AlipayPaymentAdapter
PaymentServiceFactory ..> WechatPaymentAdapter
场景一深度分析:
第三方支付接口整合是适配器模式在电商、金融系统中的典型应用场景。不同的支付渠道(支付宝、微信支付、银联等)提供的SDK在接口设计上存在显著差异:方法命名风格不一(createPayment vs unifiedOrder),参数类型各异(金额单位可能是元或分),返回值结构迥异(字符串、自定义对象或Map)。如果直接在业务代码中集成这些SDK,将导致代码充斥着if-else分支判断,难以维护和扩展。
通过引入适配器模式,我们定义统一的PaymentService接口作为业务层的契约,为每个支付渠道实现对应的适配器类。适配器内部封装了参数转换(如金额单位转换)、方法映射、返回值适配等逻辑。业务代码仅依赖PaymentService接口,通过工厂模式根据配置获取对应的适配器实例。
这种设计的扩展性体现在:当需要接入新的支付渠道(如PayPal、Apple Pay)时,只需新增一个适配器类,业务代码和现有适配器无需任何修改,完美符合开闭原则。同时,该架构支持运行时动态切换支付渠道,为灰度发布、A/B测试、故障降级提供了技术基础。
场景二:新旧系统接口兼容
/**
* 系统演进中的接口兼容
* 通过适配器使新系统能够调用遗留系统
*/
// 新系统用户接口(Target)
interface UserService {
UserDTO getUserById(Long userId);
UserDTO createUser(CreateUserRequest request);
void updateUser(UpdateUserRequest request);
List<UserDTO> searchUsers(UserSearchCriteria criteria);
}
// 新系统的数据模型
class UserDTO {
private Long id;
private String username;
private String email;
private String fullName;
private String phoneNumber;
private String status;
public UserDTO(Long id, String username, String email) {
this.id = id;
this.username = username;
this.email = email;
}
public Long getId() { return id; }
public String getUsername() { return username; }
public String getEmail() { return email; }
public String getFullName() { return fullName; }
public void setFullName(String fullName) { this.fullName = fullName; }
@Override
public String toString() {
return String.format("UserDTO[id=%d, username=%s, email=%s]", id, username, email);
}
}
class CreateUserRequest {
private String username;
private String email;
private String password;
public CreateUserRequest(String username, String email) {
this.username = username;
this.email = email;
}
public String getUsername() { return username; }
public String getEmail() { return email; }
}
class UpdateUserRequest {
private Long userId;
private String email;
private String fullName;
public UpdateUserRequest(Long userId) {
this.userId = userId;
}
}
class UserSearchCriteria {
private String keyword;
private String status;
}
// 遗留系统用户服务(Adaptee)
class OldUserService {
// 遗留系统的数据模型
static class LegacyUser {
String userId;
String loginName;
String emailAddress;
String realName;
String contactPhone;
String userState; // "A"=活跃, "I"=非活跃
LegacyUser(String userId, String loginName, String emailAddress) {
this.userId = userId;
this.loginName = loginName;
this.emailAddress = emailAddress;
}
}
public LegacyUser findUserByCode(String userCode) {
System.out.println("[遗留系统] 查询用户: " + userCode);
LegacyUser user = new LegacyUser(userCode, "legacy_user", "legacy@company.com");
user.realName = "Legacy User";
user.userState = "A";
return user;
}
public String addNewUser(String loginName, String email, String phone) {
System.out.println("[遗留系统] 创建用户: " + loginName);
return "LEGACY_" + System.currentTimeMillis();
}
public void modifyUserInfo(String userId, String email, String realName) {
System.out.println("[遗留系统] 更新用户: " + userId);
}
public List<LegacyUser> queryUsersByName(String namePattern) {
System.out.println("[遗留系统] 搜索用户: " + namePattern);
List<LegacyUser> users = new ArrayList<>();
users.add(new LegacyUser("001", "user1", "user1@test.com"));
return users;
}
}
// 用户服务适配器(防腐层)
class UserServiceAdapter implements UserService {
private OldUserService oldService;
public UserServiceAdapter(OldUserService oldService) {
this.oldService = oldService;
}
@Override
public UserDTO getUserById(Long userId) {
// ID类型转换:Long -> String
OldUserService.LegacyUser legacyUser = oldService.findUserByCode(String.valueOf(userId));
// 对象转换:LegacyUser -> UserDTO
UserDTO dto = new UserDTO(
Long.parseLong(legacyUser.userId),
legacyUser.loginName,
legacyUser.emailAddress
);
dto.setFullName(legacyUser.realName);
return dto;
}
@Override
public UserDTO createUser(CreateUserRequest request) {
String userId = oldService.addNewUser(
request.getUsername(),
request.getEmail(),
"" // 遗留系统需要phone参数,这里给默认值
);
// 返回创建的用户
return new UserDTO(Long.parseLong(userId.replace("LEGACY_", "")),
request.getUsername(),
request.getEmail());
}
@Override
public void updateUser(UpdateUserRequest request) {
oldService.modifyUserInfo(
String.valueOf(request.getUserId()),
request.getEmail(),
request.getFullName()
);
}
@Override
public List<UserDTO> searchUsers(UserSearchCriteria criteria) {
List<OldUserService.LegacyUser> legacyUsers =
oldService.queryUsersByName(criteria.getKeyword());
return legacyUsers.stream()
.map(u -> new UserDTO(Long.parseLong(u.userId), u.loginName, u.emailAddress))
.collect(Collectors.toList());
}
}
public class LegacySystemAdapterDemo {
public static void main(String[] args) {
// 遗留系统实例
OldUserService oldService = new OldUserService();
// 适配器(防腐层)
UserService userService = new UserServiceAdapter(oldService);
// 新系统通过统一接口调用
System.out.println("=== 新系统调用遗留用户服务 ===");
// 查询用户
UserDTO user = userService.getUserById(1001L);
System.out.println("查询结果: " + user);
// 创建用户
CreateUserRequest createReq = new CreateUserRequest("new_user", "new@company.com");
UserDTO newUser = userService.createUser(createReq);
System.out.println("创建用户: " + newUser);
// 搜索用户
UserSearchCriteria criteria = new UserSearchCriteria();
criteria.setKeyword("user");
List<UserDTO> users = userService.searchUsers(criteria);
System.out.println("搜索结果数量: " + users.size());
System.out.println("\n防腐层的价值:");
System.out.println("1. 隔离变化:新系统不直接依赖遗留系统接口");
System.out.println("2. 渐进式重构:可逐步用新实现替换遗留系统");
System.out.println("3. 测试友好:可以轻松Mock适配器进行单元测试");
System.out.println("4. 双向适配:也可实现从遗留系统调用新系统的适配器");
}
}
场景二时序图
sequenceDiagram
participant NewSystem as 新系统
participant Adapter as UserServiceAdapter<br/>(防腐层)
participant Legacy as 遗留系统
NewSystem->>Adapter: getUserById(1001L)
Note over Adapter: 参数转换<br/>Long -> String
Adapter->>Legacy: findUserByCode("1001")
Note over Legacy: 执行遗留逻辑
Legacy-->>Adapter: LegacyUser对象
Note over Adapter: 返回值转换<br/>LegacyUser -> UserDTO
Adapter-->>NewSystem: UserDTO
NewSystem->>Adapter: createUser(request)
Note over Adapter: 请求参数映射
Adapter->>Legacy: addNewUser(name, email, phone)
Legacy-->>Adapter: userId (String)
Note over Adapter: 响应转换<br/>String -> UserDTO
Adapter-->>NewSystem: UserDTO
场景二深度分析:
在系统演进过程中,新旧系统并存是常态。遗留系统承载着核心业务逻辑,经过长期验证稳定可靠,但其接口设计往往不符合现代架构规范。直接修改遗留系统风险极高,而让新系统直接依赖旧接口又会污染新架构的整洁性。适配器模式在此场景下扮演了**防腐层(Anti-Corruption Layer)**的角色。
适配器作为新旧系统之间的翻译官,承担双向转换职责:将新系统的标准请求转换为遗留系统能够理解的格式(参数类型转换、方法名映射、默认值填充),再将遗留系统的返回结果包装为新系统期望的数据结构。这种隔离使得新系统可以保持清晰的领域模型和接口设计,不受遗留系统的"污染"。
防腐层的战略价值还体现在它为系统演进提供了平滑路径。当条件成熟时,可以在适配器背后逐步用新实现替换遗留功能,而对调用方完全透明。这种"绞杀者模式"(Strangler Pattern)是大型系统现代化改造的核心策略之一。
场景三:多数据源驱动适配
/**
* 多数据库驱动适配器
* 统一MySQL、PostgreSQL、Oracle的连接方式
*/
// 统一数据源接口(Target)
interface DataSource {
Connection getConnection();
Connection getConnection(String username, String password);
void setUrl(String url);
void setDriverClass(String driverClass);
}
// 标准连接接口
interface Connection {
Statement createStatement();
void close();
}
interface Statement {
ResultSet executeQuery(String sql);
int executeUpdate(String sql);
}
interface ResultSet {
boolean next();
String getString(String columnName);
}
// MySQL驱动(被适配者)
class MySQLDriver {
private String host;
private int port;
private String database;
public MySQLDriver(String host, int port, String database) {
this.host = host;
this.port = port;
this.database = database;
}
public MySQLConnection connect(String user, String password) {
System.out.println("[MySQL] 连接: " + host + ":" + port + "/" + database);
return new MySQLConnection();
}
static class MySQLConnection {
public MySQLStatement create() {
return new MySQLStatement();
}
public void disconnect() {
System.out.println("[MySQL] 断开连接");
}
}
static class MySQLStatement {
public MySQLResultSet query(String sql) {
System.out.println("[MySQL] 执行查询: " + sql);
return new MySQLResultSet();
}
public int update(String sql) {
System.out.println("[MySQL] 执行更新: " + sql);
return 1;
}
}
static class MySQLResultSet {
private String[] data = {"mysql_data"};
private int index = -1;
public boolean hasNext() {
return index < data.length - 1;
}
public void moveNext() {
index++;
}
public String getValue(String column) {
return data[index];
}
}
}
// PostgreSQL驱动(被适配者)
class PostgreSQLDriver {
private String connectionString;
public PostgreSQLDriver(String connectionString) {
this.connectionString = connectionString;
}
public PGConnection openConnection(String username, String pwd) {
System.out.println("[PostgreSQL] 打开连接: " + connectionString);
return new PGConnection();
}
static class PGConnection {
public PGStatement prepareStatement() {
return new PGStatement();
}
public void terminate() {
System.out.println("[PostgreSQL] 终止连接");
}
}
static class PGStatement {
public PGResultSet executeQueryCommand(String sqlCommand) {
System.out.println("[PostgreSQL] 执行命令: " + sqlCommand);
return new PGResultSet();
}
}
static class PGResultSet {
private String[][] rows = {{"pg_data"}};
private int rowNum = 0;
public boolean fetchNext() {
return ++rowNum < rows.length;
}
public String fetchColumn(String colName) {
return rows[rowNum][0];
}
}
}
// MySQL数据源适配器
class MySQLDataSourceAdapter implements DataSource {
private MySQLDriver driver;
private String url;
private String username;
private String password;
public MySQLDataSourceAdapter() {}
@Override
public void setUrl(String url) {
this.url = url;
// 解析JDBC URL: jdbc:mysql://localhost:3306/mydb
String[] parts = url.replace("jdbc:mysql://", "").split(":");
String host = parts[0];
String[] portAndDb = parts[1].split("/");
int port = Integer.parseInt(portAndDb[0]);
String database = portAndDb[1];
this.driver = new MySQLDriver(host, port, database);
}
@Override
public void setDriverClass(String driverClass) {
// 在适配器中可以忽略,因为已固定使用MySQLDriver
}
@Override
public Connection getConnection() {
return getConnection(this.username, this.password);
}
@Override
public Connection getConnection(String username, String password) {
MySQLDriver.MySQLConnection rawConn = driver.connect(username, password);
return new MySQLConnectionAdapter(rawConn);
}
// MySQL连接适配器
static class MySQLConnectionAdapter implements Connection {
private MySQLDriver.MySQLConnection rawConn;
MySQLConnectionAdapter(MySQLDriver.MySQLConnection rawConn) {
this.rawConn = rawConn;
}
@Override
public Statement createStatement() {
return new MySQLStatementAdapter(rawConn.create());
}
@Override
public void close() {
rawConn.disconnect();
}
}
// MySQL Statement适配器
static class MySQLStatementAdapter implements Statement {
private MySQLDriver.MySQLStatement rawStmt;
MySQLStatementAdapter(MySQLDriver.MySQLStatement rawStmt) {
this.rawStmt = rawStmt;
}
@Override
public ResultSet executeQuery(String sql) {
return new MySQLResultSetAdapter(rawStmt.query(sql));
}
@Override
public int executeUpdate(String sql) {
return rawStmt.update(sql);
}
}
// MySQL ResultSet适配器
static class MySQLResultSetAdapter implements ResultSet {
private MySQLDriver.MySQLResultSet rawRs;
MySQLResultSetAdapter(MySQLDriver.MySQLResultSet rawRs) {
this.rawRs = rawRs;
}
@Override
public boolean next() {
if (rawRs.hasNext()) {
rawRs.moveNext();
return true;
}
return false;
}
@Override
public String getString(String columnName) {
return rawRs.getValue(columnName);
}
}
}
public class MultiDataSourceAdapterDemo {
public static void main(String[] args) {
System.out.println("=== MySQL数据源适配 ===");
DataSource mysqlDS = new MySQLDataSourceAdapter();
mysqlDS.setUrl("jdbc:mysql://localhost:3306/testdb");
Connection conn = mysqlDS.getConnection("root", "password");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
while (rs.next()) {
System.out.println("数据: " + rs.getString("name"));
}
conn.close();
System.out.println("\n多数据源适配的价值:");
System.out.println("1. 统一接口:应用层使用相同的DataSource API");
System.out.println("2. 屏蔽差异:隐藏不同数据库的连接、查询语法差异");
System.out.println("3. 驱动替换:更换数据库只需替换适配器实现");
System.out.println("4. 类似JDBC:这正是JDBC Driver的设计思想");
}
}
场景三流程图
flowchart TB
Start([应用请求连接]) --> GetDS[获取DataSource实例]
GetDS --> CheckType{判断数据库类型}
CheckType -->|MySQL| MySQLAdapter[MySQL适配器]
CheckType -->|PostgreSQL| PGAdapter[PostgreSQL适配器]
CheckType -->|Oracle| OracleAdapter[Oracle适配器]
MySQLAdapter --> ParseURL1[解析JDBC URL]
ParseURL1 --> CreateDriver1[创建MySQLDriver实例]
CreateDriver1 --> Connect1[调用connect方法]
Connect1 --> AdaptConn1[适配Connection对象]
PGAdapter --> ParseURL2[解析JDBC URL]
ParseURL2 --> CreateDriver2[创建PostgreSQLDriver]
CreateDriver2 --> Connect2[调用openConnection]
Connect2 --> AdaptConn2[适配PGConnection]
AdaptConn1 --> ReturnConn[返回标准Connection]
AdaptConn2 --> ReturnConn
ReturnConn --> CreateStmt[创建Statement]
CreateStmt --> ExecuteSQL[执行SQL]
ExecuteSQL --> AdaptRS[适配ResultSet]
AdaptRS --> ReturnData[返回查询结果]
ReturnData --> End([应用处理数据])
style MySQLAdapter fill:#e1f5fe
style PGAdapter fill:#e1f5fe
style OracleAdapter fill:#e1f5fe
场景三深度分析:
数据库驱动适配是适配器模式在基础设施层的经典实践,JDBC(Java Database Connectivity)本身就是这一模式的集大成者。不同的数据库厂商提供了各自的驱动实现,连接方式、SQL方言、数据类型映射均存在差异。JDBC定义了一套统一的接口规范(Connection、Statement、ResultSet等),各数据库厂商通过实现这些接口来提供适配器,使得Java应用可以用相同的API操作任何数据库。
本例模拟了JDBC的设计思想:定义DataSource作为数据源抽象,为MySQL实现完整的适配器链(MySQLDataSourceAdapter → MySQLConnectionAdapter → MySQLStatementAdapter → MySQLResultSetAdapter)。每一层适配器都持有原始驱动对象的引用,在实现标准接口方法时委托给原始对象执行,同时完成接口转换和结果适配。
这种设计的精妙之处在于分层适配:不仅适配了数据源级别,还对连接、语句、结果集进行了完整适配,形成了一条完整的适配链。应用层代码完全依赖JDBC标准接口,更换数据库时只需替换驱动jar包和连接URL,无需修改任何业务代码。这正是适配器模式实现可插拔架构的典范。
场景四:日志框架统一门面
/**
* 日志门面与适配器
* 模拟SLF4J的门面+适配器设计
*/
// 日志门面接口(Target)
interface Logger {
void info(String message);
void debug(String message);
void warn(String message);
void error(String message, Throwable t);
boolean isDebugEnabled();
boolean isInfoEnabled();
}
// 日志工厂
class LoggerFactory {
private static final Map<String, Logger> loggerCache = new HashMap<>();
private static LogBinding binding;
static {
// 静态绑定:检测类路径中的日志实现
binding = detectBinding();
System.out.println("LoggerFactory绑定到: " + binding.getName());
}
private static LogBinding detectBinding() {
try {
Class.forName("ch.qos.logback.classic.Logger");
return new LogbackBinding();
} catch (ClassNotFoundException e) {
try {
Class.forName("org.apache.logging.log4j.Logger");
return new Log4j2Binding();
} catch (ClassNotFoundException ex) {
return new NOPBinding();
}
}
}
public static Logger getLogger(String name) {
return loggerCache.computeIfAbsent(name, binding::createLogger);
}
public static Logger getLogger(Class<?> clazz) {
return getLogger(clazz.getName());
}
}
// 绑定接口
interface LogBinding {
String getName();
Logger createLogger(String name);
}
// Logback绑定
class LogbackBinding implements LogBinding {
@Override
public String getName() {
return "Logback";
}
@Override
public Logger createLogger(String name) {
return new LogbackAdapter(name);
}
}
// Log4j2绑定
class Log4j2Binding implements LogBinding {
@Override
public String getName() {
return "Log4j2";
}
@Override
public Logger createLogger(String name) {
return new Log4j2Adapter(name);
}
}
// NOP绑定(无日志实现时的默认绑定)
class NOPBinding implements LogBinding {
@Override
public String getName() {
return "NOP";
}
@Override
public Logger createLogger(String name) {
return new NOPLogger();
}
}
// Logback适配器
class LogbackAdapter implements Logger {
private final ch.qos.logback.classic.Logger logger;
public LogbackAdapter(String name) {
this.logger = (ch.qos.logback.classic.Logger)
org.slf4j.LoggerFactory.getLogger(name);
}
@Override
public void info(String message) {
logger.info(message);
}
@Override
public void debug(String message) {
logger.debug(message);
}
@Override
public void warn(String message) {
logger.warn(message);
}
@Override
public void error(String message, Throwable t) {
logger.error(message, t);
}
@Override
public boolean isDebugEnabled() {
return logger.isDebugEnabled();
}
@Override
public boolean isInfoEnabled() {
return logger.isInfoEnabled();
}
}
// Log4j2适配器
class Log4j2Adapter implements Logger {
private final org.apache.logging.log4j.Logger logger;
public Log4j2Adapter(String name) {
this.logger = org.apache.logging.log4j.LogManager.getLogger(name);
}
@Override
public void info(String message) {
logger.info(message);
}
@Override
public void debug(String message) {
logger.debug(message);
}
@Override
public void warn(String message) {
logger.warn(message);
}
@Override
public void error(String message, Throwable t) {
logger.error(message, t);
}
@Override
public boolean isDebugEnabled() {
return logger.isDebugEnabled();
}
@Override
public boolean isInfoEnabled() {
return logger.isInfoEnabled();
}
}
// NOP Logger(空实现)
class NOPLogger implements Logger {
@Override public void info(String message) {}
@Override public void debug(String message) {}
@Override public void warn(String message) {}
@Override public void error(String message, Throwable t) {}
@Override public boolean isDebugEnabled() { return false; }
@Override public boolean isInfoEnabled() { return false; }
}
// 业务类
class UserService {
private static final Logger log = LoggerFactory.getLogger(UserService.class);
public void processUser(String userId) {
log.info("开始处理用户: " + userId);
if (log.isDebugEnabled()) {
log.debug("用户详情查询中...");
}
try {
// 业务逻辑
if (userId == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
log.info("用户处理成功: " + userId);
} catch (Exception e) {
log.error("用户处理失败", e);
}
}
}
public class LoggingFacadeDemo {
public static void main(String[] args) {
System.out.println("=== 日志门面适配器模式 ===\n");
UserService userService = new UserService();
userService.processUser("user_001");
userService.processUser(null);
System.out.println("\n日志门面的设计价值:");
System.out.println("1. 门面模式:提供简洁统一的日志API");
System.out.println("2. 适配器模式:将门面调用适配到具体日志实现");
System.out.println("3. 静态绑定:编译时确定绑定,运行时零开销");
System.out.println("4. 零依赖:切换日志框架只需更换一个jar包");
}
}
场景四类图
classDiagram
class Logger {
<<interface>>
+info(String)
+debug(String)
+warn(String)
+error(String, Throwable)
}
class LoggerFactory {
-binding: LogBinding
-loggerCache: Map
+getLogger(String) Logger
-detectBinding() LogBinding
}
class LogBinding {
<<interface>>
+getName() String
+createLogger(String) Logger
}
class LogbackBinding {
+createLogger(String) Logger
}
class Log4j2Binding {
+createLogger(String) Logger
}
class LogbackAdapter {
-logger: Logger
+info(String)
+debug(String)
}
class Log4j2Adapter {
-logger: Logger
+info(String)
+debug(String)
}
class LogbackLogger {
+info(String)
+debug(String)
}
class Log4j2Logger {
+info(String)
+debug(String)
}
Logger <|.. LogbackAdapter
Logger <|.. Log4j2Adapter
LoggerFactory --> LogBinding
LogBinding <|.. LogbackBinding
LogBinding <|.. Log4j2Binding
LogbackBinding ..> LogbackAdapter
Log4j2Binding ..> Log4j2Adapter
LogbackAdapter --> LogbackLogger
Log4j2Adapter --> Log4j2Logger
场景四深度分析:
SLF4J(Simple Logging Facade for Java)是门面模式与适配器模式结合应用的巅峰之作。它首先运用门面模式为Java应用提供了一套简洁、一致的日志API,开发者只需面向SLF4J的Logger接口编程。而后端具体使用哪个日志框架(Logback、Log4j2、JUL等),则通过适配器模式在部署时动态绑定。
静态绑定机制是SLF4J的核心创新。它在LoggerFactory的静态初始化块中,通过检测类路径中是否存在特定日志框架的类,来确定当前环境应该使用哪个绑定。这种设计避免了传统SPI机制的性能开销,实现了"零开销抽象"。
适配器层(如LogbackAdapter)实现了SLF4J的Logger接口,内部持有具体日志框架的Logger实例,将所有日志调用委托给底层实现。这种设计使得应用代码与具体日志框架完全解耦,开发者可以在不修改一行代码的情况下,仅通过替换classpath中的jar包来切换日志实现。
门面模式提供简化接口,适配器模式完成接口转换,二者相辅相成,共同构成了SLF4J优雅的设计。这也是为什么SLF4J能够成为Java生态中事实上的日志标准的原因。
场景五:多媒体播放器格式适配
/**
* 多媒体播放器格式适配
* 通过适配器支持MP3、MP4、AVI、FLV等多种格式
*/
// 媒体播放器接口(Target)
interface MediaPlayer {
void play(String fileName);
void stop();
void pause();
boolean supports(String format);
}
// 音频解码器接口
interface AudioDecoder {
byte[] decodeAudio(byte[] input);
}
// 视频解码器接口
interface VideoDecoder {
VideoFrame decodeVideo(byte[] input);
}
class VideoFrame {
private byte[] data;
public VideoFrame(byte[] data) { this.data = data; }
}
// MP3解码器(被适配者)
class MP3Decoder implements AudioDecoder {
@Override
public byte[] decodeAudio(byte[] input) {
System.out.println("[MP3解码器] 解码MP3音频流");
return new byte[1024];
}
}
// MP4解码器(被适配者)
class MP4Decoder {
public VideoFrame decodeMP4Video(byte[] videoData) {
System.out.println("[MP4解码器] 解码MP4视频流");
return new VideoFrame(new byte[2048]);
}
public byte[] decodeMP4Audio(byte[] audioData) {
System.out.println("[MP4解码器] 解码MP4音频流");
return new byte[1024];
}
}
// AVI解码器(被适配者)
class AVIDecoder {
public AVIStream decodeAVI(byte[] aviData) {
System.out.println("[AVI解码器] 解码AVI流");
return new AVIStream();
}
static class AVIStream {
public VideoFrame getVideoFrame() { return new VideoFrame(new byte[2048]); }
public byte[] getAudioData() { return new byte[1024]; }
}
}
// FLV解码器(被适配者)
class FLVDecoder {
public void initialize() {
System.out.println("[FLV解码器] 初始化");
}
public FLVFrame parseFLVTag(byte[] tag) {
System.out.println("[FLV解码器] 解析FLV标签");
return new FLVFrame();
}
static class FLVFrame {
public boolean isVideo() { return true; }
public byte[] getPayload() { return new byte[2048]; }
}
}
// MP3播放器适配器
class MP3PlayerAdapter implements MediaPlayer {
private MP3Decoder decoder;
private boolean playing = false;
public MP3PlayerAdapter() {
this.decoder = new MP3Decoder();
}
@Override
public void play(String fileName) {
System.out.println("播放MP3: " + fileName);
byte[] dummyData = fileName.getBytes();
decoder.decodeAudio(dummyData);
playing = true;
}
@Override
public void stop() {
System.out.println("停止MP3播放");
playing = false;
}
@Override
public void pause() {
System.out.println("暂停MP3播放");
playing = false;
}
@Override
public boolean supports(String format) {
return "mp3".equalsIgnoreCase(format);
}
}
// MP4播放器适配器
class MP4PlayerAdapter implements MediaPlayer {
private MP4Decoder decoder;
public MP4PlayerAdapter() {
this.decoder = new MP4Decoder();
}
@Override
public void play(String fileName) {
System.out.println("播放MP4: " + fileName);
byte[] dummyVideo = new byte[1024];
byte[] dummyAudio = new byte[512];
decoder.decodeMP4Video(dummyVideo);
decoder.decodeMP4Audio(dummyAudio);
}
@Override
public void stop() {
System.out.println("停止MP4播放");
}
@Override
public void pause() {
System.out.println("暂停MP4播放");
}
@Override
public boolean supports(String format) {
return "mp4".equalsIgnoreCase(format);
}
}
// AVI播放器适配器
class AVIPlayerAdapter implements MediaPlayer {
private AVIDecoder decoder;
public AVIPlayerAdapter() {
this.decoder = new AVIDecoder();
}
@Override
public void play(String fileName) {
System.out.println("播放AVI: " + fileName);
AVIDecoder.AVIStream stream = decoder.decodeAVI(fileName.getBytes());
stream.getVideoFrame();
stream.getAudioData();
}
@Override
public void stop() {
System.out.println("停止AVI播放");
}
@Override
public void pause() {
System.out.println("暂停AVI播放");
}
@Override
public boolean supports(String format) {
return "avi".equalsIgnoreCase(format);
}
}
// 播放器工厂
class MediaPlayerFactory {
private static final List<MediaPlayer> players = new ArrayList<>();
static {
// 注册所有格式的播放器
players.add(new MP3PlayerAdapter());
players.add(new MP4PlayerAdapter());
players.add(new AVIPlayerAdapter());
}
public static MediaPlayer getPlayer(String fileName) {
String extension = fileName.substring(fileName.lastIndexOf('.') + 1);
for (MediaPlayer player : players) {
if (player.supports(extension)) {
return player;
}
}
throw new IllegalArgumentException("不支持的格式: " + extension);
}
// 插件化扩展:注册新格式播放器
public static void registerPlayer(MediaPlayer player) {
players.add(player);
}
}
// 通用播放器
class UniversalMediaPlayer {
public void playFile(String fileName) {
System.out.println("\n=== 播放文件: " + fileName + " ===");
MediaPlayer player = MediaPlayerFactory.getPlayer(fileName);
player.play(fileName);
player.stop();
}
}
public class MediaPlayerAdapterDemo {
public static void main(String[] args) {
UniversalMediaPlayer player = new UniversalMediaPlayer();
player.playFile("song.mp3");
player.playFile("movie.mp4");
player.playFile("video.avi");
// 演示插件化扩展:动态添加新格式支持
System.out.println("\n=== 动态扩展FLV格式支持 ===");
MediaPlayerFactory.registerPlayer(new MediaPlayer() {
private FLVDecoder decoder = new FLVDecoder();
@Override
public void play(String fileName) {
decoder.initialize();
decoder.parseFLVTag(fileName.getBytes());
System.out.println("播放FLV: " + fileName);
}
@Override public void stop() {}
@Override public void pause() {}
@Override public boolean supports(String format) {
return "flv".equalsIgnoreCase(format);
}
});
player.playFile("stream.flv");
System.out.println("\n多媒体播放器适配的价值:");
System.out.println("1. 统一接口:播放器核心逻辑不关心具体格式");
System.out.println("2. 插件化架构:新增格式无需修改现有代码");
System.out.println("3. 格式解耦:每种格式独立维护和优化");
System.out.println("4. 灵活组合:可同时支持音频和视频格式");
}
}
场景五流程图
flowchart TB
Start([用户选择文件]) --> ExtractExt[提取文件扩展名]
ExtractExt --> Factory[MediaPlayerFactory.getPlayer]
Factory --> Iterate{遍历注册的播放器}
Iterate -->|检查播放器| CheckSupport{supports格式?}
CheckSupport -->|否| Iterate
CheckSupport -->|是| GetPlayer[获取匹配的播放器]
GetPlayer --> CheckType{播放器类型?}
CheckType -->|MP3| MP3Flow[MP3适配器]
CheckType -->|MP4| MP4Flow[MP4适配器]
CheckType -->|AVI| AVIFlow[AVI适配器]
CheckType -->|FLV| FLVFlow[FLV适配器]
MP3Flow --> MP3Decode[MP3Decoder.decodeAudio]
MP4Flow --> MP4Video[MP4Decoder.decodeMP4Video]
MP4Video --> MP4Audio[MP4Decoder.decodeMP4Audio]
AVIFlow --> AVIDecode[AVIDecoder.decodeAVI]
FLVFlow --> FLVInit[FLVDecoder.initialize]
FLVInit --> FLVParse[FLVDecoder.parseFLVTag]
MP3Decode --> Output[输出音频]
MP4Audio --> OutputVideo[输出视频+音频]
AVIDecode --> OutputVideo
FLVParse --> OutputVideo
Output --> Stop[播放器停止]
OutputVideo --> Stop
Stop --> End([播放完成])
style Factory fill:#ffcc80
style MP3Flow fill:#a5d6a5
style MP4Flow fill:#a5d6a5
style AVIFlow fill:#a5d6a5
style FLVFlow fill:#a5d6a5
场景五深度分析:
多媒体播放器是展示适配器模式插件化架构能力的绝佳场景。不同媒体格式(MP3、MP4、AVI、FLV等)有着各自的编码规范和解码算法,通常由不同的第三方解码库实现。如果直接在播放器核心逻辑中硬编码对各种格式的处理,不仅会导致代码臃肿,更严重违背了开闭原则。
适配器模式为此提供了优雅的解决方案。我们定义统一的MediaPlayer接口作为播放器的标准契约,为每种媒体格式实现对应的适配器。每个适配器封装了特定解码库的调用逻辑,包括初始化、解码参数配置、音视频流同步等复杂细节。播放器核心只依赖MediaPlayer接口,通过工厂模式根据文件扩展名获取对应的适配器实例。
这种架构的插件化能力尤为突出。当需要支持新格式时,只需实现一个新的适配器类并注册到工厂中,完全无需修改播放器核心代码。这种设计使得播放器成为一个开放的平台,第三方甚至可以为自己的专有格式开发适配器插件,极大地提升了系统的可扩展性和生态友好性。
七、面试题精选与专家级解答
Q1:类适配器与对象适配器有什么区别?分别适用于什么场景?
参考答案:
类适配器通过继承Adaptee实现接口转换,而对象适配器通过组合持有Adaptee引用。
技术差异:
- 类适配器在编译时静态绑定Adaptee,无法在运行时更换被适配对象;对象适配器支持动态替换
- 类适配器受单继承限制,只能适配一个Adaptee;对象适配器可适配多个
- 类适配器可以重写Adaptee的方法改变其行为;对象适配器无法直接重写
- 类适配器会暴露Adaptee的public方法,破坏封装;对象适配器仅暴露Target接口
适用场景:
- 类适配器:当需要重写Adaptee的部分行为,且确定不会适配其他Adaptee子类时
- 对象适配器:绝大多数场景,特别是需要灵活适配多个Adaptee或运行时切换时
最佳实践:优先使用对象适配器,遵循"组合优于继承"原则。
Q2:适配器模式和装饰器模式在结构上相似,如何从意图上区分?
参考答案:
虽然二者都涉及包装对象,但设计意图截然不同:
| 维度 | 适配器模式 | 装饰器模式 |
|---|---|---|
| 核心意图 | 接口转换,让不兼容接口协作 | 功能增强,动态添加新职责 |
| 接口变化 | 目标接口 ≠ 被适配者接口 | 装饰器与被装饰者实现相同接口 |
| 调用关系 | 单向适配 | 可多层嵌套装饰 |
| 关注点 | 解决遗留系统集成、第三方库兼容 | 解决功能扩展、避免子类爆炸 |
代码层面区分:如果包装后接口发生变化,是适配器;如果接口保持不变,是装饰器。
面试加分点:提及Java I/O流中,InputStreamReader是适配器(字节→字符),BufferedInputStream是装饰器(功能增强)。
Q3:JDK中哪些地方使用了适配器模式?请至少列举四个并分析实现细节
参考答案:
-
Arrays.asList(T... a):
- 将数组适配为List接口
- 返回Arrays内部类ArrayList,持有数组引用,List方法委托给数组操作
- 体现对象适配器思想
-
Collections.synchronizedList/unmodifiableList:
- 包装适配器(Wrapper Adapter)
- 在委托调用前后增加同步/不可变控制逻辑
- 实现了相同的List接口
-
InputStreamReader/OutputStreamWriter:
- 字节流到字符流的桥梁适配器
- 内部持有StreamDecoder/StreamEncoder进行编码转换
- 典型的类适配器(继承Reader/Writer)
-
MouseAdapter/KeyAdapter:
- 缺省适配器(Default Adapter)变体
- 为接口所有方法提供空实现,子类只重写需要的方法
- 避免强制实现不需要的方法
加分项:还可提及Enumeration与Iterator的适配(Collections.enumeration/iterator)。
Q4:Spring MVC的HandlerAdapter是如何体现适配器模式的?它的设计解决了什么问题?
参考答案:
HandlerAdapter是Spring MVC中的核心适配器接口:
public interface HandlerAdapter {
boolean supports(Object handler);
ModelAndView handle(HttpServletRequest req, HttpServletResponse res, Object handler);
}
问题背景:
- Spring MVC早期只有Controller接口一种处理器
- 后来引入了@Controller注解、HttpRequestHandler、Servlet等多种处理器形式
- 不同类型处理器的执行方式各不相同
适配器机制:
- DispatcherServlet不直接调用处理器,而是遍历HandlerAdapter列表
- 每个Adapter负责一种处理器类型(如RequestMappingHandlerAdapter处理@Controller)
- supports()判断是否能处理当前handler,handle()执行实际调用
设计价值:
- 符合开闭原则:新增处理器类型只需添加新Adapter
- 统一处理流程:DispatcherServlet代码简洁稳定
- 职责分离:适配逻辑封装在各自Adapter中
Q5:SLF4J是如何通过适配器模式绑定不同日志实现的?请简述其静态绑定机制
参考答案:
SLF4J采用门面模式+适配器模式的双重设计:
静态绑定机制:
- SLF4J API包中定义org.slf4j.impl.StaticLoggerBinder类(编译时占位)
- 各日志适配器包(如slf4j-logback.jar)提供自己的StaticLoggerBinder实现
- JVM类加载时,classpath中只能存在一个StaticLoggerBinder实现
- LoggerFactory在静态初始化块中调用StaticLoggerBinder.getSingleton()获取ILoggerFactory
适配器链:
SLF4J Logger (接口)
↓
LogbackAdapter (实现SLF4J Logger)
↓
ch.qos.logback.classic.Logger (具体实现)
设计优势:
- 编译时确定绑定,运行时零开销
- 切换日志框架只需替换jar包
- 应用代码零修改
Q6:适配器模式与门面模式都可以解决接口问题,它们的使用场景有何不同?
参考答案:
| 对比维度 | 适配器模式 | 门面模式 |
|---|---|---|
| 解决问题 | 接口不兼容 | 接口太复杂 |
| 接口来源 | 目标接口已存在,适配已有类去匹配 | 门面接口是新定义的,简化复杂子系统 |
| 设计时序 | 事后补救(已有类接口不匹配) | 事前设计或重构(简化调用) |
| 调用方向 | 双向可能 | 单向(客户端→门面→子系统) |
| 典型应用 | 第三方SDK集成、遗留系统兼容 | 业务层封装、服务聚合 |
举例说明:
- 适配器:支付宝SDK接口 vs 统一支付接口,需要转换
- 门面:电商下单服务封装库存、支付、物流三个子系统调用
Q7:在微服务架构中,如何利用适配器模式实现多云环境的统一接口调用?
参考答案:
架构设计:
- 定义统一云服务接口:
interface CloudStorageService {
String upload(File file);
InputStream download(String objectId);
void delete(String objectId);
}
- 为每个云厂商实现适配器:
class AliyunStorageAdapter implements CloudStorageService {
private OSSClient client;
@Override
public String upload(File file) {
PutObjectResult result = client.putObject(...);
return result.getETag();
}
}
- 通过配置动态选择适配器:
@Configuration
class CloudConfig {
@Bean
@ConditionalOnProperty(name="cloud.vendor", havingValue="aliyun")
public CloudStorageService aliyunStorage() {
return new AliyunStorageAdapter();
}
}
实现价值:
- 多云无缝切换:修改配置即可更换云厂商
- 混合云部署:不同环境使用不同适配器
- 成本优化:可根据价格动态选择云服务
- 灾备能力:主云故障时快速切换到备用云
Q8:什么是双向适配器?请给出一个实际应用场景
参考答案:
定义:双向适配器同时实现两个不兼容的接口,使得它们可以互相调用。
实际场景——系统迁移过渡期:
class SystemMigrationAdapter implements NewSystemAPI, LegacySystemAPI {
private NewSystemService newService;
private LegacySystemService legacyService;
// 实现NewSystemAPI:将新系统调用适配到遗留系统
@Override
public UserDTO getUser(Long id) {
LegacyUser user = legacyService.findUserById(id);
return convertToDTO(user);
}
// 实现LegacySystemAPI:将遗留系统调用适配到新系统
@Override
public LegacyUser findUserById(Long id) {
UserDTO dto = newService.getUserInfo(id);
return convertToLegacy(dto);
}
}
应用价值:
- 平滑迁移:新旧系统可以互相调用
- 灰度验证:部分流量走新系统,部分走旧系统
- 回滚能力:新系统出问题时可快速切回
Q9:适配器模式如何与依赖注入(DI)结合使用?Spring中如何优雅地切换适配器实现?
参考答案:
结合方式:
- 通过接口注入适配器:
@Service
class PaymentService {
@Autowired
private PaymentAdapter adapter; // 注入适配器接口
public void processPayment(Order order) {
adapter.pay(order); // 透明调用
}
}
- Spring Profile切换实现:
@Configuration
class PaymentConfig {
@Bean
@Profile("alipay")
public PaymentAdapter alipayAdapter() {
return new AlipayAdapter();
}
@Bean
@Profile("wechat")
public PaymentAdapter wechatAdapter() {
return new WechatAdapter();
}
}
- @Conditional条件装配:
@Bean
@ConditionalOnProperty(name="payment.channel", havingValue="alipay")
public PaymentAdapter paymentAdapter() {
return new AlipayAdapter();
}
- 工厂模式+DI:
@Component
class PaymentAdapterFactory {
@Autowired
private Map<String, PaymentAdapter> adapters; // Spring自动注入所有实现
public PaymentAdapter getAdapter(String channel) {
return adapters.get(channel + "Adapter");
}
}
Q10:适配器模式是否违反了单一职责原则?请谈谈你的理解
参考答案:
表面看可能违反:
- 适配器既要实现Target接口,又要理解Adaptee的接口
- 涉及两种接口的转换逻辑,职责似乎不单一
深入分析——实际符合SRP:
- 适配器的唯一职责是接口转换
- 它不负责业务逻辑,只做协议翻译
- 两种接口知识的掌握是为了完成转换这一单一职责的必要条件
设计视角的辨析:
- SRP中"职责"定义:变化的原因
- 适配器变化的原因只有一个:接口转换规则变化
- Adaptee变化时适配器可能需要修改,但这正是转换职责的一部分
最佳实践:
- 保持适配器轻量,只做纯粹的转换
- 复杂的转换逻辑可以委托给专门的Converter类
- 适配器应该是"薄薄的一层"
八、总结与展望
适配器模式以其简洁而强大的设计思想,在Java技术栈的各个层面留下了深刻的印记。从JDK最基础的I/O流转换,到Spring框架灵活的扩展机制,再到分布式系统中多云环境的统一抽象,适配器模式始终扮演着"兼容性桥梁"的关键角色。
本文通过五个完整的代码演进案例、四个框架源码深度剖析、五个典型场景的独立实现,全面展示了适配器模式从基础应用到架构级设计的全貌。我们深刻认识到:适配器模式不仅是解决接口不兼容的技术手段,更是一种面向接口编程、拥抱变化的设计哲学。
在实际工程中运用适配器模式时,请牢记以下原则:
- 优先使用对象适配器,享受组合带来的灵活性
- 保持适配器的纯粹性,只做接口转换,不掺杂业务逻辑
- 与工厂模式、依赖注入结合,实现适配器的动态选择与切换
- 在系统边界使用适配器作为防腐层,保护核心领域模型的整洁
随着云原生、微服务架构的普及,适配器模式将在多协议互通、多厂商集成、异构系统对接等场景发挥愈发重要的作用。掌握适配器模式,不仅意味着掌握了一个设计模式,更意味着掌握了让不同系统"说同一种语言"的能力。