文章目录
1. 什么是适配器模式?2. 为什么需要适配器模式?3. 适配器模式的类型4. 适配器模式的结构5. 适配器模式的基本实现5.1 对象适配器示例:媒体播放器5.2 类适配器示例:电源适配器
6. 更多适配器模式示例6.1 图形绘制适配器6.2 数据转换适配器
7. 适配器模式的实际应用场景7.1 Java中的适配器模式应用7.1.1 Java I/O库中的适配器7.1.2 集合框架中的适配器7.1.3 Java事件处理中的适配器
7.2 第三方库集成
8. 适配器模式与其他设计模式的区别8.1 适配器模式 vs 桥接模式8.2 适配器模式 vs 装饰器模式8.3 适配器模式 vs 外观模式
9. 适配器模式的优缺点9.1 优点9.2 缺点
10. 何时使用适配器模式?11. 常见问题及解决方案11.1 问题:如何选择类适配器还是对象适配器?11.2 问题:如何处理适配者类方法与目标接口方法参数不匹配的情况?11.3 问题:如何处理适配者类的方法调用异常?
12. 总结
1. 什么是适配器模式?
适配器模式是一种结构型设计模式,它允许不兼容的接口一起工作。适配器模式通过将一个类的接口转换成客户端期望的另一个接口,使得原本不兼容的类能够协同工作。
就像现实生活中的电源适配器,它可以使得不同国家的电源插头能够在不同规格的插座上使用。在软件设计中,适配器模式起着类似的作用,帮助不同接口的对象能够相互协作。
2. 为什么需要适配器模式?
在以下情况下,适配器模式特别有用:
当需要使用现有的类,但其接口与需求不匹配时:适配器模式可以在不修改原有代码的情况下,使不兼容的接口能够协同工作当需要复用一些现有的类,但它们的接口与应用程序的其他部分不兼容时:适配器可以充当这些类与应用程序之间的中介当需要使用多个现有的子类,但为每一个子类都创建新的适配器类又不现实时:可以使用对象适配器,用一个适配器适配多个子类当需要集成第三方库或遗留系统时:适配器模式可以帮助新系统与旧系统无缝对接
3. 适配器模式的类型
适配器模式有两种主要类型:
类适配器:使用继承机制,通过继承目标接口和被适配者类来实现适配对象适配器:使用组合机制,通过持有被适配者的实例来实现适配
4. 适配器模式的结构
适配器模式通常包含以下角色:
目标接口(Target):定义客户端期望的接口适配者(Adaptee):需要被适配的类或接口适配器(Adapter):实现目标接口,并使用适配者,将目标接口转换为适配者接口
5. 适配器模式的基本实现
5.1 对象适配器示例:媒体播放器
假设我们有一个播放MP3格式音乐的媒体播放器,现在想要扩展它,使其能够播放其他格式的音频文件。我们可以使用适配器模式来解决这个问题。
首先,定义目标接口:
// 媒体播放器目标接口
public interface MediaPlayer {
void play(String audioType, String fileName);
}
然后,定义适配者接口及其实现:
// 高级媒体播放器接口(适配者接口)
public interface AdvancedMediaPlayer {
void playVlc(String fileName);
void playMp4(String fileName);
}
// VLC播放器实现
public class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("播放VLC文件:" + fileName);
}
@Override
public void playMp4(String fileName) {
// 什么也不做
}
}
// MP4播放器实现
public class Mp4Player implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
// 什么也不做
}
@Override
public void playMp4(String fileName) {
System.out.println("播放MP4文件:" + fileName);
}
}
接下来,创建适配器类:
// 媒体适配器
public class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer = new VlcPlayer();
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer = new Mp4Player();
}
}
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer.playVlc(fileName);
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer.playMp4(fileName);
}
}
}
最后,创建具体的媒体播放器:
// 具体媒体播放器
public class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
// 内置支持MP3格式
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("播放MP3文件:" + fileName);
}
// 使用适配器支持其他格式
else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("不支持的音频格式:" + audioType);
}
}
}
客户端代码:
public class AdapterPatternDemo {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
// 播放MP3格式(内置支持)
audioPlayer.play("mp3", "beyond_the_horizon.mp3");
// 播放VLC格式(通过适配器)
audioPlayer.play("vlc", "far_away.vlc");
// 播放MP4格式(通过适配器)
audioPlayer.play("mp4", "alone.mp4");
// 尝试播放不支持的格式
audioPlayer.play("avi", "mind_me.avi");
}
}
输出结果:
播放MP3文件:beyond_the_horizon.mp3
播放VLC文件:far_away.vlc
播放MP4文件:alone.mp4
不支持的音频格式:avi
在这个例子中,MediaAdapter是一个适配器,它实现了MediaPlayer接口(目标接口),并持有AdvancedMediaPlayer的实例(适配者)。通过这个适配器,AudioPlayer能够播放原本不支持的VLC和MP4格式的文件。
5.2 类适配器示例:电源适配器
假设我们有一个电器设备需要使用5V的电压,但可用的电源是220V。我们可以使用类适配器模式来解决这个问题。
由于Java不支持多继承,我们将使用接口+继承的方式来实现类适配器。
首先,定义目标接口和适配者类:
// 目标接口:5V电压
public interface Voltage5V {
int output5V();
}
// 适配者类:220V电压
public class Voltage220V {
public int output220V() {
int src = 220;
System.out.println("输出电压:" + src + "V");
return src;
}
}
然后,创建适配器类:
// 电压适配器(类适配器)
public class VoltageAdapter extends Voltage220V implements Voltage5V {
@Override
public int output5V() {
// 获取220V电压
int srcV = output220V();
// 转换为5V电压
int dstV = srcV / 44;
System.out.println("适配器将" + srcV + "V电压转换为" + dstV + "V");
return dstV;
}
}
最后,创建电器设备类和客户端代码:
// 电器设备类
public class ElectricDevice {
private Voltage5V voltage5V;
public ElectricDevice(Voltage5V voltage5V) {
this.voltage5V = voltage5V;
}
public void charging() {
int volt = voltage5V.output5V();
if (volt == 5) {
System.out.println("电压为5V,正常充电");
} else {
System.out.println("电压不符合要求,无法充电");
}
}
}
// 客户端代码
public class ClassAdapterDemo {
public static void main(String[] args) {
// 创建适配器
VoltageAdapter adapter = new VoltageAdapter();
// 创建电器设备,使用适配器提供电压
ElectricDevice device = new ElectricDevice(adapter);
// 充电
device.charging();
}
}
输出结果:
输出电压:220V
适配器将220V电压转换为5V
电压为5V,正常充电
在这个例子中,VoltageAdapter是一个类适配器,它继承了Voltage220V类(适配者),并实现了Voltage5V接口(目标接口)。通过这个适配器,电器设备能够使用220V的电源进行充电。
6. 更多适配器模式示例
6.1 图形绘制适配器
假设我们有一个绘图应用程序,它可以绘制各种形状。现在我们想要集成一个第三方图形库,但该库的接口与我们的应用程序不兼容。我们可以使用适配器模式来解决这个问题。
首先,定义我们的应用程序使用的目标接口:
// 形状接口(目标接口)
public interface Shape {
void draw(int x, int y, int width, int height);
}
// 矩形实现
public class Rectangle implements Shape {
@Override
public void draw(int x, int y, int width, int height) {
System.out.println("绘制矩形:位置(" + x + "," + y + "),宽度" + width + ",高度" + height);
}
}
// 圆形实现
public class Circle implements Shape {
@Override
public void draw(int x, int y, int width, int height) {
System.out.println("绘制圆形:中心(" + x + "," + y + "),半径" + width / 2);
}
}
然后,定义第三方图形库的类(适配者):
// 第三方图形库中的高级图形类(适配者)
public class LegacyRectangle {
public void drawRectangle(int x1, int y1, int x2, int y2) {
System.out.println("使用第三方库绘制矩形:左上角(" + x1 + "," + y1 + "),右下角(" + x2 + "," + y2 + ")");
}
}
// 第三方图形库中的圆形类(适配者)
public class LegacyCircle {
public void drawCircle(int x, int y, int radius) {
System.out.println("使用第三方库绘制圆形:中心(" + x + "," + y + "),半径" + radius);
}
}
接下来,创建适配器类:
// 矩形适配器
public class RectangleAdapter implements Shape {
private LegacyRectangle legacyRectangle;
public RectangleAdapter() {
this.legacyRectangle = new LegacyRectangle();
}
@Override
public void draw(int x, int y, int width, int height) {
// 将我们的接口参数转换为第三方库需要的参数
int x1 = x;
int y1 = y;
int x2 = x + width;
int y2 = y + height;
// 调用第三方库的方法
legacyRectangle.drawRectangle(x1, y1, x2, y2);
}
}
// 圆形适配器
public class CircleAdapter implements Shape {
private LegacyCircle legacyCircle;
public CircleAdapter() {
this.legacyCircle = new LegacyCircle();
}
@Override
public void draw(int x, int y, int width, int height) {
// 将我们的接口参数转换为第三方库需要的参数
int radius = width / 2;
// 调用第三方库的方法
legacyCircle.drawCircle(x, y, radius);
}
}
最后,客户端代码:
public class ShapeAdapterDemo {
public static void main(String[] args) {
// 使用我们自己的形状类
Shape rectangle = new Rectangle();
rectangle.draw(10, 20, 30, 40);
Shape circle = new Circle();
circle.draw(100, 100, 50, 50);
// 使用适配的第三方库形状类
Shape adaptedRectangle = new RectangleAdapter();
adaptedRectangle.draw(10, 20, 30, 40);
Shape adaptedCircle = new CircleAdapter();
adaptedCircle.draw(100, 100, 50, 50);
}
}
输出结果:
绘制矩形:位置(10,20),宽度30,高度40
绘制圆形:中心(100,100),半径25
使用第三方库绘制矩形:左上角(10,20),右下角(40,60)
使用第三方库绘制圆形:中心(100,100),半径25
在这个例子中,RectangleAdapter和CircleAdapter是适配器类,它们实现了我们的Shape接口(目标接口),并使用第三方库的类(适配者)来实现具体的绘图功能。通过这些适配器,我们的应用程序能够无缝地使用第三方图形库。
6.2 数据转换适配器
假设我们有一个处理JSON数据的系统,但现在需要处理XML格式的数据。我们可以使用适配器模式来解决这个问题。
首先,定义目标接口和适配者:
// 数据处理器接口(目标接口)
public interface DataProcessor {
void processData(String data);
}
// JSON数据处理器
public class JsonProcessor implements DataProcessor {
@Override
public void processData(String data) {
System.out.println("处理JSON数据:" + data);
}
}
// XML处理器(适配者)
public class XmlProcessor {
public void processXml(String xmlData) {
System.out.println("处理XML数据:" + xmlData);
}
}
然后,创建适配器类:
// XML到JSON的适配器
public class XmlToJsonAdapter implements DataProcessor {
private XmlProcessor xmlProcessor;
public XmlToJsonAdapter() {
this.xmlProcessor = new XmlProcessor();
}
@Override
public void processData(String data) {
// 检查数据是否为XML格式
if (data.trim().startsWith("<") && data.trim().endsWith(">")) {
// 如果是XML格式,直接使用XML处理器
xmlProcessor.processXml(data);
} else {
// 如果不是XML格式,假设它是JSON格式,转换后再处理
String xmlData = convertJsonToXml(data);
xmlProcessor.processXml(xmlData);
}
}
// 模拟将JSON转换为XML
private String convertJsonToXml(String jsonData) {
// 这里只是简单模拟转换过程
System.out.println("将JSON转换为XML:" + jsonData);
return "" + jsonData + "";
}
}
最后,客户端代码:
public class DataAdapterDemo {
public static void main(String[] args) {
// 创建JSON处理器
DataProcessor jsonProcessor = new JsonProcessor();
// 处理JSON数据
jsonProcessor.processData("{\"name\":\"张三\",\"age\":30}");
// 创建XML适配器
DataProcessor xmlAdapter = new XmlToJsonAdapter();
// 使用适配器处理XML数据
xmlAdapter.processData("
// 使用适配器处理JSON数据(会先转换为XML)
xmlAdapter.processData("{\"name\":\"王五\",\"age\":28}");
}
}
输出结果:
处理JSON数据:{"name":"张三","age":30}
处理XML数据:
将JSON转换为XML:{"name":"王五","age":28}
处理XML数据:{"name":"王五","age":28}
在这个例子中,XmlToJsonAdapter是一个适配器,它实现了DataProcessor接口(目标接口),并使用XmlProcessor类(适配者)来处理数据。适配器可以接收JSON格式的数据,将其转换为XML格式,然后使用XML处理器进行处理,从而实现了不同数据格式的兼容处理。
7. 适配器模式的实际应用场景
7.1 Java中的适配器模式应用
Java标准库中有很多适配器模式的例子:
7.1.1 Java I/O库中的适配器
在Java I/O库中,InputStreamReader和OutputStreamWriter是适配器类的典型例子:
import java.io.*;
public class IOAdapterExample {
public static void main(String[] args) {
try {
// 创建一个字节输入流
InputStream is = new FileInputStream("input.txt");
// 使用InputStreamReader适配器将字节流转换为字符流
Reader reader = new InputStreamReader(is, "UTF-8");
// 使用BufferedReader增强功能
BufferedReader br = new BufferedReader(reader);
// 读取文本行
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
// 关闭资源
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这个例子中,InputStreamReader是一个适配器,它将字节流(InputStream)适配为字符流(Reader)。
7.1.2 集合框架中的适配器
Java集合框架中的适配器类,如Arrays.asList():
import java.util.*;
public class CollectionAdapterExample {
public static void main(String[] args) {
// 创建一个数组
String[] namesArray = {"张三", "李四", "王五"};
// 使用Arrays.asList()适配器将数组转换为List
List
// 使用List接口的方法
System.out.println("列表大小:" + namesList.size());
System.out.println("第二个元素:" + namesList.get(1));
// 注意:通过Arrays.asList()返回的List不支持add/remove操作
try {
namesList.add("赵六"); // 这会抛出UnsupportedOperationException
} catch (UnsupportedOperationException e) {
System.out.println("不支持添加操作!");
}
// 如果需要可修改的List,可以使用另一个适配器
List
modifiableList.add("赵六");
System.out.println("可修改列表:" + modifiableList);
}
}
在这个例子中,Arrays.asList()是一个适配器方法,它将数组适配为List接口。
7.1.3 Java事件处理中的适配器
在Java Swing中,有各种适配器类,如WindowAdapter、MouseAdapter等:
import java.awt.event.*;
import javax.swing.*;
public class EventAdapterExample {
public static void main(String[] args) {
// 创建一个窗口
JFrame frame = new JFrame("窗口适配器示例");
frame.setSize(300, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 使用WindowAdapter而不是实现WindowListener接口
frame.addWindowListener(new WindowAdapter() {
// 只重写我们关心的方法
@Override
public void windowClosing(WindowEvent e) {
System.out.println("窗口正在关闭");
}
@Override
public void windowOpened(WindowEvent e) {
System.out.println("窗口已打开");
}
});
// 显示窗口
frame.setVisible(true);
}
}
在这个例子中,WindowAdapter是一个适配器类,它实现了WindowListener接口的所有方法,但所有方法都是空的。这样,我们就可以只重写我们关心的方法,而不必实现接口中的所有方法。
7.2 第三方库集成
适配器模式在集成第三方库时非常有用。假设我们有一个日志系统,需要集成不同的日志库:
// 我们的日志接口(目标接口)
public interface Logger {
void debug(String message);
void info(String message);
void warn(String message);
void error(String message);
}
// Log4j适配者
public class Log4jLogger {
private org.apache.log4j.Logger logger;
public Log4jLogger(String name) {
this.logger = org.apache.log4j.Logger.getLogger(name);
}
public void debug(String message) {
logger.debug(message);
}
public void info(String message) {
logger.info(message);
}
public void warn(String message) {
logger.warn(message);
}
public void error(String message) {
logger.error(message);
}
}
// Log4j适配器
public class Log4jAdapter implements Logger {
private Log4jLogger log4jLogger;
public Log4jAdapter(String name) {
this.log4jLogger = new Log4jLogger(name);
}
@Override
public void debug(String message) {
log4jLogger.debug(message);
}
@Override
public void info(String message) {
log4jLogger.info(message);
}
@Override
public void warn(String message) {
log4jLogger.warn(message);
}
@Override
public void error(String message) {
log4jLogger.error(message);
}
}
// 日志工厂
public class LoggerFactory {
public static Logger getLogger(String name) {
// 这里可以根据配置或其他条件选择不同的日志实现
return new Log4jAdapter(name);
}
}
使用示例:
public class LoggingExample {
private static final Logger logger = LoggerFactory.getLogger(LoggingExample.class.getName());
public static void main(String[] args) {
logger.debug("这是一条调试日志");
logger.info("这是一条信息日志");
logger.warn("这是一条警告日志");
logger.error("这是一条错误日志");
}
}
在这个例子中,Log4jAdapter是一个适配器,它实现了我们自己的Logger接口(目标接口),并使用Log4jLogger类(适配者)来实际执行日志记录。这样,我们的应用程序就可以使用统一的日志接口,而不必关心底层使用的是哪种日志库。
8. 适配器模式与其他设计模式的区别
8.1 适配器模式 vs 桥接模式
适配器模式桥接模式关注点在于使不兼容的接口一起工作关注点在于将抽象与实现分离通常在已有系统上使用,解决接口不匹配问题通常在设计新系统时使用,预先考虑灵活性不改变对象的功能,只改变接口既可以改变抽象,也可以改变实现
8.2 适配器模式 vs 装饰器模式
适配器模式装饰器模式改变对象的接口保持对象的接口不变,但增强其功能使不兼容的对象能够协同工作给对象添加职责,不改变其接口通常只在需要解决接口不匹配问题时使用可以动态地添加功能,并且可以层层叠加
8.3 适配器模式 vs 外观模式
适配器模式外观模式使不兼容的接口能够一起工作为子系统提供一个简化的接口通常只适配一个类通常涉及多个类的协作关注点在于接口转换关注点在于简化接口
9. 适配器模式的优缺点
9.1 优点
增加了类的透明性:客户端不需要知道适配者的具体实现,只需使用目标接口提高了类的复用性:通过适配器,可以复用现有的适配者类灵活性和扩展性好:可以在不修改原有代码的情况下,通过增加适配器类来扩展系统功能将目标类和适配者类解耦:客户端代码只依赖于目标接口,而不依赖于适配者类
9.2 缺点
增加了系统的复杂性:引入了新的类和接口,增加了系统的复杂度对于类适配器,由于Java不支持多继承,所以限制较多:一个类适配器只能适配一个适配者类,而不能同时适配多个对于对象适配器,可能需要适配者的子类中的方法:如果适配者的接口很复杂,适配器的实现可能会很困难
10. 何时使用适配器模式?
以下情况适合使用适配器模式:
当你想使用一个已经存在的类,但其接口不符合你的需求时当你想创建一个可以复用的类,该类可以与不相关或不可预见的类协同工作当你想使用一些已经存在的子类,但不可能对每一个子类都进行子类化以匹配接口时(使用对象适配器)当你需要集成第三方库或遗留系统时当你需要在不同的模块之间建立协作关系时
11. 常见问题及解决方案
11.1 问题:如何选择类适配器还是对象适配器?
解决方案:
当你需要适配者类的子类的一些行为,而且适配者类不是最终类时,考虑使用类适配器当你需要适配多个适配者类,或者适配者类是最终类时,应该使用对象适配器大多数情况下,对象适配器更灵活,因为它使用组合而不是继承
// 类适配器示例(继承适配者)
public class ClassAdapter extends Adaptee implements Target {
@Override
public void request() {
specificRequest(); // 调用父类方法
}
}
// 对象适配器示例(组合适配者)
public class ObjectAdapter implements Target {
private Adaptee adaptee;
public ObjectAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest(); // 调用适配者方法
}
}
11.2 问题:如何处理适配者类方法与目标接口方法参数不匹配的情况?
解决方案:在适配器中进行参数转换或适当的处理。
// 目标接口
public interface Target {
void processData(List
}
// 适配者
public class Adaptee {
public void handleData(String[] data) {
// 处理数组数据
}
}
// 适配器
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void processData(List
// 将List转换为数组
String[] arrayData = data.toArray(new String[0]);
// 调用适配者方法
adaptee.handleData(arrayData);
}
}
11.3 问题:如何处理适配者类的方法调用异常?
解决方案:在适配器中捕获和处理异常,转换为目标接口可以处理的异常或返回值。
// 目标接口
public interface Target {
boolean execute();
}
// 适配者
public class Adaptee {
public void doOperation() throws SomeSpecificException {
// 可能抛出特定异常的操作
}
}
// 适配器
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public boolean execute() {
try {
adaptee.doOperation();
return true;
} catch (SomeSpecificException e) {
// 处理异常,可能记录日志
System.err.println("操作失败:" + e.getMessage());
return false;
}
}
}
12. 总结
适配器模式是一种非常实用的设计模式,它允许不兼容的接口一起工作,提高了类的复用性和系统的灵活性。适配器模式主要分为类适配器和对象适配器两种类型,在Java中由于不支持多继承,对象适配器使用更为广泛。
适配器模式在实际开发中有广泛的应用,特别是在集成第三方库、处理遗留系统或者设计需要灵活扩展的模块时。Java标准库中的很多类都应用了适配器模式,如InputStreamReader、OutputStreamWriter、Arrays.asList()以及Swing中的各种事件适配器。
ublic void doOperation() throws SomeSpecificException { // 可能抛出特定异常的操作 } }
// 适配器 public class Adapter implements Target { private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public boolean execute() {
try {
adaptee.doOperation();
return true;
} catch (SomeSpecificException e) {
// 处理异常,可能记录日志
System.err.println("操作失败:" + e.getMessage());
return false;
}
}
}
## 12. 总结
适配器模式是一种非常实用的设计模式,它允许不兼容的接口一起工作,提高了类的复用性和系统的灵活性。适配器模式主要分为类适配器和对象适配器两种类型,在Java中由于不支持多继承,对象适配器使用更为广泛。
适配器模式在实际开发中有广泛的应用,特别是在集成第三方库、处理遗留系统或者设计需要灵活扩展的模块时。Java标准库中的很多类都应用了适配器模式,如`InputStreamReader`、`OutputStreamWriter`、`Arrays.asList()`以及Swing中的各种事件适配器。
理解和掌握适配器模式,对于提高代码复用性、系统灵活性以及处理接口不兼容问题非常有帮助。希望通过本文的讲解和示例,能够帮助读者深入理解适配器模式的原理和应用。