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

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

    当遇到tyint 生成转化bool  类型问题很恶心,记录一下解决方法 一. TinyInt转换规则 JAVA数据类型 和 MYSQL的数据类型转换,要注意tinyInt 类型,且存储长度为1的情况. ...

  2. 识别名人 · Find the Celebrity

    [抄题]: 假设你和 n 个人在一个聚会中(标记为 0 到 n - 1),其中可能存在一个名人.名人的定义是所有其他 n - 1 人都认识他/她,但他/她不知道任何一个.现在你想要找出这个名人是谁或者 ...

  3. ios 进入后台 一段时间在进入前台 动画消失

    http://www.cnblogs.com/YouXianMing/p/3670846.html

  4. swift - 网络请求数据处理 - 协议处理

    1. 在类的模型之中或类的结构体 里面 实现下面方法 /// 添加预约数据源模型 - rootModel class DataModelForAddNewBespeakModel: NSObject ...

  5. 给dede添加栏目图片和栏目描述

    有的时候我们希望调用栏目时把栏目的图片和描述调出来,但dede好像没有提供栏目图片这个功能,而栏目的描述也是给meta:Description使用的,不是很方便.   所以我们需要自已给dede添加图 ...

  6. PHP在win7安装Phalcon框架

    我的环境是64位的 Win7. 安装 Phalcon 也极其简单,只需要下载一个文件(php_phalcon.dll), 要以 phpinfo() 里面“Architecture”属性为准! 下载地址 ...

  7. ILSpy 反编译.NET

    ILSpy 是一个开源的.NET反编译工具,简洁强大易用是它的特征.在绝大多数情况下,它都能很好的完成你对未知程序集内部代码的探索.

  8. asp.net (jquery easy-ui datagrid)通用Excel文件导出(NPOI)

    http://www.cnblogs.com/datacool/archive/2013/03/12/easy-ui_datagrid_export_excel_asp_net.html

  9. 2018秋季C语言基础课第1次作业

    1.翻阅邹欣老师博客关于师生关系博客,并回答下列问题: 1)大学和高中最大的不同是没有人天天看着你,请看大学理想的师生关系是?有何感想? 答:是  Coach / Trainee (健身教练 / 健身 ...

  10. 2018.09.07 codeforces311B. Cats Transport(斜率优化dp)

    传送门 斜率优化dp好题. 对于第i只猫,显然如果管理员想从出发开始刚好接到它,需要在t[i]=h[i]−dist(1,i)" role="presentation" s ...