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

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

    参考网址:http://www.jianshu.com/p/b754709135fb http://www.jianshu.com/p/443a5b8f3894   注意:封装静态库时要注意的地方: ...

  2. python之面向对象之继承

    #写一个类SchoolMember class SchoolMember(object): member_num = 0 def __init__(self,name,age,sex): self.n ...

  3. IIS 访问不了,IIS有问题,IIS右击浏览没反应

    查看是否是下面的问题 web服务扩展中,下面三项设置为 “允许”  CGI扩展  ISAPI扩展  Active Server Pages

  4. IaaS、PaaS、SaaS、CaaS、MaaS五者的区别

    云计算构架图          很明显,这五者之间主要的区别在于第一个单词,而aaS都是as-a-service(即服务)的意思,这五个模式都是近年来兴起的,且这五者都是云计算的落地产品,所以我们先来 ...

  5. 如何定义一个高逼格的原生JS插件

    插件的需求 我们写代码,并不是所有的业务或者逻辑代码都要抽出来复用.首先,我们得看一下是否需要将一部分经常重复的代码抽象出来,写到一个单独的文件中为以后再次使用.再看一下我们的业务逻辑是否可以为团队服 ...

  6. css样式: 宽高按一定比例进行自适应

    纯 CSS 实现高度与宽度成比例的效果 最近在做一个产品列表页面,布局如右图所示.页面中有若干个 item,其中每个 item 都向左浮动,并包含在自适应浏览器窗口宽度的父元素中. item 元素的 ...

  7. 02 请求库之 selenium模块

      selenium模块   一 介绍 selenium最初是一个自动化测试工具,而爬虫中使用它主要是为了解决requests无法直接执行JavaScript代码的问题 selenium本质是通过驱动 ...

  8. python使用wmi模块获取windows下的系统信息监控系统-乾颐堂

    Python用WMI模块获取Windows系统的硬件信息:硬盘分区.使用情况,内存大小,CPU型号,当前运行的进程,自启动程序及位置,系统的版本等信息. 本文实例讲述了python使用wmi模块获取w ...

  9. 另辟蹊径:vue单页面,多路由,前进刷新,后退不刷新

    目的:vue-cli构建的vue单页面应用,某些特定的页面,实现前进刷新,后退不刷新,类似app般的用户体验.注: 此处的刷新特指当进入此页面时,触发ajax请求,向服务器获取数据.不刷新特指当进入此 ...

  10. 2017年UX设计流行的六大趋势

    UX设计在接下来的2017年会有怎样的发展趋势呢?让我们一起回顾去年用户体验设计领域中的变化,来展望新一年用户体验设计的发展趋势吧. 1. 原型制作的爆炸性增长   随着用户体验设计师和用户界面设计师 ...