设计模式之适配器模式(Adapter Pattern)
在正式开始之前,让我们先思考几个问题:
- 如果现有的新项目可以利用旧项目里大量的遗留代码,你打算从头开始完成新项目还是去了解旧项目的模块功能以及接口?
- 如果你了解过遗留代码之后,发现有几个重要的功能模块接口不同(因为它们可能来自多个旧项目),无法直接复用,你打算放弃使用遗留代码吗?
- 如果你不打算放弃(这样做应该是对的,毕竟遗留代码的正确性是经过实践检验的),那么是不是只能去改写剩余的n - 1个接口,甚至改写所有的n个接口?
- 如果不这样做,还有什么简单的方法吗?
一.什么是适配器模式?
首先,我们需要知道适配器是什么东西,嗯,笔记本电脑的电源适配器听说过吧?
它能够把220V的交流电转换为笔记本需要的15V直流电
太神奇了,一个小小的电源适配器解决了家庭用电与笔记本需要的电类型不匹配的问题
发现什么了么?
没错,我们既没有改变家庭用电(把它变成15V直流电),也没有改变笔记本(把它变成220V交流电),但我们确实解决了这个问题
-------
适配器模式——用来实现不同接口转换的设计模式
二.举个例子
假设我们有两个封装好的功能模块,但它们需要的参数不同(虽然参数的实质是同一种对象)
比如,我们的A模块(文本检查模块)是这样的:
package AdapterPattern; /**
* @author ayqy
* 文本检查模块(类似与MSOffice Word中的“拼写和语法检查”)
*/
public class TextCheckModule {
FormatText text; public TextCheckModule(FormatText text){
this.text = text;
} /*
* 省略很多具体Check操作。。
*/
}
A模块入口需要一个FormatText类型的参数,它的定义如下:
package AdapterPattern; /**
* @author ayqy
* 定义格式化文本
*/
public interface FormatText {
String text = null; /**
* @return 文本逻辑行数
*/
public abstract int getLineNumber(); /**
* @param index 行号
* @return 第index行的内容
*/
public abstract String getLine(int index); /*
* 省略其它有用的方法
*/
}
还有B模块(文本显示模块),它是这样的:
package AdapterPattern; /**
* @author ayqy
* 文本显示模块
*/
public class TextPrintModule {
DefaultText text; public TextPrintModule(DefaultText text){
this.text = text;
} /*
* 省略很多显示相关操作
* */
}
B模块入口所需的DefaultText:
package AdapterPattern; /**
* @author ayqy
* 定义默认文本
*/
public interface DefaultText {
String text = null; /**
* @return 文本逻辑行数
*/
public abstract int getLineCount(); /**
* @param index 行号
* @return 第index行的内容
*/
public abstract String getLineContent(int index); /*
* 省略其它有用的方法
*/
}
我们的新项目要求实现一个文字处理程序(像MSOffice Word那样的),我们需要调用A模块来实现文本检查功能,还需要调用B模块来实现文本显示功能
但问题是,两个模块的接口不匹配,导致我们无法直接复用现成的A和B。。
这时我们似乎只有有两个选择:
- 修改FormatText(或者DefaultText),以满足DefaultText(或者FormatText),还需要修改A(或者B)的内部实现
- 定义第三种接口MyText,修改A和B,让它们把MyText作为参数,以求接口的统一
当然,我们可能更倾向与第一种,毕竟所需的修改相对较少,不过即使这样,工作量仍然很大,我们需要打开A的封装,理解其内部实现,并修改方法调用细节
-------
其实我们还有更好的选择——定义一个Adapter,负责FormatText到DefaultText的转换(或者与此相反):
package AdapterPattern; /**
* @author ayqy
* 定义默认文本适配器
*/
public class DefaultTextAdapter implements DefaultText{
FormatText formatText = null;//源对象 /**
* @param text 需要转换的源文本对象
*/
public DefaultTextAdapter(FormatText formatText){
this.formatText = formatText;
} @Override
public int getLineCount() {
int lineNumber; lineNumber = formatText.getLineNumber();
/*
* 在此添加额外的转换处理
* */
return lineNumber;
} @Override
public String getLineContent(int index) {
String line; line = formatText.getLine(index);
/*
* 在此添加额外的转换处理
* */
return line;
}
}
我们的做法其实相当简单:
- 定义Adapter实现目标接口
- 获取并保留源接口对象
- 实现目标接口中的各个方法(在方法体中调用源接口对象的方法并添加额外的处理以实现转换)
适配器做好了,要怎么用呢?不妨实现一个Test类来测试一下:
package AdapterPattern; /**
* @author ayqy
* 测试接口适配器
*/
public class Test implements FormatText{ public static void main(String[] args) {
//创建源接口对象
FormatText text = new Test();
//创建文本检查模块对象
TextCheckModule tcm = new TextCheckModule(text);
/*调用tcm实现文本检查*/ //创建适配器对象,进行源接口对象到目标接口对象的转换
DefaultTextAdapter textAdapter = new DefaultTextAdapter(text);
//用Adapter创建文本显示模块对象
TextPrintModule tpm = new TextPrintModule(textAdapter);
/*调用tcm实现文本显示*/
} /*请忽略下面偷懒的部分。。*/
@Override
public int getLineNumber() {
// TODO Auto-generated method stub
return 0;
} @Override
public String getLine(int index) {
// TODO Auto-generated method stub
return null;
} }
(P.S.原谅我的偷懒行为,谁让FormatText偏偏是个接口呢。。)
当然,Test是不会有运行结果的,但能通过编译就足够说明我们的转换没有问题。。
-------
其实我们忽略了一个很重要的问题,例子中源接口与目标接口的方法都是对应的,换句话说就是:源接口中定义的方法在目标接口中都有类似的方法与之对应
当然,这样的情况是极少的,通常都存在方法不对应的问题(源接口中存在目标接口未定义的方法,或者相反的情况)
这时我们有2个选择:
- 抛出异常,但应该在注释或者文档作出详细说明,就像这样:
throw new UnsupportedOperationException();//源接口不支持此操作
- 完成一个空的实现,比如,return false,0,null等等
具体选择哪一种,取决于具体情景,各有各的好处,不能一概而论
三.另一种适配器实现方式
例子中我们采用了“持有源接口对象,实现目标接口”的方式来实现适配器,其实还存在另一种方式——多继承(或者实现多个接口)
如果一个Adapter类既实现了A接口又实现了B接口,那么,毫无疑问,Adapter对象既属于A类型又属于B类型(多继承的原理类似。。)
虽然Java不支持多继承,但在支持多继承的语言环境下我们应当想到这样的实现方式,再视具体情况决定是否采用多继承来实现Adapter
四.总结
当我们手里同时握着一个两孔插头和一个三孔插口时,总是习惯把插头芯拧成八字形的。为什么不去买一个适配器呢?
- 既不需要破坏插头,也不需要破坏插口(有时代码修改确实是破坏性的,我们避免了修改也就避免了破坏)
- 更关键的是:我们可以把买来的适配器借给朋友用(可复用)
设计模式之适配器模式(Adapter Pattern)的更多相关文章
- 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern)
原文:乐在其中设计模式(C#) - 适配器模式(Adapter Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern) 作者:webabc ...
- 怎样让孩子爱上设计模式 —— 7.适配器模式(Adapter Pattern)
怎样让孩子爱上设计模式 -- 7.适配器模式(Adapter Pattern) 标签: 设计模式初涉 概念相关 定义: 适配器模式把一个类的接口变换成client所期待的还有一种接口,从而 使原本因接 ...
- 二十四种设计模式:适配器模式(Adapter Pattern)
适配器模式(Adapter Pattern) 介绍将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作.示例有一个Message实体类 ...
- 【设计模式】适配器模式 Adapter Pattern
适配器模式在软件开发界使用及其广泛,在工业界,现实中也是屡见不鲜.比如手机充电器,笔记本充电器,广播接收器,电视接收器等等.都是适配器. 适配器主要作用是让本来不兼容的两个事物兼容和谐的一起工作.比如 ...
- Java设计模式之适配器模式(Adapter Pattern)
Adapter Pattern的作用是在不改变功能的前提下转换接口.Adapter分为两类,一类是Object Adapter, 还有一类是Class Adapter.因为Class Adapter的 ...
- 夜话JAVA设计模式之适配器模式(adapter pattern)
适配器模式:将一个类的接口,转换成客户期望的另一个接口,让不兼容的接口变成兼容. 1.类适配器模式:通过多重继承来实现适配器功能.多重继承就是先继承要转换的实现类,再实现被转换的接口. 2.对象适配器 ...
- 【UE4 设计模式】适配器模式 Adapter Pattern
概述 描述 将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper). 套路 Target(目标抽象类) 目标抽象类定义了客户所需要的接口,可 ...
- 设计模式系列之适配器模式(Adapter Pattern)——不兼容结构的协调
模式概述 模式定义 模式结构图 模式伪代码 类适配器,双向适配器,缺省适配器 类适配器 双向适配器 缺省适配器 模式应用 模式在JDK中的应用 模式在开源项目中的应用 模式总结 主要优点 主要缺点 适 ...
- 设计模式 - 适配器模式(adapter pattern) 具体解释
适配器模式(adapter pattern) 详细解释 本文地址: http://blog.csdn.net/caroline_wendy 适配器模式(adapter pattern): 将一个类的接 ...
- 设计模式 - 适配器模式(adapter pattern) 枚举器和迭代器 具体解释
适配器模式(adapter pattern) 枚举器和迭代器 具体解释 本文地址: http://blog.csdn.net/caroline_wendy 參考适配器模式(adapter patter ...
随机推荐
- 搜索旋转排序数组 II
跟进“搜索旋转排序数组”,假如有重复元素又将如何? 一句话思路:不能二分,因为复杂度是n eg全是0,找一个1 class Solution { public boolean search(int[] ...
- (重要)LRU cache
[抄题]: [思维问题]: 需要从任何位置访问某数字有没有(重要 ),返回其位置(不重要),所以用hashmap. 需要从任何位置删除,用linkedlist.最终二者结合,用linked hashm ...
- [leetcode]283. Move Zeroes移零
Given an array nums, write a function to move all 0's to the end of it while maintaining the relativ ...
- CComSafeArray
https://blog.csdn.net/tangaowen/article/details/6554640
- rsync 目录以 / 结尾 轻松同步数据
命令:#rsync –avz foo/ bar/ 意义:将 foo 目录之下的所有内容,同步到 bar 目录之下.如果不以斜杠结尾,意义就不同了.
- Storm 系列(三)Storm 集群部署和配置
Storm 系列(二)Storm 集群部署和配置 本章中主要介绍了 Storm 的部署过程以及相关的配置信息.通过本章内容,帮助读者从零开始搭建一个 Storm 集群. 一.Storm 的依赖组件 1 ...
- IE8以下支持css3 border-radius渲染方法
这两天在做个集团网站,web前端妹子技术水平不咋样,写个web和wap 真够费劲的,对之前流行的H5和css3 响应式看来不太会用,扔给我一个半成品~~·非说各种canvas和border-radiu ...
- 修改python ide的主题,颜色
1.找到这个名叫config-highlight.cfg文件后接下来就需要编辑它了 2. 贴上: [Obsidian] definition-foreground = #678CB1 error-fo ...
- Cygwin工具的简单使用
简介 从使用角度来看:Cygwin就是一个windows软件,该软件就是在windows上仿真linux操作系统.简言之,cygwin是一个在windows平台上运行的 linux模拟环境,使用一个D ...
- DB2 runstats、reorgchk、reorg 命令【转载】
1.runstats runsats可以搜集表的信息,也可以搜集索引信息.作为runstats本身没有优化的功能,但是它更新了统计信息以后,可以让DB2优化器使用最新的统计信息来进行优化,这样优化的效 ...