在正式开始之前,让我们先思考几个问题:

  • 如果现有的新项目可以利用旧项目里大量的遗留代码,你打算从头开始完成新项目还是去了解旧项目的模块功能以及接口?
  • 如果你了解过遗留代码之后,发现有几个重要的功能模块接口不同(因为它们可能来自多个旧项目),无法直接复用,你打算放弃使用遗留代码吗?
  • 如果你不打算放弃(这样做应该是对的,毕竟遗留代码的正确性是经过实践检验的),那么是不是只能去改写剩余的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。。

这时我们似乎只有有两个选择:

  1. 修改FormatText(或者DefaultText),以满足DefaultText(或者FormatText),还需要修改A(或者B)的内部实现
  2. 定义第三种接口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;
}
}

我们的做法其实相当简单:

  1. 定义Adapter实现目标接口
  2. 获取并保留源接口对象
  3. 实现目标接口中的各个方法(在方法体中调用源接口对象的方法并添加额外的处理以实现转换)

适配器做好了,要怎么用呢?不妨实现一个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)的更多相关文章

  1. 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern)

    原文:乐在其中设计模式(C#) - 适配器模式(Adapter Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 适配器模式(Adapter Pattern) 作者:webabc ...

  2. 怎样让孩子爱上设计模式 —— 7.适配器模式(Adapter Pattern)

    怎样让孩子爱上设计模式 -- 7.适配器模式(Adapter Pattern) 标签: 设计模式初涉 概念相关 定义: 适配器模式把一个类的接口变换成client所期待的还有一种接口,从而 使原本因接 ...

  3. 二十四种设计模式:适配器模式(Adapter Pattern)

    适配器模式(Adapter Pattern) 介绍将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作.示例有一个Message实体类 ...

  4. 【设计模式】适配器模式 Adapter Pattern

    适配器模式在软件开发界使用及其广泛,在工业界,现实中也是屡见不鲜.比如手机充电器,笔记本充电器,广播接收器,电视接收器等等.都是适配器. 适配器主要作用是让本来不兼容的两个事物兼容和谐的一起工作.比如 ...

  5. Java设计模式之适配器模式(Adapter Pattern)

    Adapter Pattern的作用是在不改变功能的前提下转换接口.Adapter分为两类,一类是Object Adapter, 还有一类是Class Adapter.因为Class Adapter的 ...

  6. 夜话JAVA设计模式之适配器模式(adapter pattern)

    适配器模式:将一个类的接口,转换成客户期望的另一个接口,让不兼容的接口变成兼容. 1.类适配器模式:通过多重继承来实现适配器功能.多重继承就是先继承要转换的实现类,再实现被转换的接口. 2.对象适配器 ...

  7. 【UE4 设计模式】适配器模式 Adapter Pattern

    概述 描述 将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper). 套路 Target(目标抽象类) 目标抽象类定义了客户所需要的接口,可 ...

  8. 设计模式系列之适配器模式(Adapter Pattern)——不兼容结构的协调

    模式概述 模式定义 模式结构图 模式伪代码 类适配器,双向适配器,缺省适配器 类适配器 双向适配器 缺省适配器 模式应用 模式在JDK中的应用 模式在开源项目中的应用 模式总结 主要优点 主要缺点 适 ...

  9. 设计模式 - 适配器模式(adapter pattern) 具体解释

    适配器模式(adapter pattern) 详细解释 本文地址: http://blog.csdn.net/caroline_wendy 适配器模式(adapter pattern): 将一个类的接 ...

  10. 设计模式 - 适配器模式(adapter pattern) 枚举器和迭代器 具体解释

    适配器模式(adapter pattern) 枚举器和迭代器 具体解释 本文地址: http://blog.csdn.net/caroline_wendy 參考适配器模式(adapter patter ...

随机推荐

  1. cdoj841-休生伤杜景死惊开 (逆序数变形)【线段树 树状数组】

    http://acm.uestc.edu.cn/#/problem/show/841 休生伤杜景死惊开 Time Limit: 3000/1000MS (Java/Others)     Memory ...

  2. Poor Warehouse Keeper

    Poor Warehouse Keeper http://acm.hdu.edu.cn/showproblem.php?pid=4803 Jenny is a warehouse keeper. He ...

  3. python之多并发socket

    先看socket多并发的服务端的代码,这里是用多线程实现的多并发socketserver import socketserver # socketserver有四个基本的类,后两个不常用,这4个类处理 ...

  4. Loitor_产品(一)

    源码:https://github.com/loitor-vis/vi_sensor_sdk 注意:以下要一直在管理员权限 1.C++ 示例程序的编译步骤 先确认你的系统已经成功安装了OpenCV. ...

  5. php的opcode缓存原理

    opcode是什么? 它是一种PHP脚本编译后的中间语言,类似java的字节码.   PHP代码执行(Zend引擎)的步骤如下: 1.Scanning(Lexing) ,将PHP代码转换为语言片段(T ...

  6. 匹配数字、字母和?%&=-_这几个符号的正则表达式

    /^[\w\?%&=\-_]+$/ 说明:(1) \w 代表 0-9a-zA-Z 即数字.字母 (2) \?%&=\-_ 匹配?%&=-_,而正则中?代表0个或1个,因为是特殊 ...

  7. 调用数据库-corina

    pym = mysql(host='#####', port=3306, user='######', password='########', database='algorithm') #查询数据 ...

  8. 在Mockplus中,如何做鼠标悬停时菜单下拉的效果?

    了解Mockplus的用户会知道,该原型工具目前并不直接支持鼠标悬停功能.但我经过尝试,发现想用它实现一个鼠标悬停事件并不是什么难事,比如网页设计中很常见的鼠标悬停时菜单下拉的效果,只要换个思路,利用 ...

  9. kafka系列 -- 基础概念

    kafka是一个分布式的.分区化.可复制提交的发布订阅消息系统 传统的消息传递方法包括两种: 排队:在队列中,一组用户可以从服务器中读取消息,每条消息都发送给其中一个人. 发布-订阅:在这个模型中,消 ...

  10. Oracle 输出树形结构

    Oracle 输出树形结构 树形结构,根 select connect_by_root(cat.parentid) root,cat.id,cat.parentid,cat.name,cat.code ...