C#设计模式

《人月神话》焦油坑、没有银弹

* 软件腐化的原因:

问题所在   设计目标
----------------------------------------------------------------------------
过于僵硬   可扩展性(新性能可以很容易加入系统)
过于脆弱   灵活性(修改不会波及其它)
复用率低   
粘度过高   可插入性(新功能容易加入系统(气囊加入方向盘))

* 提高系统可复用性的几点原则:
传统复用:
1. 代码的粘帖复用
2. 算法的复用
3. 数据结构的复用

* 可维护性与可复用性并不完全一致

* 对可维护性的支持:


一、 "开放-封闭"原则(OCP)

Open-Closed Principle原则讲的是:一个软件实体应当对扩展开放,对修改关闭。

优点:
    通过扩展已有软件系统,可以提供新的行为,以满足对软件的新的需求,使变化中的软件有一定的适应性和灵活性。
    已有软件模块,特别是最重要的抽象层模块不能再修改,这使变化中的软件系统有一定的稳定性和延续性。

例子:玉帝招安美猴王
当年大闹天宫便是美猴王对玉帝的新挑战。美猴王说:"'皇帝轮流做,明年到我家。'只教他搬出去,将天宫让于我!"对于这项挑战,太白金星给玉皇大帝提出的建议是:"降一道招安圣旨,宣上界来…,一则不劳师动众,二则收仙有道也。"

换而言之,不劳师动众、不破坏天规便是"闭",收仙有道便是"开"。招安之道便是玉帝天庭的"开放-封闭"原则。

 

招安之法的关键便是不允许更改现有的天庭秩序,但允许将妖猴纳入现有秩序中,从而扩展了这一秩序。用面向对象的语言来讲,不允许更改的是系统的抽象层,而允许更改的是系统的实现层。


二、 里氏代换原则(LSP)

Liskov Substitution Principle(里氏代换原则):子类型(subtype)必须能够替换它们的基类型。

白马、黑马
 

反过来的代换不成立
《墨子·小取》说:"娣,美人也,爱娣,非爱美人也……"娣便是妹妹,哥哥喜爱妹妹,是因为两人是兄妹关系,而不是因为妹妹是个美人。因此,喜爱妹妹不等同于喜爱美人。用面向对象语言描述,美人是基类,妹妹是美人的子类。哥哥作为一个有"喜爱()"方法,接受妹妹作为参数。那么,这个"喜爱()"方法一般不能接受美人的实例。

 

一个违反LSP的简单例子(长方形和正方形)

public  class Rectangle
{
   
private  long width;
    private  long height;
    
    public  void setWidth( long width)
    {
      
this.width = width;
   }
    public  long getWidth()
    {
      
return  this.width;
   }
    public  void setHeight( long height)
    {
      
this.height = height;
   }
    public  long getHeight()
    {
      
return  this.height;
   }
}

public  class Square
{
   
private  long side;
    
    public  void setSide( long side)
    {
      
this.side = side;
   }

    public  long getSide()
    {
      
return side;
   }
}


正方形不可以做长方形的子类

using System;

public  class Rectangle
{
   
private  long width;
    private  long height;
    
    public  void setWidth( long width)
    {
      
this.width = width;
   }
    public  long getWidth()
    {
      
return  this.width;
   }
    public  void setHeight( long height)
    {
      
this.height = height;
   }
    public  long getHeight()
    {
      
return  this.height;
   }
}

public  class Square : Rectangle
{
   
private  long side;

    public  void setWidth( long width)
    {
      setSide(width);
   }


    public  long getWidth()
    {
      
return getSide();
   }

    public  void setHeight( long height)
    {
      setSide(height);
   }


    public  long getHeight()
    {
      
return getSide();
   }

    public  long getSide()
    {
      
return side;
   }

    public  void setSide( long side)
    {
      
this.side = side;
   }
}

public  class SmartTest
{
   
public  void resize(Rectangle r)
    {
      
while (r.getHeight() >= r.getWidth() )
       {
         r.setWidth(r.getWidth() 
+ 1);
      }
   }
}

 
在执行SmartTest的resize方法时,如果传入的是长方形对象,当高度大于宽度时,会自动增加宽度直到超出高度。但是如果传入的是正方形对象,则会陷入死循环。

代码重构

public  interface Quadrangle
{
   
public  long getWidth();
    public  long getHeight();
}

public  class Rectangle : Quadrangle 
{
   
private  long width;
    private  long height;
    
    public  void setWidth( long width)
    {
      
this.width = width;
   }
    public  long getWidth()
    {
      
return  this.width;
   }
    public  void setHeight( long height)
    {
      
this.height = height;
   }
    public  long getHeight()
    {
      
return  this.height;
   }
}

public  class Square : Quadrangle 
{
   
private  long side;

    public  void setSide( long side)
    {
      
this.side = side;
   }

    public  long getSide()
    {
      
return side;
   }

    public  long getWidth()
    {
      
return getSide();
   }

    public  long getHeight()
    {
      
return getSide();
   }
}


 


三、 依赖倒置原则(DIP)

依赖倒置(Dependence Inversion Principle)原则讲的是:要依赖于抽象,不要依赖于具体。

简单的说,依赖倒置原则要求客户端依赖于抽象耦合。原则表述:

抽象不应当依赖于细节;细节应当依赖于抽象;
要针对接口编程,不针对实现编程。

反面例子:

 

缺点:耦合太紧密,Light发生变化将影响ToggleSwitch。

解决办法一:
将Light作成Abstract,然后具体类继承自Light。

 

优点:ToggleSwitch依赖于抽象类Light,具有更高的稳定性,而BulbLight与TubeLight继承自Light,可以根据"开放-封闭"原则进行扩展。只要Light不发生变化,BulbLight与TubeLight的变化就不会波及ToggleSwitch。

缺点:如果用ToggleSwitch控制一台电视就很困难了。总不能让TV继承自Light吧。

解决方法二:
 

优点:更为通用、更为稳定。

结论:
使用传统过程化程序设计所创建的依赖关系,策略依赖于细节,这是糟糕的,因为策略受到细节改变的影响。依赖倒置原则使细节和策略都依赖于抽象,抽象的稳定性决定了系统的稳定性。

四、 接口隔离原则(ISP)

接口隔离原则(Interface Segregation Principle)讲的是:使用多个专门的接口比使用单一的总接口总要好。换而言之,从一个客户类的角度来讲:一个类对另外一个类的依赖性应当是建立在最小接口上的。

过于臃肿的接口是对接口的污染。不应该强迫客户依赖于它们不用的方法。

My object-oriented umbrella(摘自Design Patterns Explained)

Let me tell you about my great umbrella. It is large enough to get into! In fact, three or four other people can get in it with me. While we are in it, staying out of the rain, I can move it from one place to another. It has a stereo system to keep me entertained while I stay dry. Amazingly enough, it can also condition the air to make it warmer or colder. It is one cool umbrella.

My umbrella is convenient. It sits there waiting for me. It has wheels on it so that I do not have to carry it around. I don't even have to push it because it can propel itself. Sometimes, I will open the top of my umbrella to let in the sun. (Why I am using my umbrella when it is sunny outside is beyond me!)

In Seattle, there are hundreds of thousands of these umbrellas in all kinds of colors. Most people call them cars.

实现方法:
1、 使用委托分离接口
2、 使用多重继承分离接口

五、 合成/聚合复用原则(CARP)

合成/聚合复用原则(Composite/Aggregate Reuse Principle或CARP)经常又叫做合成复用原则(Composite Reuse Principle或CRP),就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新对象通过向这些对象的委派达到复用已有功能的目的。

简而言之,要尽量使用合成/聚合,尽量不要使用继承。

o Design to interfaces.
o Favor composition over inheritance.
o Find what varies and encapsulate it.
(摘自:Design Patterns Explained)

区分"Has-A"与"Is-A"

"Is-A"是严格的分类学意义上定义,意思是一个类是另一个类的"一种"。而"Has-A"则不同,它表示某一个角色具有某一项责任。

导致错误的使用继承而不是合成/聚合的一个常见的原因是错误的把"Has-A"当作"Is-A"。

例如:
 

实际上,雇员、经理、学生描述的是一种角色,比如一个人是"经理"必然是"雇员",另外一个人可能是"学生雇员",在上面的设计中,一个人无法同时拥有多个角色,是"雇员"就不能再是"学生"了,这显然是不合理的。

错误源于把"角色"的等级结构与"人"的等级结构混淆起来,误把"Has-A"当作"Is-A"。解决办法:

 

六、 迪米特法则(LoD)

迪米特法则(Law of Demeter或简写LoD)又叫最少知识原则(Least Knowledge Principle或简写为LKP),也就是说,一个对象应当对其它对象有尽可能少的了解。

其它表述:
  只与你直接的朋友们通信
  不要跟"陌生人"说话
  每一个软件单位对其它的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。

迪米特法则与设计模式
Facade模式、Mediator模式

使民无知
《老子》第三章曰:"是以圣人之治,虚其心,实其腹,弱其志,常使民无知无欲。"使被"统治"的对象"愚昧"化,处于"无知"的状态,可以使"统治"的成本降低。
所谓"最少知识"原则,实际上便是老子的"使民无知"的统治之术。

不相往来
《老子》云:"小国寡民……邻国相望,鸡犬之声相闻,民至老死,不相往来。"将被统治的对象隔离开来,使它们没有直接的通信,可以达到分化瓦解,继而分而治之的效果。迪米特法则与老子的"小国寡民"的统治之术不谋而合。





工厂模式专门负责将大量有共同接口的类实例化。工厂模式可以动态决定将哪一个类实例化,不必事先知道每次要实例化哪一个类。工厂模式有以下几种形态:

  • 简单工厂(Simple Factory)模式
  • 工厂方法(Factory Method)模式
  • 抽象工厂(Abstract Factory)模式

 

一、 简单工厂(Simple Factory)模式

Simple Factory模式根据提供给它的数据,返回几个可能类中的一个类的实例。通常它返回的类都有一个公共的父类和公共的方法。

Simple Factory模式实际上不是GoF 23个设计模式中的一员。


二、 Simple Factory模式角色与结构:



工厂类角色Creator (LightSimpleFactory):工厂类在客户端的直接控制下(Create方法)创建产品对象。

抽象产品角色Product (Light):定义简单工厂创建的对象的父类或它们共同拥有的接口。可以是一个类、抽象类或接口。

具体产品角色ConcreteProduct (BulbLight, TubeLight):定义工厂具体加工出的对象。


三、 程序举例:


using  System;

public   abstract   class  Light
{
   
public abstract void TurnOn();
   
public abstract void TurnOff();
}


public   class  BulbLight : Light
{
   
public override void TurnOn()
   
{
      Console.WriteLine(
"Bulb Light is Turned on");
   }


   
public override void TurnOff()
   
{
      Console.WriteLine(
"Bulb Light is Turned off");
   }

}


public   class  TubeLight : Light
{
   
public override void TurnOn()
   
{
      Console.WriteLine(
"Tube Light is Turned on");
   }


   
public override void TurnOff()
   
{
      Console.WriteLine(
"Tube Light is Turned off");
   }

}


public   class  LightSimpleFactory
{
   
public Light Create(string LightType)
   
{
      
if(LightType == "Bulb")
         
return new BulbLight();
      
else if(LightType == "Tube")
         
return new TubeLight();
      
else
         
return null;
   }

}


public   class  Client
{
   
public static void Main()
   
{
      LightSimpleFactory lsf 
= new LightSimpleFactory();

      Light l 
= lsf.Create("Bulb");
      l.TurnOn();
      l.TurnOff();

      Console.WriteLine(
"-----------------");

      l 
= lsf.Create("Tube");
      l.TurnOn();
      l.TurnOff();
   }

}



四、 Simple Factory模式演化

Simple Factory模式演化(一)

除了上面的用法外,在有些情况下Simple Factory可以由抽象产品角色扮演,一个抽象产品类同时是子类的工厂。

程序举例:

using  System;

public   class  Light
{
   
public virtual void TurnOn()
   
{
   }


   
public virtual void TurnOff()
   
{
   }


   
public static Light Create(string LightType)
   
{
      
if(LightType == "Bulb")
         
return new BulbLight();
      
else if(LightType == "Tube")
         
return new TubeLight();
      
else
         
return null;
   }

}


public   class  BulbLight : Light
{
   
public override void TurnOn()
   
{
      Console.WriteLine(
"Bulb Light is Turned on");
   }


   
public override void TurnOff()
   
{
      Console.WriteLine(
"Bulb Light is Turned off");
   }

}


public   class  TubeLight : Light
{
   
public override void TurnOn()
   
{
      Console.WriteLine(
"Tube Light is Turned on");
   }


   
public override void TurnOff()
   
{
      Console.WriteLine(
"Tube Light is Turned off");
   }

}


public   class  Client
{
   
public static void Main()
   
{
      Light l 
= Light.Create("Bulb");
      l.TurnOn();
      l.TurnOff();

      Console.WriteLine(
"-----------------");

      l 
= Light.Create("Tube");
      l.TurnOn();
      l.TurnOff();
   }

}


Simple Factory模式演化(二)

三个角色全部合并:

 

与单件模式(Singleton)相近,但是有区别。


五、 优点与缺点:

优点:
工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅"消费"产品。简单工厂模式通过这种做法实现了对责任的分割。

缺点:
当产品有复杂的多层等级结构时,工厂类只有自己,以不变应万变,就是模式的缺点。因为工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。

同时,系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,有可能造成工厂逻辑过于复杂。

另外,简单工厂模式通常使用静态工厂方法,这使得无法由子类继承,造成工厂角色无法形成基于继承的等级结构。




一、 工厂方法(Factory Method)模式

工厂方法(FactoryMethod)模式是类的创建模式,其用意是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类中。

工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。

在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不接触哪一个产品类被实例化这种细节。这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。

在Factory Method模式中,工厂类与产品类往往具有平行的等级结构,它们之间一一对应。


二、 Factory Method模式角色与结构:

 

抽象工厂(Creator)角色:是工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。

具体工厂(Concrete Creator)角色:这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建产品对象。在上图中有两个这样的角色:BulbCreator与TubeCreator。

抽象产品(Product)角色:工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或共同拥有的接口。在上图中,这个角色是Light。

具体产品(Concrete Product)角色:这个角色实现了抽象产品角色所定义的接口。某具体产品有专门的具体工厂创建,它们之间往往一一对应。


三、 程序举例:

using  System;

public   abstract     class  Light
{
   
public abstract void TurnOn();
   
public abstract void TurnOff();
}


public   class  BulbLight : Light
{
   
public override void TurnOn()
   
{ Console.WriteLine("Bulb Light is Turned on"); }

   
public override void TurnOff()
   
{ Console.WriteLine("Bulb Light is Turned off"); }
}


public   class  TubeLight : Light
{
   
public override void TurnOn()
   
{ Console.WriteLine("Tube Light is Turned on"); }

   
public override void TurnOff()
   
{ Console.WriteLine("Tube Light is Turned off"); }
}


public   abstract     class  Creator
{
   
public abstract Light factory();
}


public   class  BulbCreator : Creator
{
   
public override Light factory()
   
return new BulbLight(); }
}


public   class  TubeCreator : Creator
{
   
public override Light factory()
   
return new TubeLight(); }
}


public   class  Client
{
   
public static void Main()
   
{
      Creator c1 
= new BulbCreator();
      Creator c2 
= new TubeCreator();

      Light l1 
= c1.factory();
      Light l2 
= c2.factory();

      l1.TurnOn();
      l1.TurnOff();

      Console.WriteLine(
"-----------------");

      l2.TurnOn();
      l2.TurnOff();
   }

}

工厂方法的活动序列图

 

活动过程包括:

客户端创建BulbCreator对象,客户端持有此对象的类型是Creator,而实际类型是BulbCreator。然后客户端调用BulbCreator的factory方法,之后BulbCreator调用BulbLight的构造函数创造出产品BulbLight对象。


四、 工厂方法模式与简单工厂模式

工厂方法模式与简单工厂模式再结构上的不同不是很明显。工厂方法类的核心是一个抽象工厂类,而简单工厂模式把核心放在一个具体类上。

工厂方法模式之所以有一个别名叫多态性工厂模式是因为具体工厂类都有共同的接口,或者有共同的抽象父类。

当系统扩展需要添加新的产品对象时,仅仅需要添加一个具体对象以及一个具体工厂对象,原有工厂对象不需要进行任何修改,也不需要修改客户端,很好的符合了"开放-封闭"原则。而简单工厂模式在添加新产品对象后不得不修改工厂方法,扩展性不好。

工厂方法模式退化后可以演变成简单工厂模式。


五、 Factory Method模式演化

使用接口或抽象类
抽象工厂角色和抽象场频角色都可以选择由接口或抽象类实现。

使用多个工厂方法
抽象工厂角色可以规定出多于一个的工厂方法,从而使具体工厂角色实现这些不同的工厂方法,这些方法可以提供不同的商业逻辑,以满足提供不同的产品对象的任务。

产品的循环使用
工厂方法总是调用产品类的构造函数以创建一个新的产品实例,然后将这个实例提供给客户端。而在实际情形中,工厂方法所做的事情可以相当复杂。

一个常见的复杂逻辑就是循环使用产品对象。工厂对象将已经创建过的产品登记到一个聚集中,然后根据客户所请求的产品状态,向聚集查询。如果有满足要求的产品对象,就直接将产品返回客户端;如果聚集中没有这样的产品对象,那么就创建一个新的满足要求的产品对象,然后将这个对象登记到聚集中,再返还给客户端。"享元模式(Flyweight Pattern)"就是这样一个模式。

 

多态性的丧失和模式的退化
一个工厂方法模式的实现依赖于工厂角色和产品角色的多态性。在有些情况下,这个模式可以出现退化。

工厂方法返回的类型应当是抽象类型,而不是具体类型。调用工厂方法的客户端应当依赖抽象产品编程,而不是具体产品。如果工厂仅仅返回一个具体产品对象,便违背了工厂方法的用意,发生退化,这时就不再是工厂模式了。

工厂的等级结构:工厂对象应当有一个抽象的超类型。如果等级结构中只有一个具体工厂类的话,抽象工厂就可以省略,发生了退化。


六、 Factory Method模式与其它模式的关系

与工厂方法模式有关的模式还包括:
模板方法模式、MVC模式、享元模式、备忘录模式


七、 另外一个例子

//  Factory Method pattern -- Real World example  

using  System;
using  System.Collections;

//  "Product"
abstract   class  Page
{
}


//  "ConcreteProduct"
class  SkillsPage : Page
{
}


//  "ConcreteProduct"
class  EducationPage : Page
{
}


//  "ConcreteProduct"
class  ExperiencePage : Page
{
}


//  "ConcreteProduct"
class  IntroductionPage : Page
{
}


//  "ConcreteProduct"
class  ResultsPage : Page
{
}


//  "ConcreteProduct"
class  ConclusionPage : Page
{
}


//  "ConcreteProduct"
class  SummaryPage : Page
{
}


//  "ConcreteProduct"
class  BibliographyPage : Page
{
}


//  "Creator"
abstract   class  Document
{
  
// Fields
   protected ArrayList pages = new ArrayList();

  
// Constructor
   public Document()
  
{
    
this.CreatePages();
  }


  
// Properties
   public ArrayList Pages
  
{
    
getreturn pages; }
  }


  
// Factory Method
   abstract public void CreatePages();
}


//  "ConcreteCreator"
class  Resume : Document
{
  
// Factory Method implementation
   override public void CreatePages()
  
{
    pages.Add( 
new SkillsPage() );
    pages.Add( 
new EducationPage() );
    pages.Add( 
new ExperiencePage() );
  }

}


//  "ConcreteCreator"
class  Report : Document
{
  
// Factory Method implementation
   override public void CreatePages()
  
{
    pages.Add( 
new IntroductionPage() );
    pages.Add( 
new ResultsPage() );
    pages.Add( 
new ConclusionPage() );
    pages.Add( 
new SummaryPage() );
    pages.Add( 
new BibliographyPage() );
  }

}


/// <summary>
///  FactoryMethodApp test
/// </summary>

class  FactoryMethodApp
{
  
public static void Main( string[] args )
  
{
    Document[] docs 
= new Document[ 2 ];

    
// Note: constructors call Factory Method
    docs[0= new Resume();
    docs[
1= new Report();

    
// Display document pages
     foreach( Document document in docs )
    
{
      Console.WriteLine( 
" " + document + " ------- " );
      
foreach( Page page in document.Pages )
        Console.WriteLine( 
" " + page );
    }

  }

}



一、 抽象工厂(Abstract Factory)模式

抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。

为了方便引进抽象工厂模式,引进一个新概念:产品族(Product Family)。所谓产品族,是指位于不同产品等级结构,功能相关联的产品组成的家族。如图:

 

图中一共有四个产品族,分布于三个不同的产品等级结构中。只要指明一个产品所处的产品族以及它所属的等级结构,就可以唯一的确定这个产品。

引进抽象工厂模式

所谓的抽象工厂是指一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象。如果用图来描述的话,如下图:

 

二、 Abstract Factory模式的结构:

 

图中描述的东西用产品族描述如下:

 


抽象工厂(Abstract Factory)角色:担任这个角色的是工厂方法模式的核心,它是与应用系统商业逻辑无关的。

具体工厂(Concrete Factory)角色:这个角色直接在客户端的调用下创建产品的实例。这个角色含有选择合适的产品对象的逻辑,而这个逻辑是与应用系统的商业逻辑紧密相关的。

抽象产品(Abstract Product)角色:担任这个角色的类是工厂方法模式所创建的对象的父类,或它们共同拥有的接口。

具体产品(Concrete Product)角色:抽象工厂模式所创建的任何产品对象都是某一个具体产品类的实例。这是客户端最终需要的东西,其内部一定充满了应用系统的商业逻辑。


三、 程序举例:

该程序演示了抽象工厂的结构,本身不具有任何实际价值。

//  Abstract Factory pattern -- Structural example  
using  System;

//  "AbstractFactory"
abstract   class  AbstractFactory
{
  
// Methods
  abstract public AbstractProductA CreateProductA();
  
abstract public AbstractProductB CreateProductB();
}


//  "ConcreteFactory1"
class  ConcreteFactory1 : AbstractFactory
{
  
// Methods
  override public AbstractProductA CreateProductA()
  
{
    
return new ProductA1();
  }

  
override public AbstractProductB CreateProductB()
  
{
    
return new ProductB1();
  }

}


//  "ConcreteFactory2"
class  ConcreteFactory2 : AbstractFactory
{
  
// Methods
  override public AbstractProductA CreateProductA()
  
{
    
return new ProductA2();
  }


  
override public AbstractProductB CreateProductB()
  
{
    
return new ProductB2();
  }

}


//  "AbstractProductA"
abstract   class  AbstractProductA
{
}


//  "AbstractProductB"
abstract   class  AbstractProductB
{
  
// Methods
  abstract public void Interact( AbstractProductA a );
}


//  "ProductA1"
class  ProductA1 : AbstractProductA
{
}


//  "ProductB1"
class  ProductB1 : AbstractProductB
{
  
// Methods
  override public void Interact( AbstractProductA a )
  
{
    Console.WriteLine( 
this + " interacts with " + a );
  }

}


//  "ProductA2"
class  ProductA2 : AbstractProductA
{
}


//  "ProductB2"
class  ProductB2 : AbstractProductB
{
  
// Methods
  override public void Interact( AbstractProductA a )
  
{
    Console.WriteLine( 
this + " interacts with " + a );
  }

}


//  "Client" - the interaction environment of the products
class  Environment
{
  
// Fields
  private AbstractProductA AbstractProductA;
  
private AbstractProductB AbstractProductB;

  
// Constructors
  public Environment( AbstractFactory factory )
  
{
    AbstractProductB 
= factory.CreateProductB();
    AbstractProductA 
= factory.CreateProductA();
  }

 
  
// Methods
  public void Run()
  
{
    AbstractProductB.Interact( AbstractProductA );
  }

}


/// <summary>
/// ClientApp test environment
/// </summary>

class  ClientApp
{
  
public static void Main(string[] args)
  
{
    AbstractFactory factory1 
= new ConcreteFactory1();
    Environment e1 
= new Environment( factory1 );
    e1.Run();

    AbstractFactory factory2 
= new ConcreteFactory2();
    Environment e2 
= new Environment( factory2 );
    e2.Run();
  }

}

 


四、 在什么情形下使用抽象工厂模式:

在以下情况下应当考虑使用抽象工厂模式:

  • 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。
  • 这个系统有多于一个的产品族,而系统只消费其中某一产品族。
  • 同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。
  • 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。

五、 抽象工厂的起源

据说最早的应用是用来创建在不同操作系统的视窗环境下都能够运行的系统。比如在Windows与Unix系统下都有视窗环境的构件,在每一个操作系统中,都有一个视窗构件组成的构件家族。我们可以通过一个抽象角色给出功能描述,而由具体子类给出不同操作系统下的具体实现,如图:

 

可以发现上面产品类图有两个产品等级结构,分别是Button与Text;同时有两个产品族:Unix产品族与Windows产品族。

 

系统对产品对象的创建要求由一个工厂的等级结构满足。其中有两个具体工厂角色,即UnixFactory和WinFactory。UnixFactory对象负责创建Unix产品族中的产品,而WinFactory负责创建Windows产品族中的产品。

 

显然一个系统只能够在某一个操作系统的视窗环境下运行,而不能同时在不同的操作系统上运行。所以,系统实际上只能消费属于同一个产品族的产品。

在现代的应用中,抽象工厂模式的使用范围已经大大扩大了,不再要求系统只能消费某一个产品族了。


六、 Abstract Factory模式在实际系统中的实现

Herbivore:草食动物
Carnivore:食肉动物
Bison:['baisn],美洲或欧洲的野牛

下面实际代码演示了一个电脑游戏中创建不同动物的抽象工厂。尽管在不同大陆下动物物种是不一样的,但动物间的关系仍然保留了下来。

//  Abstract Factory pattern -- Real World example  
using  System;

//  "AbstractFactory"
abstract   class  ContinentFactory
{
  
// Methods
  abstract public Herbivore CreateHerbivore();
  
abstract public Carnivore CreateCarnivore();
}


//  "ConcreteFactory1"
class  AfricaFactory : ContinentFactory
{
  
// Methods
  override public Herbivore CreateHerbivore()
  
return new Wildebeest(); }

  
override public Carnivore CreateCarnivore()
  
return new Lion(); }
}


//  "ConcreteFactory2"
class  AmericaFactory : ContinentFactory
{
  
// Methods
  override public Herbivore CreateHerbivore()
  
return new Bison(); }

  
override public Carnivore CreateCarnivore()
  
return new Wolf(); }
}


//  "AbstractProductA"
abstract   class  Herbivore
{
}


//  "AbstractProductB"
abstract   class  Carnivore
{
  
// Methods
  abstract public void Eat( Herbivore h );
}


//  "ProductA1"
class  Wildebeest : Herbivore
{
}


//  "ProductB1"
class  Lion : Carnivore
{
  
// Methods
  override public void Eat( Herbivore h )
  
{
    
// eat wildebeest
    Console.WriteLine( this + " eats " + h );
  }

}


//  "ProductA2"
class  Bison : Herbivore
{
}


//  "ProductB2"
class  Wolf : Carnivore
{
  
// Methods
  override public void Eat( Herbivore h )
  
{
    
// Eat bison
    Console.WriteLine( this + " eats " + h );
  }

}


//  "Client"
class  AnimalWorld
{
  
// Fields
  private Herbivore herbivore;
  
private Carnivore carnivore;

  
// Constructors
  public AnimalWorld( ContinentFactory factory )
  
{
    carnivore 
= factory.CreateCarnivore();
    herbivore 
= factory.CreateHerbivore();
  }


  
// Methods
  public void RunFoodChain()
  
{ carnivore.Eat(herbivore); }
}


/// <summary>
///  GameApp test class
/// </summary>

class  GameApp
{
  
public static void Main( string[] args )
  
{
    
// Create and run the Africa animal world
    ContinentFactory africa = new AfricaFactory();
    AnimalWorld world 
= new AnimalWorld( africa );
    world.RunFoodChain();

    
// Create and run the America animal world
    ContinentFactory america = new AmericaFactory();
    world 
= new AnimalWorld( america );
    world.RunFoodChain();
  }

}

抽象工厂的另外一个例子:

如何设计抽象类工厂留作思考。


七、 "开放-封闭"原则

"开放-封闭"原则要求系统对扩展开放,对修改封闭。通过扩展达到增强其功能的目的。对于涉及到多个产品族与多个产品等级结构的系统,其功能增强包括两方面:

增加产品族:Abstract Factory很好的支持了"开放-封闭"原则。

增加新产品的等级结构:需要修改所有的工厂角色,没有很好支持"开放-封闭"原则。

综合起来,抽象工厂模式以一种倾斜的方式支持增加新的产品,它为新产品族的增加提供方便,而不能为新的产品等级结构的增加提供这样的方便。




一、 单例(Singleton)模式

单例模式的特点:

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其它对象提供这一实例。

单例模式应用:

  • 每台计算机可以有若干个打印机,但只能有一个Printer Spooler,避免两个打印作业同时输出到打印机。
  • 一个具有自动编号主键的表可以有多个用户同时使用,但数据库中只能有一个地方分配下一个主键编号。否则会出现主键重复。


二、 Singleton模式的结构:

Singleton模式包含的角色只有一个,就是Singleton。Singleton拥有一个私有构造函数,确保用户无法通过new直接实例它。除此之外,该模式中包含一个静态私有成员变量instance与静态公有方法Instance()。Instance方法负责检验并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。(关于线程问题以及C#所特有的Singleton将在后面详细论述)。


三、 程序举例:

该程序演示了Singleton的结构,本身不具有任何实际价值。

//  Singleton pattern -- Structural example  
using  System;

//  "Singleton"
class  Singleton
{
  
// Fields
  private static Singleton instance;

  
// Constructor
  protected Singleton() {}

  
// Methods
  public static Singleton Instance()
  
{
    
// Uses "Lazy initialization"
    if( instance == null )
      instance 
= new Singleton();

    
return instance;
  }

}


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

public   class  Client
{
  
public static void Main()
  
{
    
// Constructor is protected -- cannot use new
    Singleton s1 = Singleton.Instance();
    Singleton s2 
= Singleton.Instance();

    
if( s1 == s2 )
      Console.WriteLine( 
"The same instance" );
  }

}



四、 在什么情形下使用单例模式:

使用Singleton模式有一个必要条件:在一个系统要求一个类只有一个实例时才应当使用单例模式。反过来,如果一个类可以有几个实例共存,就不要使用单例模式。

注意:

不要使用单例模式存取全局变量。这违背了单例模式的用意,最好放到对应类的静态成员中。

不要将数据库连接做成单例,因为一个系统可能会与数据库有多个连接,并且在有连接池的情况下,应当尽可能及时释放连接。Singleton模式由于使用静态成员存储类实例,所以可能会造成资源无法及时释放,带来问题。


五、 Singleton模式在实际系统中的实现

下面这段Singleton代码演示了负载均衡对象。在负载均衡模型中,有多台服务器可提供服务,任务分配器随机挑选一台服务器提供服务,以确保任务均衡(实际情况比这个复杂的多)。这里,任务分配实例只能有一个,负责挑选服务器并分配任务。

//  Singleton pattern -- Real World example  

using  System;
using  System.Collections;
using  System.Threading;

//  "Singleton"
class  LoadBalancer
{
  
// Fields
  private static LoadBalancer balancer;
  
private ArrayList servers = new ArrayList();
  
private Random random = new Random();

  
// Constructors (protected)
  protected LoadBalancer()
  
{
    
// List of available servers
    servers.Add( "ServerI" );
    servers.Add( 
"ServerII" );
    servers.Add( 
"ServerIII" );
    servers.Add( 
"ServerIV" );
    servers.Add( 
"ServerV" );
  }


  
// Methods
  public static LoadBalancer GetLoadBalancer()
  
{
    
// Support multithreaded applications through
    
// "Double checked locking" pattern which avoids
    
// locking every time the method is invoked
    if( balancer == null )
    
{
      
// Only one thread can obtain a mutex
      Mutex mutex = new Mutex();
      mutex.WaitOne();

      
if( balancer == null )
        balancer 
= new LoadBalancer();

      mutex.Close();
    }

    
return balancer;
  }


  
// Properties
  public string Server
  
{
    
get
    
{
      
// Simple, but effective random load balancer
      int r = random.Next( servers.Count );
      
return servers[ r ].ToString();
    }

  }

}


/// <summary>
/// SingletonApp test
/// </summary>
///

public   class  SingletonApp
{
  
public static void Main( string[] args )
  
{
    LoadBalancer b1 
= LoadBalancer.GetLoadBalancer();
    LoadBalancer b2 
= LoadBalancer.GetLoadBalancer();
    LoadBalancer b3 
= LoadBalancer.GetLoadBalancer();
    LoadBalancer b4 
= LoadBalancer.GetLoadBalancer();

    
// Same instance?
    if( (b1 == b2) && (b2 == b3) && (b3 == b4) )
      Console.WriteLine( 
"Same instance" );

    
// Do the load balancing
    Console.WriteLine( b1.Server );
    Console.WriteLine( b2.Server );
    Console.WriteLine( b3.Server );
    Console.WriteLine( b4.Server );
  }

}



六、 C#中的Singleton模式

C#的独特语言特性决定了C#拥有实现Singleton模式的独特方法。这里不再赘述原因,给出几个结果:

方法一:

下面是利用.NET Framework平台优势实现Singleton模式的代码:

sealed   class  Singleton
{
   
private Singleton();
   
public static readonly Singleton Instance=new Singleton();
}

这使得代码减少了许多,同时也解决了线程问题带来的性能上损失。那么它又是怎样工作的呢?

注意到,Singleton类被声明为sealed,以此保证它自己不会被继承,其次没有了Instance的方法,将原来_instance成员变量变成public readonly,并在声明时被初始化。通过这些改变,我们确实得到了Singleton的模式,原因是在JIT的处理过程中,如果类中的static属性被任何方法使用时,.NET Framework将对这个属性进行初始化,于是在初始化Instance属性的同时Singleton类实例得以创建和装载。而私有的构造函数和readonly(只读)保证了Singleton不会被再次实例化,这正是Singleton设计模式的意图。
(摘自:http://www.cnblogs.com/huqingyu/archive/2004/07/09/22721.aspx )

不过这也带来了一些问题,比如无法继承,实例在程序一运行就被初始化,无法实现延迟初始化等。

详细情况可以参考微软MSDN文章:《Exploring the Singleton Design Pattern》

方法二:

既然方法一存在问题,我们还有其它办法。

public   sealed   class  Singleton
{
  Singleton()
  
{
  }


  
public static Singleton GetInstance()
  
{
    
return Nested.instance;
  }

    
  
class Nested
  
{
    
// Explicit static constructor to tell C# compiler
    
// not to mark type as beforefieldinit
    static Nested()
    
{
    }


    
internal static readonly Singleton instance = new Singleton();
  }

}

这实现了延迟初始化,并具有很多优势,当然也存在一些缺点。详细内容请访问:《Implementing the Singleton Pattern in C#》。文章包含五种Singleton实现,就模式、线程、效率、延迟初始化等很多方面进行了详细论述。




一、 建造者(Builder)模式

建造者模式可以将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。

对象性质的建造

有些情况下,一个对象会有一些重要的性质,在它们没有恰当的值之前,对象不能作为一个完整的产品使用。比如,一个电子邮件有发件人地址、收件人地址、主题、内容、附录等部分,而在最起码的收件人地址未被赋值之前,这个电子邮件不能发出。

有些情况下,一个对象的一些性质必须按照某个顺序赋值才有意义。在某个性质没有赋值之前,另一个性质则无法赋值。这些情况使得性质本身的建造涉及到复杂的商业逻辑。

这时候,此对象相当于一个有待建造的产品,而对象的这些性质相当于产品的零件,建造产品的过程就是组合零件的过程。由于组合零件的过程很复杂,因此,这些"零件"的组合过程往往被"外部化"到一个称作建造者的对象里,建造者返还给客户端的是一个全部零件都建造完毕的产品对象。

命名的考虑

之所以使用"建造者"而没有用"生成器"就是因为用零件生产产品,"建造"更为合适,"创建"或"生成"不太恰当。


二、 Builder模式的结构:

 

建造者(Builder)角色:给出一个抽象接口,以规范产品对象的各个组成成分的建造。一般而言,此接口独立于应用程序的商业逻辑。模式中直接创建产品对象的是具体建造者(ConcreteBuilder)角色。具体建造者类必须实现这个接口所要求的方法:一个是建造方法,另一个是结果返还方法。

具体建造者(Concrete Builder)角色:担任这个角色的是于应用程序紧密相关的类,它们在应用程序调用下创建产品实例。这个角色主要完成的任务包括:

  • 实现Builder角色提供的接口,一步一步完成创建产品实例的过程。
  • 在建造过程完成后,提供产品的实例。

指导者(Director)角色:担任这个角色的类调用具体建造者角色以创建产品对象。导演者并没有产品类的具体知识,真正拥有产品类的具体知识的是具体建造者对象。

产品(Product)角色:产品便是建造中的复杂对象。

指导者角色是于客户端打交道的角色。导演者角色将客户端创建产品的请求划分为对各个零件的建造请求,再将这些请求委派给具体建造者角色。具体建造者角色是做具体建造工作的,但却不为客户端所知。


三、 程序举例:

该程序演示了Builder模式一步一步完成构件复杂产品的过程。用户可以控制生成过程以及生成不同对象。

//  Builder pattern -- Structural example  

using  System;
using  System.Collections;

//  "Director"
class  Director
{
  
// Methods
  public void Construct( Builder builder )
  
{
    builder.BuildPartA();
    builder.BuildPartB();
  }

}


//  "Builder"
abstract   class  Builder
{
  
// Methods
  abstract public void BuildPartA();
  
abstract public void BuildPartB();
  
abstract public Product GetResult();
}


//  "ConcreteBuilder1"
class  ConcreteBuilder1 : Builder
{
  
// Fields
  private Product product;

  
// Methods
  override public void BuildPartA()
  
{
    product 
= new Product();
    product.Add( 
"PartA" );
  }


  
override public void BuildPartB()
  
{
    product.Add( 
"PartB" );
  }


  
override public Product GetResult()
  
{
    
return product;
  }

}


//  "ConcreteBuilder2"
class  ConcreteBuilder2 : Builder
{
  
// Fields
  private Product product;

  
// Methods
  override public void BuildPartA()
  
{
    product 
= new Product();
    product.Add( 
"PartX" );
  }


  
override public void BuildPartB()
  
{
    product.Add( 
"PartY" );
  }


  
override public Product GetResult()
  
{
    
return product;
  }

}


//  "Product"
class  Product
{
  
// Fields
  ArrayList parts = new ArrayList();
 
  
// Methods
  public void Add( string part )
  
{
    parts.Add( part );
  }


  
public void Show()
  
{
    Console.WriteLine( 
" Product Parts -------" );
    
foreachstring part in parts )
      Console.WriteLine( part );
  }

}


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

public   class  Client
{
  
public static void Main( string[] args )
  
{
    
// Create director and builders
    Director director = new Director( );

    Builder b1 
= new ConcreteBuilder1();
    Builder b2 
= new ConcreteBuilder2();

    
// Construct two products
    director.Construct( b1 );
    Product p1 
= b1.GetResult();
    p1.Show();

    director.Construct( b2 );
    Product p2 
= b2.GetResult();
    p2.Show();
  }

}



四、 建造者模式的活动序列:

客户端负责创建指导者和具体建造者对象。然后,客户把具体建造者对象交给指导者。客户一声令下,指导者操纵建造者开始创建产品。当产品创建完成后,建造者把产品返还给客户端。


五、 建造者模式的实现:

下面的程序代码演示了Shop对象使用VehicleBuilders来建造不同的交通工具。该例子使用了Builder模式顺序建造交通工具的不同部分。

//  Builder pattern -- Real World example  

using  System;
using  System.Collections;

//  "Director"
class  Shop
{
  
// Methods
  public void Construct( VehicleBuilder vehicleBuilder )
  
{
    vehicleBuilder.BuildFrame();
    vehicleBuilder.BuildEngine();
    vehicleBuilder.BuildWheels();
    vehicleBuilder.BuildDoors();
  }

}


//  "Builder"
abstract   class  VehicleBuilder
{
  
// Fields
  protected Vehicle vehicle;

  
// Properties
  public Vehicle Vehicle
  
{
    
getreturn vehicle; }
  }


  
// Methods
  abstract public void BuildFrame();
  
abstract public void BuildEngine();
  
abstract public void BuildWheels();
  
abstract public void BuildDoors();
}


//  "ConcreteBuilder1"
class  MotorCycleBuilder : VehicleBuilder
{
  
// Methods
  override public void BuildFrame()
  
{
    vehicle 
= new Vehicle( "MotorCycle" );
    vehicle[ 
"frame" ] = "MotorCycle Frame";
  }


  
override public void BuildEngine()
  
{
    vehicle[ 
"engine" ] = "500 cc";
  }


  
override public void BuildWheels()
  
{
    vehicle[ 
"wheels" ] = "2";
  }


  
override public void BuildDoors()
  
{
    vehicle[ 
"doors" ] = "0";
  }

}


//  "ConcreteBuilder2"
class  CarBuilder : VehicleBuilder
{
  
// Methods
  override public void BuildFrame()
  
{
    vehicle 
= new Vehicle( "Car" );
    vehicle[ 
"frame" ] = "Car Frame";
  }


  
override public void BuildEngine()
  
{
    vehicle[ 
"engine" ] = "2500 cc";
  }


  
override public void BuildWheels()
  
{
    vehicle[ 
"wheels" ] = "4";
  }


  
override public void BuildDoors()
  
{
    vehicle[ 
"doors" ] = "4";
  }

}


//  "ConcreteBuilder3"
class  ScooterBuilder : VehicleBuilder
{
  
// Methods
  override public void BuildFrame()
  
{
    vehicle 
= new Vehicle( "Scooter" );
    vehicle[ 
"frame" ] = "Scooter Frame";
  }


  
override public void BuildEngine()
  
{
    vehicle[ 
"engine" ] = "none";
  }


  
override public void BuildWheels()
  
{
    vehicle[ 
"wheels" ] = "2";
  }


  
override public void BuildDoors()
  
{
    vehicle[ 
"doors" ] = "0";
  }

}


//  "Product"
class  Vehicle
{
  
// Fields
  private string type;
  
private Hashtable parts = new Hashtable();

  
// Constructors
  public Vehicle( string type )
  
{
    
this.type = type;
  }


  
// Indexers
  public object thisstring key ]
  
{
    
getreturn parts[ key ]; }
    
set{ parts[ key ] = value; }
  }


  
// Methods
  public void Show()
  
{
    Console.WriteLine( 
" ---------------------------");
    Console.WriteLine( 
"Vehicle Type: "+ type );
    Console.WriteLine( 
" Frame : " + parts[ "frame" ] );
    Console.WriteLine( 
" Engine : "+ parts[ "engine"] );
    Console.WriteLine( 
" #Wheels: "+ parts[ "wheels"] );
    Console.WriteLine( 
" #Doors : "+ parts[ "doors" ] );
  }

}


/// <summary>
/// BuilderApp test
/// </summary>

public   class  BuilderApp
{
  
public static void Main( string[] args )
  
{
    
// Create shop and vehicle builders
    Shop shop = new Shop();
    VehicleBuilder b1 
= new ScooterBuilder();
    VehicleBuilder b2 
= new CarBuilder();
    VehicleBuilder b3 
= new MotorCycleBuilder();

    
// Construct and display vehicles
    shop.Construct( b1 );
    b1.Vehicle.Show();

    shop.Construct( b2 );
    b2.Vehicle.Show();

    shop.Construct( b3 );
    b3.Vehicle.Show();
  }

}


六、 建造者模式的演化

建造者模式在使用的过程中可以演化出多种形式。

省略抽象建造者角色

如果系统中只需要一个具体建造者的话,可以省略掉抽象建造者。这时代码可能如下:

//  "Director"
class  Director
{
  
private ConcreteBuilder builder;

  
// Methods
  public void Construct()
  
{
    builder.BuildPartA();
    builder.BuildPartB();
  }

}


省略指导者角色

在具体建造者只有一个的情况下,如果抽象建造者角色已经被省略掉,那么还可以省略掉指导者角色。让Builder角色自己扮演指导者与建造者双重角色。这时代码可能如下:

public   class  Builder
{
  
private Product product = new Product();

  
public void BuildPartA()
  

    
//Some code here
  }


  
public void BuildPartB()
  
{
    
//Some code here
  }


  
public Product GetResult()
  
{
    
return product;
  }


  
public void Construct()
  
{
    BuildPartA();
    BuildPartB();
  }

}

同时,客户端也需要进行相应的调整,如下:

public   class  Client
{
  
private static Builder builder;

  
public static void Main()
  
{
    builder 
= new Builder();
    builder.Construct();
    Product product 
= builder.GetResult();
  }

}

C#中的StringBuilder就是这样一个例子。


七、 在什么情况下使用建造者模式

以下情况应当使用建造者模式:

1、 需要生成的产品对象有复杂的内部结构。
2、 需要生成的产品对象的属性相互依赖,建造者模式可以强迫生成顺序。
3、 在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到。

使用建造者模式主要有以下效果:

1、 建造模式的使用使得产品的内部表象可以独立的变化。使用建造者模式可以使客户端不必知道产品内部组成的细节。
2、 每一个Builder都相对独立,而与其它的Builder无关。
3、 模式所建造的最终产品更易于控制。





一、 原型(Prototype)模式

原型模式的用意是:通过给出一个原型对象来指明所要创建的对象类型,然后用复制这个原型对象的办法创建出更多的同类型对象。

从孙大圣的手段谈起

孙悟空在与黄风怪的战斗中,"使一个身外身的手段:把毫毛揪下一把,用口嚼得粉碎,望上一喷,叫声'变',变有百十个行者,都是一样得打扮,各执一根铁棒,把那怪围在空中。"换而言之,孙悟空可以根据自己的形象,复制出很多"身外身"来。

老孙这种身外身的手段在面向对象设计领域里叫原型(Prototype)模式。

C#对原型模式的支持

在C#里面,我们可以很容易的通过Clone()方法实现原型模式。任何类,只要想支持克隆,必须实现C#中的ICloneable接口。ICloneable接口中有一Clone方法,可以在类中复写实现自定义的克隆方法。克隆的实现方法有两种:浅拷贝(shallow copy)与深拷贝(deep copy)。

(以下摘自:《.NET框架程序设计(修订版)》,李建忠译)浅拷贝是指当对象的字段值被拷贝时,字段引用的对象不会被拷贝。例如,如果一个对象有一个指向字符串的字段,并且我们对该对象做了一个浅拷贝,那么两个对象将引用同一个字符串。而深拷贝是对对象实例中字段引用的对象也进行拷贝的一种方式,所以如果一个对象有一个指向字符串的字段,并且我们对该对象做了一个深拷贝的话,我们将创建一个新的对象和一个新的字符串--新对象将引用新字符串。需要注意的是执行深拷贝后,原来的对象和新创建的对象不会共享任何东西;改变一个对象对另外一个对象没有任何影响。


二、 Prototype模式的结构:

 

客户(Client)角色:客户类提出创建对象的请求。
抽象原型(Prototype)角色:这是一个抽象角色,通常由一个C#接口或抽象类实现。此角色给出所有的具体原型类所需的接口。在C#中,抽象原型角色通常实现了ICloneable接口。
具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象原型角色所要求的接口。


三、 程序举例:

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

//  Prototype pattern -- Structural example  
using  System;

//  "Prototype"
abstract   class  Prototype
{
  
// Fields
  private string id;

  
// Constructors
  public Prototype( string id )
  
{
    
this.id = id;
  }


  
public string Id
  
{
    
getreturn id; }
  }


  
// Methods
  abstract public Prototype Clone();
}


//  "ConcretePrototype1"
class  ConcretePrototype1 : Prototype
{
  
// Constructors
  public ConcretePrototype1( string id ) : base ( id ) {}

  
// Methods
  override public Prototype Clone()
  
{
    
// Shallow copy
    return (Prototype)this.MemberwiseClone();
  }

}


//  "ConcretePrototype2"
class  ConcretePrototype2 : Prototype
{
  
// Constructors
  public ConcretePrototype2( string id ) : base ( id ) {}

  
// Methods
  override public Prototype Clone()
  
{
    
// Shallow copy
    return (Prototype)this.MemberwiseClone();
  }

}


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

class  Client
{
  
public static void Main( string[] args )
  
{
    
// Create two instances and clone each
    ConcretePrototype1 p1 = new ConcretePrototype1( "I" );
    ConcretePrototype1 c1 
= (ConcretePrototype1)p1.Clone();
    Console.WriteLine( 
"Cloned: {0}", c1.Id );

    ConcretePrototype2 p2 
= new ConcretePrototype2( "II" );
    ConcretePrototype2 c2 
= (ConcretePrototype2)p2.Clone();
    Console.WriteLine( 
"Cloned: {0}", c2.Id );
  }

}

这个例子实现了一个浅拷贝。其中MemberwiseClone()方法是Object类的一个受保护方法,实现了对象的浅拷贝。如果希望实现一个深拷贝,应该实现ICloneable接口,并自己编写ICloneable的Clone接口方法。


四、 带Prototype Manager的原型模式

原型模式的第二种形式是带原型管理器的原型模式,其UML图如下:

 

客户(Client)角色:客户端类向原型管理器提出创建对象的请求。
抽象原型(Prototype)角色:这是一个抽象角色,通常由一个C#接口或抽象类实现。此角色给出所有的具体原型类所需的接口。在C#中,抽象原型角色通常实现了ICloneable接口。
具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口。
原型管理器(Prototype Manager)角色:创建具体原型类的对象,并记录每一个被创建的对象。


下面这个例子演示了在原型管理器中存储用户预先定义的颜色原型,客户通过原型管理器克隆颜色对象。

//  Prototype pattern -- Real World example  
using  System;
using  System.Collections;

//  "Prototype"
abstract   class  ColorPrototype
{
  
// Methods
  public abstract ColorPrototype Clone();
}


//  "ConcretePrototype"
class  Color : ColorPrototype
{
  
// Fields
  private int red, green, blue;

  
// Constructors
  public Color( int red, int green, int blue)
  
{
    
this.red = red;
    
this.green = green;
    
this.blue = blue;
  }


  
// Methods
  public override ColorPrototype Clone()
  
{
    
// Creates a 'shallow copy'
    return (ColorPrototype) this.MemberwiseClone();
  }


  
public void Display()
  
{
    Console.WriteLine( 
"RGB values are: {0},{1},{2}",
      red, green, blue );
  }

}


//  Prototype manager
class  ColorManager
{
  
// Fields
  Hashtable colors = new Hashtable();

  
// Indexers
  public ColorPrototype thisstring name ]
  
{
    
getreturn (ColorPrototype)colors[ name ]; }
    
set{ colors.Add( name, value ); }
  }

}


/// <summary>
///  PrototypeApp test
/// </summary>

class  PrototypeApp
{
  
public static void Main( string[] args )
  
{
    ColorManager colormanager 
= new ColorManager();

    
// Initialize with standard colors
    colormanager[ "red" ] = new Color( 25500 );
    colormanager[ 
"green" ] = new Color( 02550 );
    colormanager[ 
"blue" ] = new Color( 00255 );

    
// User adds personalized colors
    colormanager[ "angry" ] = new Color( 255540 );
    colormanager[ 
"peace" ] = new Color( 128211128 );
    colormanager[ 
"flame" ] = new Color( 2113420 );

    
// User uses selected colors
    string colorName = "red";
    Color c1 
= (Color)colormanager[ colorName ].Clone();
    c1.Display();

    colorName 
= "peace";
    Color c2 
= (Color)colormanager[ colorName ].Clone();
    c2.Display();

    colorName 
= "flame";
    Color c3 
= (Color)colormanager[ colorName ].Clone();
    c3.Display();
  }

}



五、 浅拷贝与深拷贝

下面给出浅拷贝与深拷贝的两个例子,例子使用了ICloneable接口。C#中的数组是引用型的变量,我们通过数组来进行演示:

浅拷贝:

using  System;

class  ShallowCopy : ICloneable
{
  
public int[] v = {1,2,3};

  
public Object Clone()
  
{
    
return this.MemberwiseClone();
  }


  
public void Display()
  
{
    
foreach(int i in v)
      Console.Write( i 
+ "");
    Console.WriteLine();
  }

}


class  Client
{
  
public static void Main()
  
{
    ShallowCopy sc1 
= new ShallowCopy();
    ShallowCopy sc2 
= (ShallowCopy)sc1.Clone();
    sc1.v[
0= 9;

    sc1.Display();
    sc2.Display();
  }

}


ShallowCopy对象实现了一个浅拷贝,因此当对sc1进行克隆时,其字段v并没有克隆,这导致sc1与sc2的字段v都指向了同一个v,因此,当修改了sc1的v[0]后,sc2的v[0]也发生了变化。

深拷贝:

using  System;

class  DeepCopy : ICloneable
{
  
public int[] v = {1,2,3};

  
// 默认构造函数
  public DeepCopy()
  
{
  }


  
// 供Clone方法调用的私有构造函数
  private DeepCopy(int[] v)
  
{
    
this.v = (int[])v.Clone();
  }


  
public Object Clone()
  
{
    
// 构造一个新的DeepCopy对象,构造参数为
    
// 原有对象中使用的 v 
    return new DeepCopy(this.v);
  }


  
public void Display()
  
{
    
foreach(int i in v)
      Console.Write( i 
+ "");
    Console.WriteLine();
  }

}


class  Client
{
  
public static void Main()
  
{
    DeepCopy dc1 
= new DeepCopy();
    DeepCopy dc2 
= (DeepCopy)dc1.Clone();
    dc1.v[
0= 9;

    dc1.Display();
    dc2.Display();
  }

}


这次在克隆的时候,不但克隆对象本身,连里面的数组字段一并克隆。因此,最终打印出来的dc1与dc2不同。


六、 Prototype模式的优点与缺点

Prototype模式的优点包括

1、Prototype模式允许动态增加或减少产品类。由于创建产品类实例的方法是产批类内部具有的,因此增加新产品对整个结构没有影响。

2、Prototype模式提供了简化的创建结构。工厂方法模式常常需要有一个与产品类等级结构相同的等级结构,而Prototype模式就不需要这样。

3、Portotype模式具有给一个应用软件动态加载新功能的能力。由于Prototype的独立性较高,可以很容易动态加载新功能而不影响老系统。

4、产品类不需要非得有任何事先确定的等级结构,因为Prototype模式适用于任何的等级结构。


Prototype模式的缺点:

Prototype模式的最主要缺点就是每一个类必须配备一个克隆方法。而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事。




结构模式(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、 带参数的适配器模式。使用这种办法,适配器类可以根据参数返还一个合适的实例给客户端。




一、 合成(Composite)模式

合成模式有时又叫做部分-整体模式(Part-Whole)。合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式可以使客户端将单纯元素与复合元素同等看待。

从和尚的故事谈起

这是小时候我奶奶讲的故事:从前有个山,山里有个庙,庙里有个老和尚在给小和尚讲故事,讲的什么故事呢?从前有个山,山里有个庙……。奶奶的故事要循环多少次,根据你多长时间睡着而定。在故事中有山、有庙、有和尚、有故事。因此,故事的角色有两种:一种里面没有其它角色;另一种内部有其它角色。

对象的树结构

一个树结构由两种节点组成:树枝节点和树叶节点。树枝节点可以有子节点,而一个树叶节点不可以有子节点。除了根节点外,其它节点有且只有一个父节点。

注意:一个树枝节点可以不带任何叶子,但是它因为有带叶子的能力,因此仍然是树枝节点,而不会成为叶节点。一个树叶节点永远不可能带有子节点。


二、 合成模式概述

下图所示的类图省略了各个角色的细节。

 

可以看出,上面的类图结构涉及到三个角色:

  • 抽象构件(Component)角色:这是一个抽象角色,它给参与组合的对象规定一个接口。这个角色给出共有接口及其默认行为。
  • 树叶构件(Leaf)角色:代表参加组合的树叶对象。一个树叶对象没有下级子对象。
  • 树枝构件(Composite)角色:代表参加组合的有子对象的对象,并给出树枝构件对象的行为。

可以看出,Composite类型的对象可以包含其它Component类型的对象。换而言之,Composite类型对象可以含有其它的树枝(Composite)类型或树叶(Leaf)类型的对象。

合成模式的实现根据所实现接口的区别分为两种形式,分别称为安全模式和透明模式。合成模式可以不提供父对象的管理方法,但合成模式必须在合适的地方提供子对象的管理方法(诸如:add、remove、getChild等)。

透明方式

作为第一种选择,在Component里面声明所有的用来管理子类对象的方法,包括add()、remove(),以及getChild()方法。这样做的好处是所有的构件类都有相同的接口。在客户端看来,树叶类对象与合成类对象的区别起码在接口层次上消失了,客户端可以同等同的对待所有的对象。这就是透明形式的合成模式。

这个选择的缺点是不够安全,因为树叶类对象和合成类对象在本质上是有区别的。树叶类对象不可能有下一个层次的对象,因此add()、remove()以及getChild()方法没有意义,是在编译时期不会出错,而只会在运行时期才会出错。

安全方式

第二种选择是在Composite类里面声明所有的用来管理子类对象的方法。这样的做法是安全的做法,因为树叶类型的对象根本就没有管理子类对象的方法,因此,如果客户端对树叶类对象使用这些方法时,程序会在编译时期出错。

这个选择的缺点是不够透明,因为树叶类和合成类将具有不同的接口。

这两个形式各有优缺点,需要根据软件的具体情况做出取舍决定。


三、 安全式的合成模式的结构

安全式的合成模式要求管理聚集的方法只出现在树枝构件类中,而不出现在树叶构件中。

 

这种形式涉及到三个角色:

  • 抽象构件(Component)角色:这是一个抽象角色,它给参加组合的对象定义出公共的接口及其默认行为,可以用来管理所有的子对象。在安全式的合成模式里,构件角色并不是定义出管理子对象的方法,这一定义由树枝构件对象给出。
  • 树叶构件(Leaf)角色:树叶对象是没有下级子对象的对象,定义出参加组合的原始对象的行为。
  • 树枝构件(Composite)角色:代表参加组合的有下级子对象的对象。树枝对象给出所有的管理子对象的方法,如add()、remove()、getChild()等。


四、 安全式的合成模式实现

以下示例性代码演示了安全式的合成模式代码:

//  Composite pattern -- Structural example  
using  System;
using  System.Text;
using  System.Collections;

//  "Component"
abstract   class  Component
{
  
// Fields
  protected string name;

  
// Constructors
  public Component( string name )
  
{
    
this.name = name;
  }


  
// Operation
  public abstract void Display( int depth );
}


//  "Composite"
class  Composite : Component
{
  
// Fields
  private ArrayList children = new ArrayList();

  
// Constructors
  public Composite( string name ) : base( name ) {}

  
// Methods
  public void Add( Component component )
  
{
    children.Add( component );
  }

  
public void Remove( Component component )
  
{
    children.Remove( component );
  }

  
public override void Display( int depth )
  
{
    Console.WriteLine( 
new String( '-', depth ) + name );

    
// Display each of the node's children
    foreach( Component component in children )
      component.Display( depth 
+ 2 );
  }

}


//  "Leaf"
class  Leaf : Component
{
  
// Constructors
  public Leaf( string name ) : base( name ) {}

  
// Methods
  public override void Display( int depth )
  
{
    Console.WriteLine( 
new String( '-', depth ) + name );
  }

}


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

public   class  Client
{
  
public static void Main( string[] args )
  
{
    
// Create a tree structure
    Composite root = new Composite( "root" );
    root.Add( 
new Leaf( "Leaf A" ));
    root.Add( 
new Leaf( "Leaf B" ));
    Composite comp 
= new Composite( "Composite X" );

    comp.Add( 
new Leaf( "Leaf XA" ) );
    comp.Add( 
new Leaf( "Leaf XB" ) );
    root.Add( comp );

    root.Add( 
new Leaf( "Leaf C" ));

    
// Add and remove a leaf
    Leaf l = new Leaf( "Leaf D" );
    root.Add( l );
    root.Remove( l );

    
// Recursively display nodes
    root.Display( 1 );
  }

}


五、 透明式的合成模式结构

与安全式的合成模式不同的是,透明式的合成模式要求所有的具体构件类,不论树枝构件还是树叶构件,均符合一个固定的接口。

 

这种形式涉及到三个角色:

  • 抽象构件(Component)角色:这是一个抽象角色,它给参加组合的对象规定一个接口,规范共有的接口及默认行为。
  • 树叶构件(Leaf)角色:代表参加组合的树叶对象,定义出参加组合的原始对象的行为。树叶类会给出add()、remove()以及getChild()之类的用来管理子类对对象的方法的平庸实现。
  • 树枝构件(Composite)角色:代表参加组合的有子对象的对象,定义出这样的对象的行为。


六、 透明式的合成模式实现

以下示例性代码演示了安全式的合成模式代码:

//  Composite pattern -- Structural example  

using  System;
using  System.Text;
using  System.Collections;

//  "Component"
abstract   class  Component
{
  
// Fields
  protected string name;

  
// Constructors
  public Component( string name )
  
this.name = name; }

  
// Methods
  abstract public void Add(Component c);
  
abstract public void Remove( Component c );
  
abstract public void Display( int depth );
}


//  "Composite"
class  Composite : Component
{
  
// Fields
  private ArrayList children = new ArrayList();

  
// Constructors
  public Composite( string name ) : base( name ) {}

  
// Methods
  public override void Add( Component component )
  
{ children.Add( component ); }
  
  
public override void Remove( Component component )
  
{ children.Remove( component ); }
  
  
public override void Display( int depth )
  

    Console.WriteLine( 
new String( '-', depth ) + name );

    
// Display each of the node's children
    foreach( Component component in children )
      component.Display( depth 
+ 2 );
  }

}


//  "Leaf"
class  Leaf : Component
{
  
// Constructors
  public Leaf( string name ) : base( name ) {}

  
// Methods
  public override void Add( Component c )
  
{ Console.WriteLine("Cannot add to a leaf"); }

  
public override void Remove( Component c )
  
{ Console.WriteLine("Cannot remove from a leaf"); }

  
public override void Display( int depth )
  
{ Console.WriteLine( new String( '-', depth ) + name ); }
}


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

public   class  Client
{
  
public static void Main( string[] args )
  
{
    
// Create a tree structure
    Composite root = new Composite( "root" );
    root.Add( 
new Leaf( "Leaf A" ));
    root.Add( 
new Leaf( "Leaf B" ));
    Composite comp 
= new Composite( "Composite X" );

    comp.Add( 
new Leaf( "Leaf XA" ) );
    comp.Add( 
new Leaf( "Leaf XB" ) );
    root.Add( comp );

    root.Add( 
new Leaf( "Leaf C" ));

    
// Add and remove a leaf
    Leaf l = new Leaf( "Leaf D" );
    root.Add( l );
    root.Remove( l );

    
// Recursively display nodes
    root.Display( 1 );
  }

}


七、 使用合成模式时考虑的几个问题

  1. 明显的给出父对象的引用。在子对象里面给出父对象的引用,可以很容易的遍历所有父对象。有了这个引用,可以方便的应用责任链模式。
  2. 在通常的系统里,可以使用享元模式实现构件的共享,但是由于合成模式的对象经常要有对父对象的引用,因此共享不容易实现。
  3. 有时候系统需要遍历一个树枝结构的子构件很多次,这时候可以考虑把遍历子构件的结果暂时存储在父构件里面作为缓存。
  4. 关于使用什么数据类型来存储子对象的问题,在示意性的代码中使用了ArrayList,在实际系统中可以使用其它聚集或数组等。
  5. 客户端尽量不要直接调用树叶类中的方法,而是借助其父类(Component)的多态性完成调用,这样可以增加代码的复用性。


八、 和尚的故事


九、 一个实际应用Composite模式的例子

下面是一个实际应用中的程序,演示了通过一些基本图像元素(直线、园等)以及一些复合图像元素(由基本图像元素组合而成)构建复杂的图形树的过程。

//  Composite pattern -- Real World example  

using  System;
using  System.Collections;

//  "Component"
abstract   class  DrawingElement
{
  
// Fields
  protected string name;

  
// Constructors
  public DrawingElement( string name )
  
this.name = name; }
 
  
// Operation
  abstract public void Display( int indent );
}


//  "Leaf"
class  PrimitiveElement : DrawingElement
{
  
// Constructors
  public PrimitiveElement( string name ) : base( name ) {}

  
// Operation
  public override void Display( int indent )
  
{
    Console.WriteLine( 
new String( '-', indent ) + 
      
" draw a {0}", name );
  }

}


//  "Composite"
class  CompositeElement : DrawingElement
{
  
// Fields
  private ArrayList elements = new ArrayList();
 
  
// Constructors
  public CompositeElement( string name ) : base( name ) {}

  
// Methods
  public void Add( DrawingElement d )
  
{ elements.Add( d ); }

  
public void Remove( DrawingElement d )
  
{ elements.Remove( d ); }

  
public override void Display( int indent )
  
{
    Console.WriteLine( 
new String( '-', indent ) +
      
"" + name );

    
// Display each child element on this node
    foreach( DrawingElement c in elements )
      c.Display( indent 
+ 2 );
  }

}

 
/// <summary>
///  CompositeApp test
/// </summary>

public   class  CompositeApp
{
  
public static void Main( string[] args )
  
{
    
// Create a tree structure
    CompositeElement root = new  
      CompositeElement( 
"Picture" );
    root.Add( 
new PrimitiveElement( "Red Line" ));
    root.Add( 
new PrimitiveElement( "Blue Circle" ));
    root.Add( 
new PrimitiveElement( "Green Box" ));

    CompositeElement comp 
= new  
      CompositeElement( 
"Two Circles" );
    comp.Add( 
new PrimitiveElement( "Black Circle" ) );
    comp.Add( 
new PrimitiveElement( "White Circle" ) );
    root.Add( comp );

    
// Add and remove a PrimitiveElement
    PrimitiveElement l = new PrimitiveElement( "Yellow Line" );
    root.Add( l );
    root.Remove( l );

    
// Recursively display nodes
    root.Display( 1 );
  }

}

合成模式与很多其它模式都有联系,将在后续内容中逐步介绍。





一、 装饰(Decorator)模式

装饰(Decorator)模式又名包装(Wrapper)模式[GOF95]。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。

引言

孙悟空有七十二般变化,他的每一种变化都给他带来一种附加的本领。他变成鱼儿时,就可以到水里游泳;他变成雀儿时,就可以在天上飞行。而不管悟空怎么变化,在二郎神眼里,他永远是那只猢狲。

装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不使用创造更多子类的情况下,将对象的功能加以扩展。


二、 装饰模式的结构

装饰模式使用原来被装饰的类的一个子类的实例,把客户端的调用委派到被装饰类。装饰模式的关键在于这种扩展是完全透明的。

在孙猴子的例子里,老孙变成的鱼儿相当于老孙的子类,这条鱼儿与外界的互动要通过"委派",交给老孙的本尊,由老孙本尊采取行动。

装饰模式的类图如下图所示:

 

在装饰模式中的各个角色有:

  • 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
  • 具体构件(Concrete Component)角色:定义一个将要接收附加责任的类。
  • 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
  • 具体装饰(Concrete Decorator)角色:负责给构件对象"贴上"附加的责任。


三、 装饰模式示例性代码

以下示例性代码实现了装饰模式:

//  Decorator pattern -- Structural example  
using  System;

//  "Component"
abstract   class  Component
{
  
// Methods
  abstract public void Operation();
}


//  "ConcreteComponent"
class  ConcreteComponent : Component
{
  
// Methods
  override public void Operation()
  
{
    Console.WriteLine(
"ConcreteComponent.Operation()");
  }

}


//  "Decorator"
abstract   class  Decorator : Component
{
  
// Fields
  protected Component component;

  
// Methods
  public void SetComponent( Component component )
  
{
    
this.component = component;
  }


  
override public void Operation()
  
{
    
if( component != null )
      component.Operation();
  }

}


//  "ConcreteDecoratorA"
class  ConcreteDecoratorA : Decorator
{
  
// Fields
  private string addedState;

  
// Methods
  override public void Operation()
  
{
    
base.Operation();
    addedState 
= "new state";
    Console.WriteLine(
"ConcreteDecoratorA.Operation()");
  }

}


//  "ConcreteDecoratorB"
class  ConcreteDecoratorB : Decorator
{
  
// Methods
  override public void Operation()
  
{
    
base.Operation();
    AddedBehavior();
    Console.WriteLine(
"ConcreteDecoratorB.Operation()");
  }


  
void AddedBehavior()
  
{
  }

}


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

public   class  Client
{
  
public static void Main( string[] args )
  
{
    
// Create ConcreteComponent and two Decorators
    ConcreteComponent c = new ConcreteComponent();
    ConcreteDecoratorA d1 
= new ConcreteDecoratorA();
    ConcreteDecoratorB d2 
= new ConcreteDecoratorB();

    
// Link decorators
    d1.SetComponent( c );
    d2.SetComponent( d1 );

    d2.Operation();
  }

}

上面的代码在执行装饰时是通过SetComponent方法实现的,在实际应用中,也有通过构造函数实现的,一个典型的创建过程可能如下:

new  Decorator1(
   
new  Decorator2(
      
new  Decorator3(
         
new  ConcreteComponent()
         )
      )
   )

装饰模式常常被称为包裹模式,就是因为每一个具体装饰类都将下一个具体装饰类或者具体构件类包裹起来。


四、 装饰模式应当在什么情况下使用

在以下情况下应当使用装饰模式:

  1. 需要扩展一个类的功能,或给一个类增加附加责任。
  2. 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
  3. 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实。

五、 装饰模式实际应用的例子

该例子演示了通过装饰模式为图书馆的图书与录像带添加"可借阅"装饰。

//  Decorator pattern -- Real World example  
using  System;
using  System.Collections;

//  "Component"
abstract   class  LibraryItem
{
  
// Fields
  private int numCopies;

  
// Properties
  public int NumCopies
  
{
    
getreturn numCopies; }
    
set{ numCopies = value; }
  }


  
// Methods
  public abstract void Display();
}


//  "ConcreteComponent"
class  Book : LibraryItem
{
  
// Fields
  private string author;
  
private string title;

  
// Constructors
  public Book(string author,string title,int numCopies)
  
{
    
this.author = author;
    
this.title = title;
    
this.NumCopies = numCopies;
  }


  
// Methods
  public override void Display()
  
{
    Console.WriteLine( 
" Book ------ " );
    Console.WriteLine( 
" Author: {0}", author );
    Console.WriteLine( 
" Title: {0}", title );
    Console.WriteLine( 
" # Copies: {0}", NumCopies );
  }

}


//  "ConcreteComponent"
class  Video : LibraryItem
{
  
// Fields
  private string director;
  
private string title;
  
private int playTime;

  
// Constructor
  public Video( string director, string title,
    
int numCopies, int playTime )
  
{
    
this.director = director;
    
this.title = title;
    
this.NumCopies = numCopies;
    
this.playTime = playTime;
  }


  
// Methods
  public override void Display()
  
{
    Console.WriteLine( 
" Video ----- " );
    Console.WriteLine( 
" Director: {0}", director );
    Console.WriteLine( 
" Title: {0}", title );
    Console.WriteLine( 
" # Copies: {0}", NumCopies );
    Console.WriteLine( 
" Playtime: {0}", playTime );
  }

}


//  "Decorator"
abstract   class  Decorator : LibraryItem
{
  
// Fields
  protected LibraryItem libraryItem;

  
// Constructors
  public Decorator ( LibraryItem libraryItem )
  
this.libraryItem = libraryItem; }

  
// Methods
  public override void Display()
  
{ libraryItem.Display(); }
}


//  "ConcreteDecorator"
class  Borrowable : Decorator
{
  
// Fields
  protected ArrayList borrowers = new ArrayList();

  
// Constructors
  public Borrowable( LibraryItem libraryItem )
    : 
base( libraryItem ) {}

  
// Methods
  public void BorrowItem( string name )
  
{
    borrowers.Add( name );
    libraryItem.NumCopies
--;
  }


  
public void ReturnItem( string name )
  
{
    borrowers.Remove( name );
    libraryItem.NumCopies
++;
  }


  
public override void Display()
  
{
    
base.Display();
    
foreachstring borrower in borrowers )
      Console.WriteLine( 
" borrower: {0}", borrower );
  }

}

 
/// <summary>
///  DecoratorApp test
/// </summary>

public   class  DecoratorApp
{
  
public static void Main( string[] args )
  
{
    
// Create book and video and display
    Book book = new Book( "Schnell""My Home"10 );
    Video video 
= new Video( "Spielberg",
      
"Schindler's list"2360 );
    book.Display();
    video.Display();

    
// Make video borrowable, then borrow and display
    Console.WriteLine( " Video made borrowable:" );
    Borrowable borrowvideo 
= new Borrowable( video );
    borrowvideo.BorrowItem( 
"Cindy Lopez" );
    borrowvideo.BorrowItem( 
"Samuel King" );

    borrowvideo.Display();
  }

}


六、 使用装饰模式的优点和缺点

使用装饰模式主要有以下的优点:

  1. 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
  2. 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
  3. 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错。

使用装饰模式主要有以下的缺点:

由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。


七、 模式实现的讨论

大多数情况下,装饰模式的实现都比上面定义中给出的示意性实现要简单。对模式进行简化时需要注意以下的情况:

(1)一个装饰类的接口必须与被装饰类的接口相容。

(2)尽量保持Component作为一个"轻"类,不要把太多的逻辑和状态放在Component类里。

(3)如果只有一个ConcreteComponent类而没有抽象的Component类(接口),那么Decorator类经常可以是ConcreteComponent的一个子类。如下图所示:

 

(4)如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。


八、 透明性的要求

透明的装饰模式

装饰模式通常要求针对抽象编程。装饰模式对客户端的透明性要求程序不要声明一个ConcreteDecorator类型的变量,而应当声明一个Component类型的变量。换言之,下面的做法是对的:

Component c  =   new  ConcreteComponent();
Component c1 
=   new  ConcreteDecorator1(c);
Component c2 
=   new  ConcreteDecorator(c1);

而下面的做法是不对的:

ConcreteComponent c  =   new  ConcreteDecorator();

这就是前面所说的,装饰模式对客户端是完全透明的含义。

用孙悟空的例子来说,必须永远把孙悟空的所有变化都当成孙悟空来对待,而如果把老孙变成的雀儿当成雀儿,而不是老孙,那就被老孙骗了,而这是不应当发生的。

下面的做法是不对的:

大圣本尊 c  =   new  大圣本尊();
雀儿 bird 
=   new  雀儿 (c);

半透明的装饰模式

然而,纯粹的装饰模式很难找到。装饰模式的用意是在不改变接口的前提下,增强所考虑的类的性能。在增强性能的时候,往往需要建立新的公开的方法。即便是在孙大圣的系统里,也需要新的方法。比如齐天大圣类并没有飞行的能力,而雀儿有。这就意味着雀儿应当有一个新的fly()方法。

这就导致了大多数的装饰模式的实现都是"半透明"(semi-transparent)的,而不是完全"透明"的。换言之,允许装饰模式改变接口,增加新的方法。即声明ConcreteDecorator类型的变量,从而可以调用ConcreteDecorator类中才有的方法:

齐天大圣 c  =   new  大圣本尊();
雀儿 bird 
=   new  雀儿(c);
bird.fly();

齐天大圣接口根本没有fly()这个方法,而雀儿接口里有这个方法。


九、 装饰模式在.NET中的应用

.net中存在如下类模型:

 

下面的代码段用来将XmlDocument的内容格式输出。我们可以体会Decorator模式在这里所起的作用。

//  生成ConcreteComponent(内存流ms)
MemoryStream ms  =   new  MemoryStream();

//  用XmlTextWriter对内存流 ms 进行装饰
//  此处使用了半透明的装饰模式
XmlTextWriter xtw  =   new  XmlTextWriter(ms, Encoding.UTF8);
xtw.Formatting 
=  Formatting.Indented;

//  对装饰xtw的操作会转而操作本体-内存流ms
xmlDoc.Save(xtw);

byte [] buf  =  ms.ToArray();
txtResult.Text 
=  Encoding.UTF8.GetString(buf, 0 ,buf.Length);
xtw.Close();




一、 代理(Proxy)模式

代理(Proxy)模式给某一个对象提供一个代理,并由代理对象控制对原对象的引用。

代理模式的英文叫做Proxy或Surrogate,中文都可译成"代理"。所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。


二、 代理的种类

如果按照使用目的来划分,代理有以下几种:

  • 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是在本机器中,也可是在另一台机器中。远程代理又叫做大使(Ambassador)。
  • 虚拟(Virtual)代理:根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。
  • Copy-on-Write代理:虚拟代理的一种。把复制(克隆)拖延到只有在客户端需要时,才真正采取行动。
  • 保护(Protect or Access)代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。
  • Cache代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
  • 防火墙(Firewall)代理:保护目标,不让恶意用户接近。
  • 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
  • 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。

在所有种类的代理模式中,虚拟(Virtual)代理、远程(Remote)代理、智能引用代理(Smart Reference Proxy)和保护(Protect or Access)代理是最为常见的代理模式。


三、 远程代理的例子

Achilles是一个用来测试网站的安全性能的工具软件。Achilles相当于位于客户端的的一个桌面代理服务器,在一个HTTP过程里起到一个中间人的作用,但是Achilles与通常的代理服务器又有不同。Achilles截获双向的通信数据,使得Achilles软件的用户可以改变来自和发往网络服务器的数据,甚至可以拦截并修改SSL通讯。(这点在《Java与模式》中解释的不是很清楚,关于对非对称密钥加密拦截、破解方法,可以参考我的另外一篇文章《通过代理截取并修改非对称密钥加密信息》)。

另外一个例子就是Windows的快捷方式。快捷方式是它所引用的程序的一个代理。


四、 代理模式的结构

代理模式的类图如下图所示:

代理模式所涉及的角色有:

抽象主题角色(Subject):声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题。

代理主题(Proxy)角色:代理主题角色内部含有对真是主题的引用,从而可以在任何时候操作真实主题对象;代理主题角色提供一个与真实主题角色相同的接口,以便可以在任何时候都可以替代真实主体;控制真实主题的应用,负责在需要的时候创建真实主题对象(和删除真实主题对象);代理角色通常在将客户端调用传递给真实的主题之前或之后,都要执行某个操作,而不是单纯的将调用传递给真实主题对象。

真实主题角色(RealSubject)角色:定义了代理角色所代表的真实对象。


五、 代理模式示例性代码

以下示例性代码实现了代理模式:

//  Proxy pattern -- Structural example  
using  System;

//  "Subject"
abstract   class  Subject
{
  
// Methods
  abstract public void Request();
}


//  "RealSubject"
class  RealSubject : Subject
{
  
// Methods
  override public void Request()
  
{
    Console.WriteLine(
"Called RealSubject.Request()");
  }

}


//  "Proxy"
class  Proxy : Subject
{
  
// Fields
  RealSubject realSubject;

  
// Methods
  override public void Request()
  
{
    
// Uses "lazy initialization"
    if( realSubject == null )
      realSubject 
= new RealSubject();

    preRequest();
    realSubject.Request();
    postRequest();
  }


  
public void preRequest()
  
{ Console.WriteLine("PreRequest."); }

  
public void postRequest()
  
{ Console.WriteLine("PostRequest."); }
}


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

public   class  Client
{
  
public static void Main( string[] args )
  
{
    
// Create proxy and request a service
    Proxy p = new Proxy();
    p.Request();
  }

}


六、 高老庄悟空降八戒

尽管那时候八戒还不叫八戒,但为了方便,这里仍然这样称呼他。

高老庄的故事

却说那春融时节,悟空牵着白马,与唐僧赶路西行。忽一日天色将晚,远远地望见一村人,这就是高老庄,猪八戒的丈人高太公家。为了将高家三小姐解救出八戒的魔掌,悟空决定扮做高小姐,会一会这个妖怪:

"行者却弄神通,摇身一变,变得就如那女子一般,独自个坐在房里等那妖精。不多时,一阵风来,真个是走石飞砂……那阵狂风过处,只见半空里来了一个妖精,果然生得丑陋:黑脸短毛,长喙大耳,穿一领青不青、蓝不蓝的梭布直裰,系一条花布手巾……走进房,一把搂住,就要亲嘴……"

高家三小姐的神貌和本人

悟空的下手之处是将高家三小姐的神貌和她本人分割开来,这和"开一闭"原则有异曲同工之妙。这样一来,"高家三小姐本人"也就变成了"高家三小姐神貌"的具体实现,而"高家三小姐神貌"则变成了抽象角色,如下图所示。

悟空扮演并代替高家三小姐

悟空巧妙地实现了"高家三小姐神貌",也就是说同样变成了"高家三小姐神貌"的子类。悟空可以扮演高家三小姐,并代替高家三小姐会见八戒,其静态结构图如下图所示。

悟空代替"高家三小姐本人"去会见猪八戒。显然这就是代理模式的应用。具体地讲,这是保护代理模式的应用。只有代理对象认为合适时,才会将客户端的请求传递给真实主题对象。

八戒分辨不出真假老婆

从《西游记》的描述可以看出,猪八戒根本份辨不出悟空扮演的"高家三小姐替身"和 "高家三小姐本人"。客户端分辨不出代理主题对象与真实主题对象,这是代理模式的一个
重要用意。

悟空代替高家三小姐会见八戒的对象图如下图所示。


七、 不同类型的代理模式

远程代理

可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户完全可以认为被代理的对象是局域的而不是远程的,而代理对象承担了大部分的网络通信工作,远程代理的结构图如下图所示。

虚拟代理

使用虚拟代理模式的优点就是代理对象可以在必要的时候才将被代理的对象加载。代理可以对加载的过程加以必要的优化。当一个模块的加载十分耗费资源的时候,虚拟代理的优点就非常明显。

保护代理

保护代理可以在运行时间对用户的有关权限进行检查,然后在核实后决定将调用传递给被代理的对象。

智能引用代理

在访问一个对象时可以执行一些内务处理(Housekeeping)操作,比如计数操作等。


八、 代理模式实际应用的例子

该例子演示了利用远程代理模式提供对另外一个应用程序域(AppDomain)的对象进行访问控制。

//  Proxy pattern -- Real World example
using  System;
using  System.Runtime.Remoting;

//  "Subject" 
public   interface  IMath
{
  
// Methods
  double Add( double x, double y );
  
double Sub( double x, double y );
  
double Mul( double x, double y );
  
double Div( double x, double y );
}


//  "RealSubject" 
class  Math : MarshalByRefObject, IMath
{
  
// Methods
  public double Add( double x, double y )return x + y; }
  
public double Sub( double x, double y )return x - y; }
  
public double Mul( double x, double y )return x * y; }
  
public double Div( double x, double y )return x / y; }
}


//  Remote "Proxy Object" 
class  MathProxy : IMath
{
  
// Fields
  Math math;

  
// Constructors
  public MathProxy()
  
{
    
// Create Math instance in a different AppDomain
    AppDomain ad = System.AppDomain.CreateDomain("MathDomain",nullnull );
    ObjectHandle o 
= ad.CreateInstance("Proxy_RealWorld""Math"false,
      System.Reflection.BindingFlags.CreateInstance, 
nullnullnull,null,null );
    math 
= (Math) o.Unwrap();
  }


  
// Methods
  public double Add( double x, double y )
  

    
return math.Add(x,y); 
  }

  
public double Sub( double x, double y )
  

    
return math.Sub(x,y); 
  }

  
public double Mul( double x, double y )
  

    
return math.Mul(x,y); 
  }

  
public double Div( double x, double y )
  

    
return math.Div(x,y); 
  }

}

/// <summary>
///   ProxyApp test
/// </summary>

public   class  ProxyApp
{
  
public static void Main( string[] args )
  
{
    
// Create math proxy
    MathProxy p = new MathProxy();

    
// Do the math
    Console.WriteLine( "4 + 2 = {0}", p.Add( 42 ) );
    Console.WriteLine( 
"4 - 2 = {0}", p.Sub( 42 ) );
    Console.WriteLine( 
"4 * 2 = {0}", p.Mul( 42 ) );
    Console.WriteLine( 
"4 / 2 = {0}", p.Div( 42 ) );
  }

}



一、 享元(Flyweight)模式

Flyweight在拳击比赛中指最轻量级,即"蝇量级",有些作者翻译为"羽量级"。这里使用"享元模式"更能反映模式的用意。

享元模式以共享的方式高效地支持大量的细粒度对象。享元对象能做到共享的关键是区分内蕴状态(Internal State)和外蕴状态(External State)。内蕴状态是存储在享元对象内部并且不会随环境改变而改变。因此内蕴状态并可以共享。

外蕴状态是随环境改变而改变的、不可以共享的状态。享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。外蕴状态与内蕴状态是相互独立的。

享元模式的应用

享元模式在编辑器系统中大量使用。一个文本编辑器往往会提供很多种字体,而通常的做法就是将每一个字母做成一个享元对象。享元对象的内蕴状态就是这个字母,而字母在文本中的位置和字模风格等其他信息则是外蕴状态。比如,字母a可能出现在文本的很多地方,虽然这些字母a的位置和字模风格不同,但是所有这些地方使用的都是同一个字母对象。这样一来,字母对象就可以在整个系统中共享。


二、 单纯享元模式的结构

在单纯享元模式中,所有的享元对象都是可以共享的。单纯享元模式所涉及的角色如下:

抽象享元(Flyweight)角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口。那些需要外蕴状态(External State)的操作可以通过调用商业方法以参数形式传入。

具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享的。

享元工厂(FlyweightFactory)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个复合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。

客户端(Client)角色:本角色需要维护一个对所有享元对象的引用。本角色需要自行存储所有享元对象的外蕴状态。


三、 单纯享元模式的示意性源代码

//  Flyweight pattern -- Structural example  
using  System;
using  System.Collections;

//  "FlyweightFactory"
class  FlyweightFactory
{
  
// Fields
  private Hashtable flyweights = new Hashtable();

  
// Constructors
  public FlyweightFactory()
  
{
    flyweights.Add(
"X"new ConcreteFlyweight());
    flyweights.Add(
"Y"new ConcreteFlyweight());
    flyweights.Add(
"Z"new ConcreteFlyweight());
  }


  
// Methods
  public Flyweight GetFlyweight(string key)
  
{
    
return((Flyweight)flyweights[ key ]);
  }

}


//  "Flyweight"
abstract   class  Flyweight
{
  
// Methods
  abstract public void Operation( int extrinsicstate );
}


//  "ConcreteFlyweight"
class  ConcreteFlyweight : Flyweight
{
  
private string intrinsicstate = "A";
  
// Methods
  override public void Operation( int extrinsicstate )
  
{
    Console.WriteLine(
"ConcreteFlyweight: intrinsicstate {0}, extrinsicstate {1}"
      intrinsicstate, extrinsicstate );
  }

}


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

public   class  Client
{
  
public static void Main( string[] args )
  
{
    
// Arbitrary extrisic state
    int extrinsicstate = 22;
     
    FlyweightFactory f 
= new FlyweightFactory();

    
// Work with different flyweight instances
    Flyweight fx = f.GetFlyweight("X");
    fx.Operation( 
--extrinsicstate );

    Flyweight fy 
= f.GetFlyweight("Y");
    fy.Operation( 
--extrinsicstate );

    Flyweight fz 
= f.GetFlyweight("Z");
    fz.Operation( 
--extrinsicstate );
  }

}


四、 复合享元模式的结构

单纯享元模式中,所有的享元对象都可以直接共享。下面考虑一个较为复杂的情况,即将一些单纯享元使用合成模式加以复合,形成复合享元对象。这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。

复合享元模式的类图如下图所示:

享元模式所涉及的角色有抽象享元角色、具体享元角色、复合享元角色、享员工厂角色,以及客户端角色等。

抽象享元角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口。那些需要外蕴状态(External State)的操作可以通过方法的参数传入。抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非所有的享元对象都是可以共享的。

具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享。有时候具体享元角色又叫做单纯具体享元角色,因为复合享元角色是由单纯具体享元角色通过复合而成的。

复合享元(UnsharableFlyweight)角色:复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复合享元角色又称做不可共享的享元对象。

享元工厂(FlyweightFactoiy)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象请求一个享元对象的时候,享元工厂角色需要检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个新的合适的享元对象。

客户端(Client)角色:本角色还需要自行存储所有享元对象的外蕴状态。

注:由于复合享元模式比较复杂,这里就不再给出示意性代码。通过将享元模式与合成模式组合在一起,可以确保复合享元中所包含的每个单纯享元都具有相同的外蕴状态,而这些单纯享元的内蕴状态往往不同。该部分内容可以参考《Java与模式》第31章内容。


五、 一个咖啡摊的例子

在这个咖啡摊(Coffee Stall)所使用的系统里,有一系列的咖啡"风味(Flavor)"。客人到摊位上购买咖啡,所有的咖啡均放在台子上,客人自己拿到咖啡后就离开摊位。咖啡有内蕴状态,也就是咖啡的风味;咖啡没有环境因素,也就是说没有外蕴状态。如果系统为每一杯咖啡都创建一个独立的对象的话,那么就需要创建出很多的细小对象来。这样就不如把咖啡按照种类(即"风味")划分,每一种风味的咖啡只创建一个对象,并实行共享。

使用咖啡摊主的语言来讲,所有的咖啡都可按"风味"划分成如Capucino、Espresso等,每一种风味的咖啡不论卖出多少杯,都是全同、不可分辨的。所谓共享,就是咖啡风味的共享,制造方法的共享等。因此,享元模式对咖啡摊来说,就意味着不需要为每一份单独调制。摊主可以在需要时,一次性地调制出足够一天出售的某一种风味的咖啡。

很显然,这里适合使用单纯享元模式。系统的设计如下:

using  System;
using  System.Collections;

public   abstract   class  Order
{
  
// 将咖啡卖给客人
  public abstract void Serve();
  
// 返回咖啡的名字
  public abstract string GetFlavor();
}


public   class  Flavor : Order
{
  
private string flavor;

  
// 构造函数,内蕴状态以参数方式传入
  public Flavor(string flavor)
  
{
    
this.flavor = flavor;
  }


  
// 返回咖啡的名字
  public override string GetFlavor()
  
{
    
return this.flavor;
  }


  
// 将咖啡卖给客人
  public override void Serve()
  
{
    Console.WriteLine(
"Serving flavor " + flavor);
  }

}


public   class  FlavorFactory
{
  
private Hashtable flavors = new Hashtable();

  
public Order GetOrder(string key)
  
{
    
if(! flavors.ContainsKey(key))
      flavors.Add(key, 
new Flavor(key));

        
return ((Order)flavors[key]);
  }


  
public int GetTotalFlavorsMade()
  
{
    
return flavors.Count;
  }

}


public   class  Client
{
  
private static FlavorFactory flavorFactory;
  
private static int ordersMade = 0;

  
public static void Main( string[] args )
  
{
    flavorFactory 
= new FlavorFactory();

    TakeOrder(
"Black Coffee");
    TakeOrder(
"Capucino");
    TakeOrder(
"Espresso");
    TakeOrder(
"Capucino");
    TakeOrder(
"Espresso");
    TakeOrder(
"Black Coffee");
    TakeOrder(
"Espresso");
    TakeOrder(
"Espresso");
    TakeOrder(
"Black Coffee");
    TakeOrder(
"Capucino");
    TakeOrder(
"Capucino");
    TakeOrder(
"Black Coffee");

    Console.WriteLine(
"\nTotal Orders made: " + ordersMade);

    Console.WriteLine(
"\nTotal Flavor objects made: " + 
      flavorFactory.GetTotalFlavorsMade());
  }


  
private static void TakeOrder(string aFlavor)
  
{
    Order o 
= flavorFactory.GetOrder(aFlavor);
    
// 将咖啡卖给客人
    o.Serve();

    ordersMade
++;
  }

}


六、 咖啡屋的例子

在前面的咖啡摊项目里,由于没有供客人坐的桌子,所有的咖啡均没有环境的影响。换言之,咖啡仅有内蕴状态,也就是咖啡的种类,而没有外蕴状态。

下面考虑一个规模稍稍大一点的咖啡屋(Coffee Shop)项目。屋子里有很多的桌子供客人坐,系统除了需要提供咖啡的"风味"之外,还需要跟踪咖啡被送到哪一个桌位上,因此,咖啡就有了桌子作为外蕴状态。

由于外蕴状态的存在,没有外蕴状态的单纯享元模式不再符合要求。系统的设计可以利用有外蕴状态的单纯享元模式。系统的代码如下:

using  System;
using  System.Collections;

public   abstract   class  Order
{
  
// 将咖啡卖给客人
  public abstract void Serve(Table table);
  
// 返回咖啡的名字
  public abstract string GetFlavor();
}


public   class  Flavor : Order
{
  
private string flavor;

  
// 构造函数,内蕴状态以参数方式传入
  public Flavor(string flavor)
  
{
    
this.flavor = flavor;
  }


  
// 返回咖啡的名字
  public override string GetFlavor()
  
{
    
return this.flavor;
  }


  
// 将咖啡卖给客人
  public override void Serve(Table table)
  
{
    Console.WriteLine(
"Serving table {0} with flavor {1}", table.Number, flavor);
  }

}


public   class  FlavorFactory
{
  
private Hashtable flavors = new Hashtable();

  
public Order GetOrder(string key)
  
{
    
if(! flavors.ContainsKey(key))
      flavors.Add(key, 
new Flavor(key));

        
return ((Order)flavors[key]);
  }


  
public int GetTotalFlavorsMade()
  
{
    
return flavors.Count;
  }

}


public   class  Table
{
  
private int number;

  
public Table(int number)
  
{
    
this.number = number;
  }


  
public int Number
  
{
    
get return number; }
  }

}


public   class  Client
{
  
private static FlavorFactory flavorFactory;
  
private static int ordersMade = 0;

  
public static void Main( string[] args )
  
{
    flavorFactory 
= new FlavorFactory();

    TakeOrder(
"Black Coffee");
    TakeOrder(
"Capucino");
    TakeOrder(
"Espresso");
    TakeOrder(
"Capucino");
    TakeOrder(
"Espresso");
    TakeOrder(
"Black Coffee");
    TakeOrder(
"Espresso");
    TakeOrder(
"Espresso");
    TakeOrder(
"Black Coffee");
    TakeOrder(
"Capucino");
    TakeOrder(
"Capucino");
    TakeOrder(
"Black Coffee");

    Console.WriteLine(
"\nTotal Orders made: " + ordersMade);

    Console.WriteLine(
"\nTotal Flavor objects made: " + 
      flavorFactory.GetTotalFlavorsMade());
  }


  
private static void TakeOrder(string aFlavor)
  
{
    Order o 
= flavorFactory.GetOrder(aFlavor);
    
    
// 将咖啡卖给客人
    o.Serve(new Table(++ordersMade));
  }

}

 

七、 享元模式应当在什么情况下使用

当以下所有的条件都满足时,可以考虑使用享元模式:

  • 一个系统有大量的对象。
  • 这些对象耗费大量的内存。
  • 这些对象的状态中的大部分都可以外部化。
  • 这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。
  • 软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。

满足以上的这些条件的系统可以使用享元对象。

最后,使用享元模式需要维护一个记录了系统已有的所有享元的表,而这需要耗费资源。因此,应当在有足够多的享元实例可供共享时才值得使用享元模式。


八、 享元模式的优点和缺点

享元模式的优点在于它大幅度地降低内存中对象的数量。但是,它做到这一点所付出的代价也是很高的:

  • 享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
  • 享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。





一、 门面(Facade)模式

外部与一个子系统的通信必须通过一个统一的门面(Facade)对象进行,这就是门面模式。

医院的例子

用一个例子进行说明,如果把医院作为一个子系统,按照部门职能,这个系统可以划分为挂号、门诊、划价、化验、收费、取药等。看病的病人要与这些部门打交道,就如同一个子系统的客户端与一个子系统的各个类打交道一样,不是一件容易的事情。

首先病人必须先挂号,然后门诊。如果医生要求化验,病人必须首先划价,然后缴款,才能到化验部门做化验。化验后,再回到门诊室。

解决这种不便的方法便是引进门面模式。可以设置一个接待员的位置,由接待员负责代为挂号、划价、缴费、取药等。这个接待员就是门面模式的体现,病人只接触接待员,由接待员负责与医院的各个部门打交道。

什么是门面模式

门面模式要求一个子系统的外部与其内部的通信必须通过一个统一的门面(Facade)对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。

就如同医院的接待员一样,门面模式的门面类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与门面对象打交道,而不需要与子系统内部的很多对象打交道。


二、 门面模式的结构

门面模式是对象的结构模式。门面模式没有一个一般化的类图描述,下图演示了一个门面模式的示意性对象图:

 

在这个对象图中,出现了两个角色:

门面(Facade)角色:客户端可以调用这个角色的方法。此角色知晓相关的(一个或者多个)子系统的功能和责任。在正常情况下,本角色会将所有从客户端发来的请求委派到相应的子系统去。

子系统(subsystem)角色:可以同时有一个或者多个子系统。每一个子系统都不是一个单独的类,而是一个类的集合。每一个子系统都可以被客户端直接调用,或者被门面角色调用。子系统并不知道门面的存在,对于子系统而言,门面仅仅是另外一个客户端而已。


三、 门面模式的实现

一个系统可以有几个门面类

【GOF】的书中指出:在门面模式中,通常只需要一个门面类,并且此门面类只有一个实例,换言之它是一个单例类。当然这并不意味着在整个系统里只能有一个门面类,而仅仅是说对每一个子系统只有一个门面类。或者说,如果一个系统有好几个子系统的话,每一个子系统有一个门面类,整个系统可以有数个门面类。

为子系统增加新行为

初学者往往以为通过继承一个门面类便可在子系统中加入新的行为,这是错误的。门面模式的用意是为子系统提供一个集中化和简化的沟通管道,而不能向子系统加入新的行为。


四、 在什么情况下使用门面模式

  • 为一个复杂子系统提供一个简单接口
  • 提高子系统的独立性
  • 在层次化结构中,可以使用Facade模式定义系统中每一层的入口。


五、 一个例子

我们考察一个保安系统的例子,以说明门面模式的功效。一个保安系统由两个录像机、三个电灯、一个遥感器和一个警报器组成。保安系统的操作人员需要经常将这些仪器启动和关闭。

不使用门面模式的设计

首先,在不使用门面模式的情况下,操作这个保安系统的操作员必须直接操作所有的这些部件。下图所示就是在不使用门面模式的情况下系统的设计图。

 


可以看出,Client对象需要引用到所有的录像机(Camera)、电灯(Light)、感应器(Sensor)和警报器(Alarm)对象。代码如下:

using  System;

public   class  Camera
{
  
public void TurnOn()
  
{
    Console.WriteLine(
"Turning on the camera.");
  }


  
public void TurnOff()
  
{
    Console.WriteLine(
"Turning off the camera.");
  }


  
public void Rotate(int degrees)
  
{
    Console.WriteLine(
"Rotating the camera by {0} degrees.", degrees);
  }

}


public   class  Light
{

  
public void TurnOff()
  
{
    Console.WriteLine(
"Turning on the light.");
  }


  
public void TurnOn()
  
{
    Console.WriteLine(
"Turning off the light.");
  }


  
public void ChangeBulb()
  
{
    Console.WriteLine(
"changing the light-bulb.");
  }

}


public   class  Sensor
{
  
public void Activate()
  
{
    Console.WriteLine(
"Activating the sensor.");
  }


  
public void Deactivate()
  
{
    Console.WriteLine(
"Deactivating the sensor.");
  }


  
public void Trigger()
  
{
    Console.WriteLine(
"The sensor has triggered.");
  }

}


public   class  Alarm
{

  
public void Activate()
  
{
    Console.WriteLine(
"Activating the alarm.");
  }


  
public void Deactivate()
  
{
    Console.WriteLine(
"Deactivating the alarm.");
  }


  
public void Ring()
  
{
    Console.WriteLine(
"Ringing the alarm.");
  }


  
public void StopRing()
  
{
    Console.WriteLine(
"Stop the alarm.");
  }

}


public   class  Client
{
  
private static Camera camera1, camera2;
  
private static Light light1, light2, light3;
  
private static Sensor sensor;
  
private static Alarm alarm;

  
static Client()
  
{
    camera1 
= new Camera();
    camera2 
= new Camera();
    light1 
= new Light();
    light2 
= new Light();
    light3 
= new Light();
    sensor 
= new Sensor();
    alarm 
= new Alarm();
  }
  

  
public static void Main( string[] args )
  
{
    camera1.TurnOn();
    camera2.TurnOn();
    light1.TurnOn();
    light2.TurnOn();
    light3.TurnOn();
    sensor.Activate();
    alarm.Activate();
  }

}

 

六、 使用门面模式的设计

一个合情合理的改进方法就是准备一个系统的控制台,作为保安系统的用户界面。如下图所示:

 

程序代码如下:

using  System;

public   class  Camera
{
  
public void TurnOn()
  
{
    Console.WriteLine(
"Turning on the camera.");
  }


  
public void TurnOff()
  
{
    Console.WriteLine(
"Turning off the camera.");
  }


  
public void Rotate(int degrees)
  
{
    Console.WriteLine(
"Rotating the camera by {0} degrees.", degrees);
  }

}


public   class  Light
{

  
public void TurnOff()
  
{
    Console.WriteLine(
"Turning on the light.");
  }


  
public void TurnOn()
  
{
    Console.WriteLine(
"Turning off the light.");
  }


  
public void ChangeBulb()
  
{
    Console.WriteLine(
"changing the light-bulb.");
  }

}


public   class  Sensor
{
  
public void Activate()
  
{
    Console.WriteLine(
"Activating the sensor.");
  }


  
public void Deactivate()
  
{
    Console.WriteLine(
"Deactivating the sensor.");
  }


  
public void Trigger()
  
{
    Console.WriteLine(
"The sensor has triggered.");
  }

}


public   class  Alarm
{

  
public void Activate()
  
{
    Console.WriteLine(
"Activating the alarm.");
  }


  
public void Deactivate()
  
{
    Console.WriteLine(
"Deactivating the alarm.");
  }


  
public void Ring()
  
{
    Console.WriteLine(
"Ringing the alarm.");
  }


  
public void StopRing()
  
{
    Console.WriteLine(
"Stop the alarm.");
  }

}


public   class  SecurityFacade
{
  
private static Camera camera1, camera2;
  
private static Light light1, light2, light3;
  
private static Sensor sensor;
  
private static Alarm alarm;

  
static SecurityFacade()
  
{
    camera1 
= new Camera();
    camera2 
= new Camera();
    light1 
= new Light();
    light2 
= new Light();
    light3 
= new Light();
    sensor 
= new Sensor();
    alarm 
= new Alarm();
  }

  
  
public void Activate()
  
{
    camera1.TurnOn();
    camera2.TurnOn();
    light1.TurnOn();
    light2.TurnOn();
    light3.TurnOn();
    sensor.Activate();
    alarm.Activate();
  }


  
public void Deactivate()
  
{
    camera1.TurnOff();
    camera2.TurnOff();
    light1.TurnOff();
    light2.TurnOff();
    light3.TurnOff();
    sensor.Deactivate();
    alarm.Deactivate();
  }

}


public   class  Client
{
  
private static SecurityFacade security;

  
public static void Main( string[] args )
  
{
    security 
= new SecurityFacade();
    security.Activate();
    Console.WriteLine(
"\n--------------------\n");
    security.Deactivate();
  }

}

 






一、 桥梁(Bridge)模式

桥梁模式是一个非常有用的模式,也是比较复杂的一个模式。熟悉这个模式对于理解面向对象的设计原则,包括"开-闭"原则(OCP)以及组合/聚合复用原则(CARP)都很有帮助。理解好这两个原则,有助于形成正确的设计思想和培养良好的设计风格。

注:《Java与模式》一书认为Bridge模式不是一个使用频率很高的模式,我不太赞同,我认为Bridge模式中蕴涵了很多设计模式的关键思想在里面,所以我这里采纳了《Design Patterns Explained》一书的作者Alan Shalloway与James R. Trott的观点:The Bridge pattern is quite a bit more complex than the other patterns you just learned; it is also much more useful.

桥梁模式的用意

【GOF95】在提出桥梁模式的时候指出,桥梁模式的用意是"将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化"。这句话有三个关键词,也就是抽象化、实现化和脱耦。

抽象化

存在于多个实体中的共同的概念性联系,就是抽象化。作为一个过程,抽象化就是忽略一些信息,从而把不同的实体当做同样的实体对待【LISKOV94】。

实现化

抽象化给出的具体实现,就是实现化。

脱耦

所谓耦合,就是两个实体的行为的某种强关联。而将它们的强关联去掉,就是耦合的解脱,或称脱耦。在这里,脱耦是指将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联。

将两个角色之间的继承关系改为聚合关系,就是将它们之间的强关联改换成为弱关联。因此,桥梁模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以相对独立地变化。这就是桥梁模式的用意。


二、 桥梁模式的结构

桥梁模式【GOF95】是对象的结构模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。

下图所示就是一个实现了桥梁模式的示意性系统的结构图。

可以看出,这个系统含有两个等级结构,也就是:

  • 由抽象化角色和修正抽象化角色组成的抽象化等级结构。
  • 由实现化角色和两个具体实现化角色所组成的实现化等级结构。

桥梁模式所涉及的角色有:

  • 抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用。
  • 修正抽象化(Refined Abstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义。
  • 实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。
  • 具体实现化(Concrete Implementor)角色:这个角色给出实现化角色接口的具体实现。


三、 桥梁模式的示意性源代码

//  Bridge pattern -- Structural example  
using  System;

//  "Abstraction"
class  Abstraction
{
  
// Fields
  protected Implementor implementor;

  
// Properties
  public Implementor Implementor
  
{
    
set{ implementor = value; }
  }


  
// Methods
  virtual public void Operation()
  
{
    implementor.Operation();
  }

}


//  "Implementor"
abstract   class  Implementor
{
  
// Methods
  abstract public void Operation();
}


//  "RefinedAbstraction"
class  RefinedAbstraction : Abstraction
{
  
// Methods
  override public void Operation()
  
{
    implementor.Operation();
  }

}


//  "ConcreteImplementorA"
class  ConcreteImplementorA : Implementor
{
  
// Methods
  override public void Operation()
  
{
    Console.WriteLine(
"ConcreteImplementorA Operation");
  }

}


//  "ConcreteImplementorB"
class  ConcreteImplementorB : Implementor
{
  
// Methods
  override public void Operation()
  
{
    Console.WriteLine(
"ConcreteImplementorB Operation");
  }

}


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

public   class  Client
{
  
public static void Main( string[] args )
  
{
    Abstraction abstraction 
= new RefinedAbstraction();

    
// Set implementation and call
    abstraction.Implementor = new ConcreteImplementorA();
    abstraction.Operation();

    
// Change implemention and call
    abstraction.Implementor = new ConcreteImplementorB();
    abstraction.Operation();
  }

}


四、 调制解调器问题

感觉《敏捷软件开发-原则、模式与实践》中关于Bridge模式的例子很好。(《Java与模式》一书33章的对变化的封装一节也写得很不错,推荐大家读一读。它深入的阐述了《Design Patterns Explained》一书中"1)Design to interfaces. 2)Favor composition over inheritance. 3)Find what varies and encapsulate it"的三个观点。)。

如图所示,有大量的调制解调器客户程序在使用Modem接口。Modem接口被几个派生类HayesModem、USRoboticsModem和EarniesModem实现。它很好地遵循了OCP、LSP和DIP。当增加新种类的调制解调器时,调制解调器的客户程序不会受影响。

假定这种情形持续了几年,并有许多调制解调器的客户程序都在使用着Modem接口。现出现了一种不拨号的调制解调器,被称为专用调制解调器。它们位于一条专用连接的两端。有几个新应用程序使用这些专用调制解调器,它们无需拨号。我们称这些使用者为DedUser。但是,客户希望当前所有的调制解调器客户程序都可以使用这些专用调制解调器。他们不希望去更改许许多多的调制解调器客户应用程序,所以完全可以让这些调制解调器客户程序去拨一些假(dummy)电话号码。

如果能选择的话,我们会把系统的设计更改为下图所示的那样。

我们把拨号和通信功能分离为两个不同的接口。原来的调制解调器实现这两个接口,而调制解调器客户程序使用这两个接口。DedUser只使用Modem接口,而DedicateModem只实现Modem接口。但这样做会要求我们更改所有的调制解调器客户程序--这是客户不允许的。

一个可能的解决方案是让DedicatedModem从Modem派生并且把dial方法和hangup方法实现为空,就像下面这样:

几个月后,已经有了大量的DedUser,此时客户提出了一个新的更改。为了能拨国际电话号码、信用卡电话、PIN标识电话等等,必修对现有dial中使用char[10]存储号码改为能够拨打任意长度的电话号码。

显然,所有的调制解调器客户程序都必须更改。客户同意了对调制解调器客户程序的更改,因为他们别无选择。糟糕的是,现在必须要去告诉DedUser的编写者,他们必须要更改他们的代码!你可以想象他们听到这个会有多高兴。本来他们是不用调用dial的。

这就是许多项目都会具有的那种有害的混乱依赖关系。系统某一部分中的一个杂凑体(kludge)创建了一个有害的依赖关系,最终导致系统中完全无关的部分出现问题。

如果使用ADAPTER模式解决最初的问题的话,就可以避免这个严重问题。如图:

请注意,杂凑体仍然存在。适配器仍然要模拟连接状态。然而,所有的依赖关系都是从适配器发起的。杂凑体和系统隔离,藏身于几乎无人知晓的适配器中。

BRIDGE模式

看待这个问题,还有另外一个方式。现在,出现了另外一种切分Modem层次结构的方式。如下图:

这不是一个理想的结构。每当增加一款新硬件时,就必须创建两个新类--一个针对专用的情况,一个针对拨号的情况。每当增加一种新连接类型时,就必须创建3个新类,分别对应3款不同的硬件。如果这两个自由度根本就是不稳定的,那么不用多久,就会出现大量的派生类。

在类型层次结构具有多个自由度的情况中,BRIDGE模式通常是有用的。我们可以把这些层次结构分开并通过桥把它们结合到一起,而不是把它们合并起来。如图:

我们把调制解调器类层次结构分成两个层次结构。一个表示连接方法,另一个表示硬件。

这个结构虽然复杂,但是很有趣。它的创建不会影响到调制解调器的使用者,并且还完全分离了连接策略和硬件实现。ModemConnectController的每个派生类代表了一个新的连接策略。在这个策略的实现中可以使用sendlmp、receivelmp、diallmp和hanglmp。新imp方法的增加不会影响到使用者。可以使用ISP来给连接控制类增加新的接口。这种做法可以创建出一条迁移路径,调制解调器的客户程序可以沿着这条路径慢慢地得到一个比dial和hangup层次更高的API。


五、 另外一个实际应用Bridge模式的例子

该例子演示了业务对象(BusinessObject)通过Bridge模式与数据对象(DataObject)解耦。数据对象的实现可以在不改变客户端代码的情况下动态进行更换。

//  Bridge pattern -- Real World example
using  System;
using  System.Collections;

//  "Abstraction"
class  BusinessObject
{
  
// Fields
  private DataObject dataObject;
  
protected string group;

  
// Constructors
  public BusinessObject( string group )
  
{
    
this.group = group;
  }


  
// Properties
  public DataObject DataObject
  
{
    
set{ dataObject = value; }
    
getreturn dataObject; }
  }


  
// Methods
  virtual public void Next()
  
{ dataObject.NextRecord(); }

  
virtual public void Prior()
  
{ dataObject.PriorRecord(); }

  
virtual public void New( string name )
  
{ dataObject.NewRecord( name ); }

  
virtual public void Delete( string name )
  
{ dataObject.DeleteRecord( name ); }

  
virtual public void Show()
  
{ dataObject.ShowRecord(); }

  
virtual public void ShowAll()
  
{
    Console.WriteLine( 
"Customer Group: {0}", group );
    dataObject.ShowAllRecords();
  }

}


//  "RefinedAbstraction"
class  CustomersBusinessObject : BusinessObject
{
  
// Constructors
  public CustomersBusinessObject( string group )
    : 
base( group ){}

  
// Methods
  override public void ShowAll()
  
{
    
// Add separator lines
    Console.WriteLine();
    Console.WriteLine( 
"------------------------" );
    
base.ShowAll();
    Console.WriteLine( 
"------------------------" );
  }

}


//  "Implementor"
abstract   class  DataObject
{
  
// Methods
  abstract public void NextRecord();
  
abstract public void PriorRecord();
  
abstract public void NewRecord( string name );
  
abstract public void DeleteRecord( string name );
  
abstract public void ShowRecord();
  
abstract public void ShowAllRecords();
}


//  "ConcreteImplementor"
class  CustomersDataObject : DataObject
{
  
// Fields
  private ArrayList customers = new ArrayList();
  
private int current = 0;

  
// Constructors
  public CustomersDataObject()
  
{
    
// Loaded from a database
    customers.Add( "Jim Jones" );
    customers.Add( 
"Samual Jackson" );
    customers.Add( 
"Allen Good" );
    customers.Add( 
"Ann Stills" );
    customers.Add( 
"Lisa Giolani" );
  }


  
// Methods
  public override void NextRecord()
  
{
    
if( current <= customers.Count - 1 )
      current
++;
  }


  
public override void PriorRecord()
  
{
    
if( current > 0 )
      current
--;
  }


  
public override void NewRecord( string name )
  
{
    customers.Add( name );
  }


  
public override void DeleteRecord( string name )
  
{
    customers.Remove( name );
  }


  
public override void ShowRecord()
  
{
    Console.WriteLine( customers[ current ] );
  }


  
public override void ShowAllRecords()
  
{
    
foreachstring name in customers )
      Console.WriteLine( 
" " + name );
  }

}


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

public   class  BusinessApp
{
  
public static void Main( string[] args )
  
{
    
// Create RefinedAbstraction
    CustomersBusinessObject customers =
      
new CustomersBusinessObject(" Chicago ");

    
// Set ConcreteImplementor
    customers.DataObject = new CustomersDataObject();

    
// Exercise the bridge
    customers.Show();
    customers.Next();
    customers.Show();
    customers.Next();
    customers.Show();
    customers.New( 
"Henry Velasquez" );

    customers.ShowAll();
  }

}

 

六、 在什么情况下应当使用桥梁模式

根据上面的分析,在以下的情况下应当使用桥梁模式:

  • 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的联系。
  • 设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对客户端是完全透明的。
  • 一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。
  • 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。




行为模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。行为模式不仅仅是关于类和对象的,而且是关于它们之间的相互作用的。

行为模式分为类的行为模式和对象的行为模式两种。

  • 类的行为模式:类的行为模式使用继承关系在几个类之问分配行为。
  • 对象的行为模式:对象的行为模式则使用对象的聚合来分配行为。

在后面将要介绍的行为模式包括以下几种:

  • Chain of Resp.(责任链模式)A way of passing a request between a chain of objects
  • Command(命令模式)Encapsulate a command request as an object
  • Interpreter(解释器模式)A way to include language elements in a program
  • Iterator(迭代子模式)Sequentially access the elements of a collection
  • Mediator(中介者模式)Defines simplified communication between classes
  • Memento(备忘录模式)Capture and restore an object's internal state
  • Observer(观察者模式)A way of notifying change to a number of classes
  • State(状态模式)Alter an object's behavior when its state changes
  • Strategy(策略模式)Encapsulates an algorithm inside a class
  • Template Method(模版方法模式)Defer the exact steps of an algorithm to a subclass
  • Visitor(访问者模式)Defines a new operation to a class without change

 

一、 职责链(Chain of Responsibility)模式

责任链模式是一种对象的行为模式【GOF95】。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。

从击鼓传花谈起

击鼓传花是一种热闹而又紧张的饮酒游戏。在酒宴上宾客依次坐定位置,由一人击鼓,击鼓的地方与传花的地方是分开的,以示公正。开始击鼓时,花束就开始依次传递,鼓声一落,如果花束在某人手中,则该人就得饮酒。

击鼓传花便是责任链模式的应用。责任链可能是一条直线、一个环链或者一个树结构的一部分。


二、 责任链模式的结构

 

责任链模式涉及到的角色如下所示:

抽象处理者(Handler)角色:定义出一个处理请求的接口。如果需要,接口可以定义出一个方法,以设定和返回对下家的引用。这个角色通常由一个抽象类或接口实现。

具体处理者(ConcreteHandler)角色:具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。


三、 责任链模式的示意性源代码

//  Chain of Responsibility pattern -- Structural example  
using  System;

//  "Handler"
abstract   class  Handler
{
  
// Fields
  protected Handler successor;
 
  
// Methods
  public void SetSuccessor( Handler successor )
  
{
    
this.successor = successor;
  }

  
abstract public void HandleRequest( int request );
}


//  "ConcreteHandler1"
class  ConcreteHandler1 : Handler
{
  
// Methods
  override public void HandleRequest( int request )
  
{
    
if( request >= 0 && request < 10 )
      Console.WriteLine(
"{0} handled request {1}",
        
this, request );
    
else
      
if( successor != null )
      successor.HandleRequest( request );
  }

}


//  "ConcreteHandler2"
class  ConcreteHandler2 : Handler
{
  
// Methods
  override public void HandleRequest( int request )
  
{
    
if( request >= 10 && request < 20 )
      Console.WriteLine(
"{0} handled request {1}",
        
this, request );
    
else
      
if( successor != null )
      successor.HandleRequest( request );
  }

}


//  "ConcreteHandler3"
class  ConcreteHandler3 : Handler
{
  
// Methods
  override public void HandleRequest( int request )
  
{
    
if( request >= 20 && request < 30 )
      Console.WriteLine(
"{0} handled request {1}",
        
this, request );
    
else
      
if( successor != null )
      successor.HandleRequest( request );
  }

}


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

public   class  Client
{
  
public static void Main( string[] args )
  
{
    
// Setup Chain of Responsibility
    Handler h1 = new ConcreteHandler1();
    Handler h2 
= new ConcreteHandler2();
    Handler h3 
= new ConcreteHandler3();
    h1.SetSuccessor(h2);
    h2.SetSuccessor(h3);

    
// Generate and process request