设计模式速记-适配器模式
适配器模式
适配器模式定义:
将一个类的接口转换成客户希望的另外一个接口。适配器 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式的一些例子:
- 电脑显示器接口一般有VGA、HDMI、DVI几种,笔记本接口一般为HDMI接口,连接线即为适配器。
- 音乐播放器可播放格式有MP3、MP4等,需要对音乐文件格式进行适配后才能使用。
- 代码编辑器代码高亮功能,需要适配代码语言,如Java、Python,适配后方能关键字高亮显示。
- Excel 可以查看xls、xlsx、csv文件
故适配器模式主要解决的问题是,假如两个不匹配的接口需要做通信,则通过适配器做中间转换再进行通信。
适配器模式常用有两种:
-
对象适配器模式
对象适配器模式通过在适配器中持有一个类的对象实例进行适配
-
类适配器模式
类适配器模式通过继承一个类后进行适配
下面分别对对象适配器模式 和类适配器模式举例讨论(示例代码github地址)
对象适配器模式
对象适配器模式通过在适配器中持有一个类的对象实例进行适配功能,下面通过一个例子解释对象适配器模式的工作过程。
1. 示例描述
假如我们有一个音乐播放器,还有一些MP3和MP4文件,现在需要用适配器模式来对文件进行适配,以满足可以播放所有文件格式。
2. 设计思路
对象适配器模式在于持有类的对象,所以可以参考如下步骤进行设计:
- 建立MusicFile类表示音乐文件
- 建立接口MusicPlayer,内置方法play,参数类型为MusicFile,表示播放文件。
- 播放器程序抽象接口MusicPlayerProgram,内置方法play,其参数类型为MusicFile,表示播放音乐文件。另外建立MP3播放器程序和MP4播放器程序具体实现类,实现播放器接口MusicPlayerProgram。
- 建立MusicAdapter适配器类,并分别实现MusicPlayer接口,重写其父类play方法,并分别持有(3)中的MP3PlayerProgram和MP4PlayerProgram实例对象,方法play中则分别调用各自播放程序的play方法即可。
- 测试程序
3. 示例代码
-
MusicFile音乐文件及MusicPlayer音乐播放器接口
/** * @Description 音乐文件 */ @Data @AllArgsConstructor public class MusicFile { /** * 文件类型 */ private String type; /** * 文件名 */ private String name; } /** * @Description 音乐播放器 */ public interface MusicPlayer { /** * 播放音乐文件 * @param musicFile 音乐文件 */ void play(MusicFile musicFile); }
-
MusicPlayerProgram播放程序接口及MP3、MP4各自的播放程序实现类。
/** * @Description 音乐播放程序抽象接口 */ public interface MusicPlayerProgram { /** * 开始播放程序 * @param musicFile 音乐文件 */ void programStart(MusicFile musicFile); } /** * @Description MP3播放程序 */ public class MP3PlayerProgram implements MusicPlayerProgram { @Override public void programStart(MusicFile musicFile) { String option = String.format("MP3播放程序无法播放该音乐文件,文件类型%s,文件名%s",musicFile.getType(),musicFile.getName()); if("mp3".equals(musicFile.getType())){ option = String.format("音乐%s正在播放---MP3播放程序",musicFile.getName()); } System.out.println(option); } } /** * @Description MP4音乐播放程序 */ public class MP4PlayerProgram implements MusicPlayerProgram { @Override public void programStart(MusicFile musicFile) { String option = String.format("MP4播放程序无法播放该音乐文件,文件类型%s,文件名%s",musicFile.getType(),musicFile.getName()); if("mp4".equals(musicFile.getType())){ option = String.format("音乐%s正在播放---MP4播放程序",musicFile.getName()); } System.out.println(option); } }
-
音乐播放适配器
/** * @Description 适配器类 */ public class MusicAdapter implements MusicPlayer { /** * 持有播放程序实例对象 */ private MusicPlayerProgram playerProgram ; public MusicAdapter(MusicPlayerProgram playerProgram){ this.playerProgram = playerProgram; } @Override public void play(MusicFile musicFile) { playerProgram.programStart(musicFile); } }
-
测试代码
/** * @Description 对象适配器模式测试 */ public class ObjAdapterPatternTest { @Test public void test(){ MusicFile mp3Music = new MusicFile("mp3","稻香"); MusicFile mp4Music = new MusicFile("mp4","七里香"); /** * 正确播放案例 */ MusicPlayer mp3Player = new MusicAdapter(new MP3PlayerProgram()); mp3Player.play(mp3Music); MusicPlayer mp4Player = new MusicAdapter(new MP4PlayerProgram()); mp4Player.play(mp4Music); /** * 错误播放案例 */ mp4Player.play(mp3Music); } }
-
测试结果
音乐稻香正在播放—MP3播放程序
音乐七里香正在播放—MP4播放程序
MP4播放程序无法播放该音乐文件,文件类型mp3,文件名稻香
类适配器模式
类适配器模式通过继承一个类后进行适配,下面通过一个例子看一下类适配器模式是如何工作的
1. 示例描述
假如我们有一些文件,分别是csv、xls、xlsx类型的,使用类适配器模式模拟Excel工作过程。
2. 设计思路
任务目的在于如何使用一个软件(Excel)来同时适配三种文件,那么可以按照以下步骤进行
- 定义 ExcelCore 接口表示Excel内核,内置方法show来打开并显示文件,参数为自定义文件类型(自定义文件类型为ExcelFile)
- 定义抽象接口ExcelFileReader,表示Excel文件读取器,内置getContent方法读取文件数据。
- 分别定义CSVReader、XLSReader和XLSXReader三种具体类,并实现ExcelFileReader接口。
- 问题在于如何在ExcelCore中适配三种具体类,定义CSVAdapter类并实现(1)中Excel接口再继承CSVReader,重写show方法,在show方法中调用getContent方法,完成CSV文件适配。XLS和XLSX文件同理。
- 测试
3. 示例代码
-
内核接口ExcelCore和自定义文件类型
/** * @Description Excel可读取的文件 */ @AllArgsConstructor @Data public class ExcelFile { /** * 文件类型 */ private String type; /** * 文件内容 */ private String content; } /** * @Description Excel 内核 */ public interface ExcelCore { /** * 读取文件 * * @param excelFile 文件 * @return 文件内容 * @exception RuntimeException 文件类型不匹配时抛出运行时异常 */ String show(ExcelFile excelFile); }
-
抽象接口ExcelFileReader及三种文件读取器
/** * @Description 文件读取器抽象接口 */ public interface ExcelFileReader { /** * 读取文件内容 * @param file 文件 * @return 文件内容 */ String getFileContent(ExcelFile file); } /** * @Description csv文件读取器 */ public class CSVFileReader implements ExcelFileReader{ @Override public String getFileContent(ExcelFile file) { if("csv".equals(file.getType())){ return file.getContent(); } throw new RuntimeException("文件类型不匹配,CSV文件读取器不可读取"); } } /** * @Description XLS文件读取器 */ public class XLSFileReader implements ExcelFileReader { @Override public String getFileContent(ExcelFile file) { if("xls".equals(file.getType())){ return file.getContent(); } throw new RuntimeException("文件类型不匹配,xls文件读取器不可读取"); } } /** * @Description xlsx文件读取器 */ public class XLSXFileReader implements ExcelFileReader { @Override public String getFileContent(ExcelFile file) { if("xlsx".equals(file.getType())){ return file.getContent(); } throw new RuntimeException("文件类型不匹配,XLSX文件读取器不可读取"); } }
-
适配器
/** * @Description CSV文件读取适配器 */ public class CSVReaderAdapter extends CSVFileReader implements ExcelCore{ /** * 使用父类方法 CSVFileReader.getFileContent() 获取文件内容 * 使用时创建该适配器,定义类型为ExcelCore , 例如: * ExcelCore core = new CsvReaderAdapter(); * * @param excelFile 文件 * @return 文件内容 */ @Override public String show(ExcelFile excelFile) { return super.getFileContent(excelFile); } } // ########## // ########## XLS文件适配器和XLSX文件适配器和上述CSV适配器类似,这里同样给出实现代码 // ########## /** * @Description XLSX 文件读取适配器 */ public class XLSXReaderAdapter extends XLSXFileReader implements ExcelCore{ @Override public String show(ExcelFile excelFile) { return super.getFileContent(excelFile); } } /** * @Description XLS文件读取适配器 */ public class XLSReaderAdapter extends XLSFileReader implements ExcelCore{ @Override public String show(ExcelFile excelFile) { return super.getFileContent(excelFile); } }
-
测试代码
/** * @Description 类适配器模式测试 */ public class ClaAdapterPatternTest { @Test public void test(){ ExcelFile csvFile = new ExcelFile("csv" , "csv 文件内容在此"); ExcelFile xlsFile = new ExcelFile("xls" , "xls 文件内容在此"); ExcelFile xlsxFile = new ExcelFile("xlsx" , "xlsx 文件内容在此"); ExcelCore csvCore = new CSVReaderAdapter(); ExcelCore xlsCore = new XLSReaderAdapter(); ExcelCore xlsxCore = new XLSXReaderAdapter(); /** * 正确读取文件数据 */ System.out.println(csvCore.show(csvFile));; System.out.println(xlsCore.show(xlsFile));; System.out.println(xlsxCore.show(xlsxFile));; /** * 错误读取文件时抛出异常,仅写一个,其他类似 */ try { csvCore.show(xlsFile); }catch (RuntimeException e){ System.out.println(e.getMessage()); } } }
-
测试结果
csv 文件内容在此
xls 文件内容在此
xlsx 文件内容在此
文件类型不匹配,CSV文件读取器不可读取
总结
通过上面两个例子可以看出,如果源接口A想调用目标对象B,但是接口不匹配不能直接调用,这时候需要构造适配器来进行间接调用;而构造适配器的方法可以分成两种:
- 类适配器模式:通过实现源接口A并且继承目标类B,在源接口A的抽象方法里调用B的方法(此时B是父类,可以直接使用)
- 对象适配器模式:实现源接口A,持有目标类B的一个实例对象,此时在源接口A的抽象方法里直接调用B的实例对象,这样也可以实现适配的功能。
也就是说如果接口A的子类想要调用B的某方法,那么可以通过持有B或者实现A继承B类的方法,来实现适配器模式进而完成需求目标。