适配器模式
概述
这一章我们通过对适配器模式 (Adapter Pattern) 的讲解来继续我们对设计模式的学习。 Adapter 模式是一个经常使用的模式,正如你将会看到的那样,她常和其他模式一起使用。
在这一章里:
l 我们将学习到什么是 Adapter 模式,她有什么用,以及怎么用。
l 在章末的时候将会有 Adapter 模式的主要功能的总结。
l 通过 Adapter 模式演示多态的使用。
l 演示用 UML 图来表示不同层次的细节描述。
l 我在实践过程中的一些对 Adapter 模式的观察和思考,还会将 Adapter 模式和 Facade 模式进行对比。
l
Adapter 模式简介
在 GoF 的经典著作《设计模式》中指出: Adapter 模式的目的就是将类的接口转换成客户对象需要的接口, Adapter 模式使原本不相容的接口变的相容。也就是说我们有一个可以满足我们需要的对象,但是她的接口却不是象我们所期望的那样,而我们现在所需要的就是创建一个新的接口,让原本的接口能够满足我们的需要。
Adapter 模式详解
要理解 Adapter 模式最简单的方式就是看看她实际应用的一个例子。假设有如下的要求:
l 创建一些代表着 点、线、方形 的类,并包含“ display ” ( 显示 ) 这个方法 / 行为。
l 客户对象不需要知道正在使用的对象是 点、线 还是方形,只需要知道是一种形状 (shape) 就可以了。
换句话说,我需要把这些确切的点啦、线啦、方形啦等等用更概念性的语言类概括起来,也就是 显示一个形状。
当我们在完成这个例子的时候,我们可能会遇到这样的情况:
l 我们想用一段子程序或者说一些别人已经写好了的代码,因为这些刚好能够完成我们所需要的某些功能。
l 但是,我们又不能把这些程序啦、代码啦直接放到我们自己的程序里面去,
l 因为这些程序、代码的接口的调用方式和我们期望的又不一样。
也就是说虽然当前的系统里有 点、线、方形等等这些对象,但是,我的程序仅仅需要把他们统统当成是一种形状这就足够了。具体是点、线、还是方形,我不感兴趣。
l 这可以使得我的程序可以用一种方式来处理所有的“形状”,这样,就不必再去考虑这个 点 和 线 他们有什么不同之类的问题。
l 这种方式还可以让我以后可以在不改变现有的使用方式的情况下添加进新的具体形状。
这样,我们就需要用到 多态 (polymorphism) 。就是说,我的系统里会有各种各样的具体形状,但是我可以用一个通用的方式来操纵这些所有的形状。
这样,客户对象就可以简单地告诉这些 点、线 让他们在屏幕上显示出来,或者从屏幕上消失。这些 点、线 他们自己会知道自己该怎么样的去显示或不显示,不需要客户对象去考虑这个问题。
要达到这点,我们先创建一个 Shape 类,然后从她派生 (derive) 出一些代表着 点、线 等具体形状的类。请参看图 7-2 。
图 7-2 Point 、 Lines 、和 Square 都是一种具体的 Shape 。
首先,我们必须给 Shape 类确定一些她将会提供的具体行为。我们可以通过在 Shape 里面定义一些抽象接口然后在 Point Line Square 等类中具体实现这些接口的方式来达到目的。
我们给 Shape 提供以下的一些方法:
l 指定 Shape 在屏幕上的位置
l 获取 Shape 在屏幕上的位置
l 让 Shape 在屏幕上显示
l 对 Shape 进行填充
l 给 Shape 指定一个颜色
l 让 Shape 从屏幕上消失
假设现在我们又被要求要去实现一个 圆 一个新的形状 ( 要知道,客户对我们的要求总是在不停的变来变去的 ) 。这样,我们就又要创建一个新的 Circle 类了。我们让 Circle 也继承 Shape ,这样我们就也可以对 Circle 使用多态,让他向上转型 (upcasting) 到 Shape 了。 Circle 类是建立好了,但是我们现在就需要对她的 display() fill() undisplay() 等等这些方法编码了。这好像是一个痛苦的事情。
幸运的是,几经搜索 ( 就像优秀的编码人员经常做的那样 ) ,我终于找到一个替代品 (alternative) 了。我发现 Jill 同学已经写了一个名为 XXCircle 的类来处理和圆相关的东西 ( 如图 7-4) 。然而,很不幸的是, Jill 并没有象我们这样定义 XXCircle 里的方法。她定义成了: displayIt() 、 fillIt() 、 undisplayIt() 。
图 7-4 Jill 的 XXCircle 类
由于我们还要用 Shape 的多态,所以我们不可能直接用 Jill 的 XXCircle 类。至于为什么,那是因为:
l 不同的方法名称和参数表 XXCircle 里的方法名称和参数表和我们的 Shape 类的完全不一样。
l 不是 Shape 的派生类 ( 子类 ) 我们不仅仅要求方法名称参数表要一样还要求继承 Shape 类。
也许我们可以试着让 Jill 同学按照我们的需要来重写她的 XXCircle ,改变方法名称和参数表并且继承 Shape 类。但是,这肯定是不现实的。因为,如果重写了 XXCircle ,那么 Jill 以前用到 XXCircle 的地方也都要完全重写,而且,修改正在使用中的代码有可能会产生难以预知的副作用。
看起来我们就要达到目的了,然而却不能用,而我又懒得自己老老实实去写,怎么办呢?
( 不是有句话说:“既然不能改变它,就去适应它么”?……译著 )
与其改变 XXCircle ,倒不如适配它。
我可以从 Shape 派生一个类,这样,这个类就可以实现 Shape 的所有接口而避免重写 XXCircle 。具体情况请看图 7-5 。
图 7-5 Adapter 模式: Circle 包含 XXCircle
l Circle 类继承自 Shape 。
l Circle 里包含了 XXCircle 。
l Circle 把所有传递给 Circle 类型对象的请求全部传递给 XXCircle 类的对象。
图 7-5 中 Circle 和 XXCircle 中连线末端的菱形表明 Circle 包含得有 XXCircle 。当 Circle 类的对象初始化的时候,该对象会同时初始化一个相关的 XXCircle 类的对象。如果 XXCircle 对象包含有 Circle 所需要的所有功能,那么对 Circle 对象的任何操作都将被传递到 XXCircle 上去执行完成 (XXCircle 不包含所有 Circle 需要功能的情况随后再说 ) 。
看下面的一段代码
Example 7-1 Java Code Fragments: Implementing the Adapter Pattern
通过 Adapter 我们就可以继续对 Shape 使用多态了。就是说,客户对象并不需要知道 Shape 对象所代表的确切类型。这也是一种新的封装思想。 Shape 类封装了形状的确切类型。 Adapter 应用得最多的目的就是使我们能够有机会使用多态。在随后的章节中你就会发现其他很多模式都要求用到多态。
我们通常都会遇到和前面例子相似的情况,但是有时,被适配的对象并不一定能完全满足我们的需要。
在这样的情况下,我们仍然可以用到 Adapter 模式,但似乎不如先前的那么完美了。在这样的一个情况下:
l 在既存在的类中已经实现的功能可以被我们适配。
l 那些我们需要但又不在既存在类中的方法,我们可以在适配器类中去实现。
虽然这样并不如先前例子中的那么好,但是,至少,我们只需要再去实现一部分功能就可以了。
Adapter 模式使我在设计过程中不再为那些已经存在的接口所烦恼。如果一个类可以完成我的工作,那么至少从理论上讲,我完全可以通过 Adapter 模式来得到适当的接口。
当你学习了更多的模式以后,你会越发发现 Adapter 的重要性。很多模式都要求某些类都继承自同一个类。如果有既存在的类, Adapter 模式就可以用来将该类适配给恰当的抽象类 ( 就象 Circle 把 XXCircle 适配给 Shape 那样 ) 。
通过我们讲到的这么些类,总有人会认为好像 Adapter 模式和 Facade 模式是一回事。他们都有既存在的类,都没有恰当的接口,我们都创建了包含恰当接口的新对象。
图 7-6 客户对象 使用 既存在 但拥有不恰当接口 的对象
我们常听到 包装 和 对对象进行包装 的说法。现在很流行对包装遗留系统 (wrapping legacy system) 和使对象变的简单可用的思考。
以上文章转自:http://fengzl.javaeye.com/blog/116914