设计模式之适配器模式(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。。

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

  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

四.总结

当我们手里同时握着一个两孔插头和一个三孔插口时,总是习惯把插头芯拧成八字形的。为什么不去买一个适配器呢?

  • 既不需要破坏插头,也不需要破坏插口(有时代码修改确实是破坏性的,我们避免了修改也就避免了破坏)
  • 更关键的是:我们可以把买来的适配器借给朋友用(可复用)
时间: 2024-07-30 08:30:09

设计模式之适配器模式(Adapter Pattern)的相关文章

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

适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作. 1. 解决的问题 即Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作. 2. 模式中的角色 2.1 目标接口(Target):客户所期待的接口.目标可以是具体的或抽象的类,也可以是接口. 2.2 需要适配的类(Adaptee):需要适配的类或适配者类. 2.3 适配器(Adapter):通过包装一个需要适配的对象,把

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

Adapter Pattern的作用是在不改变功能的前提下转换接口.Adapter分为两类,一类是Object Adapter, 另一类是Class Adapter.由于Class Adapter的实现需要用到多继承,而Java不支持多继承,所以这里只关注Object Adapter. 在JDK1.5之前是没有 java.util.Iterator 接口的,java.util.Enumeration 接口起着 Iterator 的作用.那么如果我们需要维护一些年代比较久远的代码,可能就会面临着没

如何让孩子爱上设计模式 —— 7.适配器模式(Adapter Pattern)

如何让孩子爱上设计模式 -- 7.适配器模式(Adapter Pattern) 概念相关 定义: 适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而 使原本因接口不匹配而无法在一起工作的两个类能够在一起工作. 简单点说: 两个彼此间没有太大关联的类,想进行交互完成某些事情,如果 直接去修改各自的接口,就显得有些繁琐了,可以加个中间类, 用它来协调两类之间的关系,完成相关业务.这种玩法就叫适配器模式! 两种适配器模式: 根据适配器类与适配者类的关系不同,适配器模式可分为 类适配器 和 对

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

适配器模式(Adapter Pattern) 介绍将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. 示例有一个Message实体类,某个类对它的操作有Insert()和Get()方法.现在需要把这个类转到另一个接口,分别对应Add()和Select()方法. MessageModel using System; using System.Collections.Generic; using System.Text; name

设计模式 - 适配器模式(adapter pattern) 枚举器和迭代器 详解

适配器模式(adapter pattern) 枚举器和迭代器 详解 本文地址: http://blog.csdn.net/caroline_wendy 参考适配器模式(adapter pattern): http://blog.csdn.net/caroline_wendy/article/category/2281679 Java早期版本的枚举器(Enumeration)和现在的迭代器(Iterator) 可以使用适配器(adapter)进行转换. 适配器(adapter)代码: /** *

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

适配器模式(adapter pattern) 枚举器和迭代器 具体解释 本文地址: http://blog.csdn.net/caroline_wendy 參考适配器模式(adapter pattern): http://blog.csdn.net/caroline_wendy/article/category/2281679 Java早期版本号的枚举器(Enumeration)和如今的迭代器(Iterator) 能够使用适配器(adapter)进行转换. 适配器(adapter)代码: /**

适配器模式--Adapter Pattern

模拟场景:很多人都喜欢看NBA吧,姚明进驻NBA,打开了中国的市场.虽然后面姚明在NBA打得还不错,但是在刚进入NBA篮坛的时候,并不是那么顺利的.语言交流就是一个最大的问题.刚开始打球期间,教练及队员的战术部署姚明都无法理解,所以他需要这么一个翻译者,将教练及队员的意思转达给姚明,这样才可以进行合作. 现在进行场景的模拟,先不考虑那么多.假如姚明一开始进入NBA的时候就已经会英语,可以进行交流了.那么这个时候教练就可以进行战术的部署了. 转换成类,所有的队员都要服从教练的战术要求,假设现在教练

设计模式之适配器模式(Adapter)摘录

23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象.创建型模式有两个不断出现的主旋律.第一,它们都将关于该系统使用哪些具体的类的信息封装起来.第二,它们隐藏了这些类的实例是如何被创建和放在一起的.整个系统关于这些对象所知道的是由抽象类所定义的接口.因此,创建型模式在什么被创建,谁创建它,它是怎样被创建的,以

用最简单的例子理解适配器模式(Adapter Pattern)

中国足球的水平虽然不高,但实际上,在每个城市会有一批足球爱好者,他们踢球.看球.懂球.有这样的2个足球爱好者,一个是左脚选手,另一个是右脚选手. public class PlayWithLeft { public void Play() { Console.WriteLine("我是左脚选手"); } } public class PlayWitRight { public void Play() { Console.WriteLine("我是右脚选手"); }

php适配器模式(adapter pattern)

下午陪家人和小孩,晚上练起来. <?php /* The adapter pattern allows the interface of an existing class to be used from another interface, basically, helping two incompatible interfaces to work together by converting the interface of one class into an interface expec