适配器模式
概述
这一章我们通过对适配器模式
(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
1 class Circle extends Shape{
2
3 private XXCircle myXXCircle;
4
5 public Circle(){
6 myXXCricle = new XXCircle();
7 }
8
9 public void display(){
10 myXXCircle.display();
11 }
12 }
通过
Adapter
我们就可以继续对
Shape
使用多态了。就是说,客户对象并不需要知道
Shape
对象所代表的确切类型。这也是一种新的封装思想。
Shape
类封装了形状的确切类型。
Adapter
应用得最多的目的就是使我们能够有机会使用多态。在随后的章节中你就会发现其他很多模式都要求用到多态。
我们通常都会遇到和前面例子相似的情况,但是有时,被适配的对象并不一定能完全满足我们的需要。
在这样的情况下,我们仍然可以用到
Adapter
模式,但似乎不如先前的那么完美了。在这样的一个情况下:
l
在既存在的类中已经实现的功能可以被我们适配。
l
那些我们需要但又不在既存在类中的方法,我们可以在适配器类中去实现。
虽然这样并不如先前例子中的那么好,但是,至少,我们只需要再去实现一部分功能就可以了。
Adapter
模式使我在设计过程中不再为那些已经存在的接口所烦恼。如果一个类可以完成我的工作,那么至少从理论上讲,我完全可以通过
Adapter
模式来得到适当的接口。
当你学习了更多的模式以后,你会越发发现
Adapter
的重要性。很多模式都要求某些类都继承自同一个类。如果有既存在的类,
Adapter
模式就可以用来将该类适配给恰当的抽象类
(
就象
Circle
把
XXCircle
适配给
Shape
那样
)
。
通过我们讲到的这么些类,总有人会认为好像
Adapter
模式和
Facade
模式是一回事。他们都有既存在的类,都没有恰当的接口,我们都创建了包含恰当接口的新对象。
图
7-6
客户对象
使用
既存在
但拥有不恰当接口
的对象
我们常听到
包装
和
对对象进行包装
的说法。现在很流行对包装遗留系统
(wrapping legacy system)
和使对象变的简单可用的思考。
从这个高度来说,
Facade
和
Adapter
的确有点相似,他们都是进行
包装
。但他们是不同类型的包装方式。你应该明白这其中的小小不同。找到并理解到这其中的不同将给我们一个对模式的性质的新视角,在讨论一个设计或者给设计写文档以使别人知道对象的确切用途的时候,这些都非常有用。让我们来看看
Facade
和
Adapter
有何不同。
Facade
模式和
Adapter
模式的比较
|
Facade
|
Adapter
|
是否有既存在的类?
|
是
|
是
|
是否有必须实现的接口?
|
否
|
是
|
是否要用到多态?
|
否
|
有可能
|
是否需要比原接口更简单的接口?
|
是
|
否
|
从上面的表里面可以看出:
l
Facade
和
Adapter
都有预先存在的类。
l
Facade
并不象
Adapter
那样有必须实现的接口。
l
Facade
模式下我并不对多态感兴趣,而
Adapter
模式有时要用到多态。
(
有时,我们仅仅是因为需要实现某个特定接口而使用
Adapter
,这时,多态就无关紧要了。
)
l
Facade
模式的目的是简化接口。
Adapter
模式下,虽然是越简单越好,但我们是根据一个既存在的类进行设计,我们不能简化它。
总之
,
Facade
简化接口,
Adapter
把一个已经存在的接口转化成另一接口。
总结
Adapter
模式是一个经常使用的模式,她可以将已经存在的接口按照我们的需要进行转换。
Adapter
模式通过创建一个包含有需要的接口的类,并把被适配的对象包含到新类当中的方式来实现。
Adapter
模式的主要功能
|
目的
|
把既存在的但超出控制范围的对象匹配到一个特定接口中。
|
环境
|
某系统有恰当的数据和方法但是不恰当的接口。典型地用在需要继承一个抽象类的时候。
|
解决方案
|
Adapter
给需要的接口提供一个对象的包装。
|
参与方式
|
Adapter
适配被适配的
(Adaptee)
对象去匹配
Adapter
的目标
(Target)
对象。使得
Client
可以把被适配的对象当成目标对象使用。
|
结果
|
Adapter
使得既存在的对象适配新类的结构而不受其自身接口的限制。
|
执行方式
|
在另一类中包含现有类,并让该类满足接口上的要求和负责调用被包含类的方法。
|
|
图
7-7 Adapter
模式的通用结构
|
========================原文================原文==============原文=============================
The Adapter Pattern
Overview
I will continue our study of design patterns with the Adapter pattern. The Adapter pattern is a very common pattern, and, as you will see, it is used with many other patterns.
This chapter
l
Explains what the Adapter pattern is, where it is used, and how it is implemented.
l
Presents the key features of the pattern.
l
Uses the pattern to illustrate polymorphism.
l
Illustrates how the UML can be used at different levels of detail.
l
Presents some observations on the Adapter pattern from my own practice, including a comparison of the Adapter pattern and the Facade pattern.
l
Relates the Adapter pattern to the CAD/CAM problem.
Introducing the Adapter pattern
According to the Gang of Four, the intent of the Adapter pattern is to
Convert the interface of a class into another interface that the clients expect. Adapter lets classes work together that could not otherwise because of incompatible interfaces.
Basically, this is saying that we need a way to create a new interface for an object that does the right stuff but has the wrong interface.
Learning the Adapter Pattern
The easiest way to understand the intent of the Adapter pattern is to look at an example of where it is useful. Let’s say I have been given the following requirements:
l
Create classes for points, lines, and squares that have the behavior “display”.
l
The client objects should not have to know whether they actually have a point, a line, or a square. They just want to know that they have one of these shapes.
In other words, I want to encompass these specific shapes in a higher-level concept that I will call a “displayable shape”.
Now, as I work through this simple example, try to imagine other situations that you have run into that are similar, such as the following:
l
You have wanted to use a subroutine or a method that someone else has written because it performs some function that you need.
l
You cannot incorporate the routine directly into your program.
l
The interface or the way of calling the code is not exactly equivalent to the way that its related objects need to use it.
In other words, although the system will have points, lines, and squares, I want the client objects to think I have only shapes.
l
This allows client objects to deal with all these objects in the same way
-
freed from having to pay attention to their differences.
l
It also enables me to add different kinds of shapes in the future without having to change the clients.
I will make use of polymorphism; that is, I will have different objects in my system, but I want the clients of these objects to interact with them in a common way.
In this case, the client object will simply tell a point, line, or square to do something such as display itself or undisplay itself. Each point, line, and square is then responsible for knowing the way to carry out the behavior that is appropriate to its type.
To accomplish this, I will create a Shape class and then derive from it the classes that represent points, lines, and squares (see Figure 7-2).
Figure 7-2 Points, Lines, and Squares are types of Shape
First I must specify the particular behavior that Shapes are going to provide. To accomplish this, I define an interface in the Shape class and then implement the behavior appropriately in each of the derived classes.
The behaviors that a Shape needs to have are as follows:
l
Set a Shape’s location.
l
Get a Shape’s location.
l
Display a Shape.
l
Fill a Shape.
l
Set the color of a Shape.
l
Undisplay a Shape.
I show these in Figure 7-3.
Figure 7-3 Points, Lines, and Squares showing methods.
Suppose I am now asked to implement a circle, a new kind of Shape. (Remember, requirements always change!) To do this, I will want to create a new class
-
Circle
-
that implements the shape “circle” and derive it from the Shape class so that I can still get polymorphic behavior.
Now I am faced with the task of having to code the display, fill and undisplay methods for Circle. That could be a pain.
Fortunately, as I scout around for an alternative (as a good coder always should), I discover that Jill down the hall has already written a class she called XXCircle that already handles circles (see Figure 7-4). Unfortunately, she didn’t ask me what she should name the methods. She named the methods as follows:
l
displayIt
l
fillIt
l
undisplayIt
Figure 7-4 Jill’s XXCircle class.
I cannot use XXCircle directly because I want to preserve polymorphic behavior with Shape. There are two reasons for this:
l
I have different names and parameter lists
-
The method names and parameter lists are different from Shape’s method names and parameter list.
l
I cannot derive it
-
Not only must the names be the same, but the class must be derived from Shape as well.
It is unlikely that Jill will be willing to let me change the names of her methods or derive XXCircle from Shape. To do so would require her to modify all the other objects that are currently using XXCircle. Plus, I would still be concerned about creating unanticipated side effects when I modify someone else’s code.
I have what I want almost within reach, but I cannot use it and I don’t want to rewrite it. What can I do?
Rather than change it, I adapt it.
I can make a new class that does derive from Shape and therefore implements Shape’s interface but avoids rewriting the circle implementation in XXCircle (see Figure 7-5):
Figure 7-5 The Adapter pattern: Circle “wraps” XXCircle.
l
Class Circle derives from Shape.
l
Circle contains XXCircle.
l
Circle passes request made to the Circle object through to the XXCircle object.
The diamond at the end of the line between Circle and XXCircle in Figure 7-5 indicates that Circle contains an XXCircle. When a Circle object is instantiated, it must instantiate a corresponding XXCircle object. Anything the Circle object is told to do will get passed to the XXCircle object has the complete functionality the Circle object needs (I discuss soon what happens if this is not the case), the Circle object will be able to manifest its behavior by letting the XXCircle object do the job.
An example of wrapping is shown in Example 7-1
Example 7-1 Java Code Fragments: Implementing the Adapter Pattern
Class Circle extends Shape {
…
private XXCircle myXXCircle;
…
Public Circle () {
myXXCircle = new XXCircle ();
}
public void display () {
myXXCircle.displayIt();
}
…
Using the Adapter pattern enabled me to continue using polymorphism with Shape. In other words, the client objects of Shape do not know what types of shapes are actually present. This is also an example of our new thinking about encapsulation as well
-
the class Shape encapsulates the specific shapes present. The Adapter pattern is most commonly used to allow for polymorphism. As you shall see in later chapters, it is often used to allow for polymorphism required by other design patterns.
Fields Notes: The Adapter Pattern
Often I am in a situation similar to the one just described, but the object being adapted does not do all the things I need.
In this case, I can still use the Adapter pattern, but it is not such a perfect fit. In such as case
l
Those functions that are implemented in the existing class can be adapted.
l
Those functions that are not present can be implemented in the wrapping class.
This does not give me quite the same benefit, but at least I do not have to implement all of the required functionality.
The Adapter pattern frees me from worrying about the interfaces of existing classes when I am doing a design. If I have a class that does what I need, at lease conceptually, I know that I can always use the Adapter pattern to give it the correct interface.
This will become more important as you learn a few more patterns. Many patterns require certain classes to derive from the same class. If there are preexisting classes, the Adapter pattern can be used to adapt it to the appropriate abstract class (as Circle adapted XXCircle to Shape).
There are actually two types of Adapter patterns:
l
Object Adapter pattern
-
The Adapter pattern I have been using is called an Object Adapter because it relies on one object (the adapting object) containing another (the adapted object).
l
Class Adapter pattern
-
Another way to implement the Adapter pattern is with multiple inheritance. In this case, it is called a Class Adapter pattern.
The Class Adapter works by creating a new class which
l
Derives publicly from our abstract class to define its interface.
l
Derives privately from our existing class to access its implementation.
Each wrapped method calls its associated, privately inherited method.
The decision of which Adapter pattern to use is based on the different forces at work in the problem domain. At a conceptual level, I may ignore the distinction; however, when it comes time to implement it, I need to consider more of the forces involved.
In my classes on design patterns, someone almost always states that it sounds as if both the Adapter pattern and the Facade pattern are the same. In both cases there is a preexisting class (or a class) that does not have the interface that is needed. In both cases, I create a new object that has the desired interface (see Figure 7-6).
Figure 7-6 A Client object using another, preexisting object with the wrong interface.
Wrappers and object wrappers are terms that you hear a lot about. It is popular to think about wrapping legacy systems with objects to make them easier to use.
At this high view, the Facade and the Adapter patterns do seem similar. They are both wrappers. But they are different kinds of wrappers. You need to understand the differences, which can be subtle. Finding and understanding these more subtle differences gives insight into a pattern’s properties. It also helps when discussing and documenting a design so others know precisely what the object(s) are for. Let’s look at some different forces involved with these patterns (see the following table).
Comparing the Facade pattern with the Adapter pattern
|
Facade
|
Adapter
|
Are there preexisting classes?
|
Yes
|
Yes
|
Is there an interface we must design to?
|
No
|
Yes
|
Does an object need to behave polymorphically?
|
No
|
Probably
|
Is a simpler interface needed?
|
Yes
|
No
|
This table tells us the following:
l
With both the Facade and Adapter pattern, I have preexisting classes.
l
With the Facade, however, I do not have an interface I must design to, as I in the Adapter pattern.
l
I am not interested in polymorphic behavior with the Facade; whereas with the Adapter, I probably am. (There are times when we just need to design to a particular interface and therefore must use an Adapter. In this case, polymorphism may not be an issue
-
that’s way I say “probably”.)
l
In the case of the Facade pattern, the motivation is to simplify the interface. With the Adapter, although simpler is better, I am trying to design to an existing interface and cannot simplify things even if a simpler interface were otherwise possible.
Sometimes people draw the conclusion that another difference between the Facade and the Adapter pattern is that the Facade hides multiple classes behind it whereas the Adapter only hides one. Although this is often true, it is not part of the pattern. It is possible that a Facade could be used in front of a very complex object while an Adapter wrapped several objects that among them implemented the desired function.
Bottom line
: A Facade simplifies an interface while an Adapter converts a preexisting interface into another interface.
Summary
The Adapter pattern is a frequently used pattern that converts the interface of a class (or classes) into another interface, which we need the class to have. It is implemented by creating a new class with the desired interface and then wrapping the original class methods to effectively contain the adapted object.
The Adapter pattern: Key Features
|
Intent
|
Match an existing object beyond your control to a particular interface.
|
Problem
|
A system has the right data and behavior but the wrong interfaces. Typically used when you have to make something a derivative of an abstract class.
|
Solution
|
The Adapter provides a wrapper with the desired interface.
|
Participants and collaborators
|
The Adapter adapts the interface of an Adaptee to match that of the use the Adaptee’s Target (the class it derives from). This allows the Client to use the Adaptee as if it were a type of Target.
|
Consequences
|
The Adapter pattern allows for preexisting objects to fit into new class structures without being limited by their interfaces.
|
Implementation
|
Contain the existing class in another class. Have the containing class math the required interface and call the methods of the contained class.
|
|
Figure 7-6 Generic structure of the Adapter pattern.
|
posted on 2006-10-27 14:50
xiaosilent 阅读(1745)
评论(1) 编辑 收藏 所属分类:
设计模式