First we try, then we trust

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  183 随笔 :: 111 文章 :: 3190 评论 :: 358 引用

结构模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构。结构模式描述两种不同的东西:类与类的实例。根据这一点,结构模式可以分为类的结构模式和对象的结构模式。

后续内容将包括以下结构模式:

  • 适配器模式(Adapter):Match interfaces of different classes
  • 合成模式(Composite):A tree structure of simple and composite objects
  • 装饰模式(Decorator):Add responsibilities to objects dynamically
  • 代理模式(Proxy):An object representing another object
  • 享元模式(Flyweight):A fine-grained instance used for efficient sharing
  • 门面模式(Facade):A single class that represents an entire subsystem
  • 桥梁模式(Bridge):Separates an object interface from its implementation


一、 适配器(Adapter)模式

适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法在一起工作的两个类能够在一起工作。

名称由来

这很像变压器(Adapter),变压器把一种电压变换成另一种电压。美国的生活用电电压是110V,而中国的电压是220V。如果要在中国使用美国电器,就必须有一个能把220V电压转换成110V电压的变压器。这个变压器就是一个Adapter。

Adapter模式也很像货物的包装过程:被包装的货物的真实样子被包装所掩盖和改变,因此有人把这种模式叫做包装(Wrapper)模式。事实上,大家经常写很多这样的Wrapper类,把已有的一些类包装起来,使之有能满足需要的接口。

适配器模式的两种形式

适配器模式有类的适配器模式和对象的适配器模式两种。我们将分别讨论这两种Adapter模式。


二、 类的Adapter模式的结构:

 

由图中可以看出,Adaptee类没有Request方法,而客户期待这个方法。为了使客户能够使用Adaptee类,提供一个中间环节,即类Adapter类,Adapter类实现了Target接口,并继承自Adaptee,Adapter类的Request方法重新封装了Adaptee的SpecificRequest方法,实现了适配的目的。

因为Adapter与Adaptee是继承的关系,所以这决定了这个适配器模式是类的。

该适配器模式所涉及的角色包括:

目标(Target)角色:这是客户所期待的接口。因为C#不支持多继承,所以Target必须是接口,不可以是类。
源(Adaptee)角色:需要适配的类。
适配器(Adapter)角色:把源接口转换成目标接口。这一角色必须是类。


三、 类的Adapter模式示意性实现:

下面的程序给出了一个类的Adapter模式的示意性的实现:

//  Class Adapter pattern -- Structural example  
using System;

// "ITarget"
interface ITarget
{
  
// Methods
  void Request();
}


// "Adaptee"
class Adaptee
{
  
// Methods
  public void SpecificRequest()
  
{
    Console.WriteLine(
"Called SpecificRequest()" );
  }

}


// "Adapter"
class Adapter : Adaptee, ITarget
{
  
// Implements ITarget interface
  public void Request()
  
{
    
// Possibly do some data manipulation
    
// and then call SpecificRequest
    this.SpecificRequest();
  }

}


/// <summary>
/// Client test
/// </summary>

public class Client
{
  
public static void Main(string[] args)
  
{
    
// Create adapter and place a request
    ITarget t = new Adapter();
    t.Request();
  }

}



四、 对象的Adapter模式的结构:

 

从图中可以看出:客户端需要调用Request方法,而Adaptee没有该方法,为了使客户端能够使用Adaptee类,需要提供一个包装(Wrapper)类Adapter。这个包装类包装了一个Adaptee的实例,从而将客户端与Adaptee衔接起来。由于Adapter与Adaptee是委派关系,这决定了这个适配器模式是对象的。

该适配器模式所涉及的角色包括:

目标(Target)角色:这是客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
源(Adaptee)角色:需要适配的类。
适配器(Adapter)角色:通过在内部包装(Wrap)一个Adaptee对象,把源接口转换成目标接口。


五、 对象的Adapter模式示意性实现:

下面的程序给出了一个类的Adapter模式的示意性的实现:

// Adapter pattern -- Structural example  
using System;

// "Target"
class Target
{
  
// Methods
  virtual public void Request()
  
{
    
// Normal implementation goes here
  }

}


// "Adapter"
class Adapter : Target
{
  
// Fields
  private Adaptee adaptee = new Adaptee();

  
// Methods
  override public void Request()
  
{
    
// Possibly do some data manipulation
    
// and then call SpecificRequest
    adaptee.SpecificRequest();
  }

}


// "Adaptee"
class Adaptee
{
  
// Methods
  public void SpecificRequest()
  
{
    Console.WriteLine(
"Called SpecificRequest()" );
  }

}


/// <summary>
/// Client test
/// </summary>

public class Client
{
  
public static void Main(string[] args)
  
{
    
// Create adapter and place a request
    Target t = new Adapter();
    t.Request();
  }

}



六、 在什么情况下使用适配器模式

在以下各种情况下使用适配器模式:

1、 系统需要使用现有的类,而此类的接口不符合系统的需要。
2、 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口。
3、 (对对象适配器而言)在设计里,需要改变多个已有子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。


七、 一个实际应用Adapter模式的例子

下面的程序演示了Class Adapter与Object Adapter的应用。

// Example of implementing the Adapter pattern
using System;

// Target
public interface  ICar
{
  
void  Drive();
}


// Direct use without Adapter
public class  CToyota : ICar
{
  
public void  Drive()
  
{
    Console.WriteLine(
"Vroom Vroom, we're off in our Toyota");
  }

}


// Adaptee
public class  CCessna
{
  
public void  Fly()
  
{
    Console.WriteLine(
"Static runup OK, we're off in our C172");
  }

}


// Class Adapter
public class  CDrivableCessna : CCessna, ICar
{
  
public void  Drive()  {  base.Fly();  }
}


// Object Adapter
public class  CDrivableCessna2 : ICar
{
  
private CCessna  m_oContained;

  
public CDrivableCessna2()
  
{
    m_oContained 
= new CCessna();
  }


  
public void  Drive()  {  m_oContained.Fly();  }
}


// Client
public class  Client
{
  
public static void  Main(string[] args)
  
{
    ICar  oCar 
= new CToyota();

    Console.Write(
"Class Adapter: Driving an Automobile");
    oCar.Drive();
    oCar 
= new CDrivableCessna();
    Console.Write(
"Driving a Cessna");
    oCar.Drive();
    oCar 
= new CDrivableCessna2();
    Console.Write(
" Object Adapter: Driving a Cessna");
    oCar.Drive();
  }

}


八、 关于Adapter模式的讨论

Adapter模式在实现时有以下这些值得注意的地方:

1、 目标接口可以省略,模式发生退化。但这种做法看似平庸而并不平庸,它可以使Adaptee不必实现不需要的方法(可以参考Default Adapter模式)。其表现形式就是父类实现缺省方法,而子类只需实现自己独特的方法。这有些像模板(Template)模式。
2、 适配器类可以是抽象类。
3、 带参数的适配器模式。使用这种办法,适配器类可以根据参数返还一个合适的实例给客户端。


参考文献:
阎宏,《Java与模式》,电子工业出版社
[美]James W. Cooper,《C#设计模式》,电子工业出版社
[美]Alan Shalloway  James R. Trott,《Design Patterns Explained》,中国电力出版社
[美]Robert C. Martin,《敏捷软件开发-原则、模式与实践》,清华大学出版社
[美]Don Box, Chris Sells,《.NET本质论 第1卷:公共语言运行库》,中国电力出版社

1
0
(请您对文章做出评价)
« 上一篇:C#设计模式(9)-Prototype Pattern
» 下一篇:C#设计模式(10)-Adapter Pattern
posted on 2004-09-03 16:14 吕震宇 阅读(16312) 评论(39)  编辑 收藏 所属分类: 设计模式

评论

#1楼 2004-09-04 23:45 wayfarer
今晚一口气把这个系列看完了,谢谢吕震宇!

每篇文章都深入浅出的介绍了每种设计模式,期待后面更精彩的文章。

对于原型的理解还不够深,也许是因为我在实际应用中用得少的缘故。

  回复  引用    

#2楼 2004-09-05 08:56 吕震宇
@wayfarer

说起原型模式,我在做局域网内部的Remoting时,希望消息收发方从消息中知道对方的一些信息,比如IP,MAC,URL等,如果每次从计算机中都提取IP等信息是很繁琐的。所以我就做了个“消息原型”,在这个原型中,将IP, MAC等信息事先初始化好,然后每次发消息都克隆一份,然后填入消息类型以及消息内容再发送。不知道这算不算一个“原型模式”的应用。

(此回复我在Prototype模式下面复制一份,以备查看)

  回复  引用    

#3楼 2004-09-05 22:10 寒枫天伤
补充一下:
类的适配器模式使用起来有点像多重继承机制,但因为多重继承往往带来麻烦,C#中已经不支持了,利用接口的特性,把一些零散类组织到一起,成为一个新的类来对实现调用,并且看起来像是对一个类的操作,就是适配器的目的。实际上,适配器模式更多的是强调对代码的组织,而不是功能的实现。
用通俗而不精确的话来说,为了方便代码的组织与模型的准确表示,这个模式在组织代码的中作用是:可以把一个类中的成员插到另一个类的继承子类中,从而让这个继承的子类看起来像一个新类。命名进行对类的思考时,可以对父类减少依赖。
举一个很不精确的例子:
类1:虎 类2:鸟
继承一个虎,然后用适配器模式的翅膀成员拿过来,然后此继承的类,可以称之为飞虎(成语“如虎添翼”的产物,就是这个东西了)
如何拿过翅膀成员过来呢?这就是适配器的作用。

  回复  引用    

#4楼 2004-09-05 22:14 寒枫天伤
你的文章不错,希望继续。

过段时间,编辑一下,出书吧,书名可以是<<走下神坛的设计模式>>、<<平民化的设计模式>、<<设计模式ABC>>、<<设计模式从入门到精通>>、<<设计模式导论>>等等,应该畅销的:)

  回复  引用    

#5楼 2004-09-06 06:57 吕震宇
@寒枫天伤

不知道你有没有注意这篇文章的第一个图,它与Gof设计模式原来的图有些不一样。我把左侧的继承关系的实线改成了接口实现的虚线。而且在Target上添加了一个<<Interface>>。因为原书针对C++和SmallTalk写的。SmallTalk我没有用过,但C++是支持多重继承的。

因为这里我写C#设计模式,于是我用画笔把原图改了,以适应C#的语法结构。

在Adapter这里:《Design Patterns Explained》中的Favor Composite over Inheritance 这句话很有深意。

至于出书,我从来没想过。其实这个系列中的内容绝大多数都是来自参考资料的。我只是将认为关键的内容组合在了一起而已。

  回复  引用    

#6楼 2004-09-07 13:27 John.J.Dengba
@寒枫天伤 
  “如虎添翼”,真是Adapter模式的很恰当的一个注解,你可真能想!

@吕震宇
  我以前学习设计模式时是参考Gof的,很多地方看不懂,想把自己看懂的几个模式贴出来,又感觉缺乏表现力,现在您来做这件事,真是大家的幸事!而且从您的文章中也的确学习到了一些新东西,加油啊!

  回复  引用    

#7楼 2004-10-20 22:21 sai
你的这一整套东西太有用了,真是受益匪浅呀
  回复  引用    

#8楼[楼主] 2004-10-21 00:50 吕震宇      
@sai

谢谢,不过还没有整理完,最近一直很忙,进度虽然慢,但我会继续的。

  回复  引用  查看    

#9楼 2005-03-31 15:46 天生钝刀      
写的很好!让人看的很明白
  回复  引用  查看    

#10楼 2005-06-14 10:59 delicious9
谢谢,讲的清除明了!
  回复  引用    

#11楼 2005-06-27 14:25 cv222
"如虎添翼"比喻的好, 吕震宇的范例和讲解给的更好, 真的是受益匪浅
  回复  引用    

#12楼 2005-07-22 13:12 oneway[未注册用户]
good
  回复  引用    

真的很不错,通俗易懂。
楼主再接再厉

  回复  引用    

最新在学习设计模式,想请问一下以下描述采用哪种设计模式比较好:
主程序可以灵活的调用类型数形结构的目标,并且增加树的根时不会影响主程序,想请问一下采用哪种设计模式比较好?

  回复  引用    

#15楼 2006-05-13 00:14 MS的明天      
我可不可以这样理解楼主上面的代码
CDrivableCessna2 实现ICar接口,同时它又包含了一个CCessna 的对象,所以它即适应了Fly()也适应了Driver()

  回复  引用  查看    

#16楼 2006-09-26 10:30 msjqd[未注册用户]
好久没有看吕老师的设计模式了
我可不可以这么理解Adapter设计模式
我们可以把整个工作流看作是一个网页制作过程
1、Target(目标)就是客户要的最终产品,客户看好一个还不错的网站
2、Adaptee(源)就是客户看好的网站源码
现在客户让程序员在原先网站的基础上建一个适合现在公司形象的网站Adapte(适配器)
那么程序员就在原先的基础上增加了网站的功能。虽然这个例子不是太正确。。

  回复  引用    

#17楼 2006-09-26 21:27 沙漠野狼[匿名]
@msjqd
巧了,我也在看这本书,还有今天我也刚看完Adapter Pattern。我感觉这章挺不好理解的,有的我觉得模糊的很。有一个地方我改了一下,你帮我看看这样行不行,呵呵,我是刚学这个的,请多指教!我把CDrivableCessna2改成CDrivableCessna3,如下:
//Object Adapter
class CDrivableCessna2 : ICar
{
CCessna m_oContained;
public CDrivableCessna2()
{
m_oContained = new CCessna();
}
public void Drive()
{
m_oContained.Fly();
}
}

//我自己的??????????一样的结果,可以吗????????
class CDrivableCessna3 : ICar
{
public void Drive()
{
CCessna c = new CCessna();
c.Fly();
}
}

  回复  引用    

#18楼 2006-09-26 21:37 沙漠野狼[匿名]
我不知这个是不是错误?(我是个学生,以后请吕老师多多关照!)
我认为是这样的:(就是前两个输出是不是颠倒了,请吕老师指教!)
// Client
public class Client
{
public static void Main(string[] args)
{
ICar oCar = new CToyota();
Console.WriteLine("Driving an AutoMobile...!");
oCar.Drive();

oCar = new CDrivableCessna();
Console.WriteLine("Class Adapter:Driving a Cessna...!");
oCar.Drive();

oCar = new CDrivableCessna2();
Console.WriteLine("Object Adapter:Driving a Cessna...!");
oCar.Drive();
}
}

还有这个问题:我把CDrivableCessna2改成CDrivableCessna3的内容,可以得出同样的结果,你看行不行?如下:
//Object Adapter
class CDrivableCessna2 : ICar
{
CCessna m_oContained;
public CDrivableCessna2()
{
m_oContained = new CCessna();
}
public void Drive()
{
m_oContained.Fly();
}
}

//我自己的??????????一样的结果,可以吗????????
class CDrivableCessna3 : ICar
{
public void Drive()
{
CCessna c = new CCessna();
c.Fly();
}
}

  回复  引用    

#19楼 2007-02-10 10:28 luoluo[未注册用户]
第一次在这发言,首先十分感谢吕震宇,刚开始接触设计模式,除了看些书外,就是看你和wayfarer的blog.
我觉得“如虎添翼”的比喻打的不好,Adapter的目的不是为了添加新的功能(翅膀),只是为了达到接口的统一,不知道我的想法是不是有问题,见笑了。

  回复  引用    

#20楼 2007-05-21 09:56 jincan[未注册用户]
好文!谢谢吕老师。
  回复  引用    

#21楼 2007-08-21 19:16 懵懵[未注册用户]
有个问题一直没有改啊?Decorator模式被称为wrapper模式,adapter应该不能再称为wrapper模式。
  回复  引用    

#22楼 2007-10-14 21:49 YUI[未注册用户]
谢谢,这个系列的打算每天看一个,今天看了三个,有点吃不消了,呵呵
  回复  引用    

我也是吕老师的一个忠实读者,虽然是最近才发现您这一系列的文章,但我会坚持看完的,真是受益匪浅啊!
  回复  引用    

#24楼 2008-04-15 11:17 专研.NET      
好文应该多看几遍
  回复  引用  查看    

这两天一直都在学习中,非常感谢楼主.辛苦了.
  回复  引用    

#26楼 2008-08-15 16:00 张念      
我觉得类适配器的那个Adapter违反了单一职责原则,不提倡使用吧
  回复  引用  查看    

#27楼 2008-08-19 05:09 EJB[未注册用户]
About
" 对象的Adapter模式"
I think we should use singlton, because the adapter has no knowledge what instance of "Adaptee" will be created if we can keep a singlton of "Aadaptee" it will be great.

or direct change Adaptee to static class, however if we cannot change the existing code, then create a static "Adaptee" varibable in the Adapter class, or directly make the adapter class to be static.

  回复  引用    

個人認為適配器模式完全可以用對象組合方式實現.
  回复  引用    

#29楼 2008-09-04 15:10 ssss[未注册用户]
@寒枫天伤
适配器不是增加新的方法,而是将变现实现以定义的方法。飞是不属于虎类的。

  回复  引用    

#30楼 2008-09-04 15:13 ssss[未注册用户]
@Bob.Wang
对象适配器就是

  回复  引用    

太笨了,能看懂,不知道用在什么地方!
  回复  引用  查看    

#32楼 2009-04-23 12:08 大同      
看了感覺很清晰
  回复  引用  查看    

#33楼 2009-07-28 10:37 x4646      
讲得浅显易懂,适合初学者,对于入门比较有帮助, 学习了.
  回复  引用  查看