简明设计模式

设计模式 收藏

转贴:http://www.cnblogs.com/zhenyulu/articles/36058.html

 

来源:亚历山大的建筑模式、Gamma等人(1995)创作的"Design Patterns: Elements of Reusable Software"。这本书通常被称作"Gang of Four"或"GoF",开创性的创造了《设计模式》。

 

也有人说"三十六计"就是"模式"。

 

 

一、 "开放-封闭"原则(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();

   }

}

 

 

 

 

 

 

 

C#设计模式(3)

 

三、 依赖倒置原则(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模式

 

使民无知

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

所谓"最少知识"原则,实际上便是老子的"使民无知"的统治之术。

 

不相往来

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

 

 

 

 

 

 

 

 

 

C#设计模式(4)-Simple Factory Pattern 

 

 

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

 

简单工厂(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)相近,但是有区别。

 

 

 

五、 优点与缺点:

优点:

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

 

缺点:

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

 

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

 

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

 

 

 

 

 

 

 

 

C#设计模式(5)-Factory Method Pattern 

 

一、 工厂方法(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

  {

    get{ return 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 );

    }

  }

}

 

 

 

 

 

 

 

C#设计模式(6)-Abstract Factory Pattern 

 

、 抽象工厂(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很好的支持了"开放-封闭"原则。

 

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

 

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

 

 

 

 

 

 

C#设计模式(7)-Singleton Pattern 

 

一、 单例(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实现,就模式、线程、效率、延迟初始化等很多方面进行了详细论述。

 

 

 

 

C#设计模式(8)-Builder Pattern 

 

一、 建造者(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 -------" );

    foreach( string 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

  {

    get{ return 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 this[ string key ]

  {

    get{ return 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、 模式所建造的最终产品更易于控制。

 

 

 

 

 

 

 

 

C#设计模式(9)-Prototype Pattern 

、 原型(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

  {

    get{ return 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 this[ string name ]

  {

    get{ return (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( 255, 0, 0 );

    colormanager[ "green" ] = new Color( 0, 255, 0 );

    colormanager[ "blue" ] = new Color( 0, 0, 255 );

 

    // User adds personalized colors

    colormanager[ "angry" ] = new Color( 255, 54, 0 );

    colormanager[ "peace" ] = new Color( 128, 211, 128 );

    colormanager[ "flame" ] = new Color( 211, 34, 20 );

 

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

 

 

 

 

 

 

 

C#设计模式(10)-Adapter Pattern 

 

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

 

 

 

 

 

C#设计模式(11)-Composite Pattern 

 

 

一、 合成(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 );

  }

}

 

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

明显的给出父对象的引用。在子对象里面给出父对象的引用,可以很容易的遍历所有父对象。有了这个引用,可以方便的应用责任链模式。

在通常的系统里,可以使用享元模式实现构件的共享,但是由于合成模式的对象经常要有对父对象的引用,因此共享不容易实现。

有时候系统需要遍历一个树枝结构的子构件很多次,这时候可以考虑把遍历子构件的结果暂时存储在父构件里面作为缓存。

关于使用什么数据类型来存储子对象的问题,在示意性的代码中使用了ArrayList,在实际系统中可以使用其它聚集或数组等。

客户端尽量不要直接调用树叶类中的方法,而是借助其父类(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 );

  }

}

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

 

 

 

 

 

 

 

C#设计模式(12)-Decorator Pattern 

 

一、 装饰(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()

         )

      )

   )

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

 

 

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

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

 

需要扩展一个类的功能,或给一个类增加附加责任。

需要动态地给一个对象增加功能,这些功能可以再动态地撤销。

需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实。

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

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

 

// Decorator pattern -- Real World example  

using System;

using System.Collections;

 

// "Component"

abstract class LibraryItem

{

  // Fields

  private int numCopies;

 

  // Properties

  public int NumCopies

  {

    get{ return 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();

    foreach( string 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", 23, 60 );

    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)尽量保持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();

 

 

 

 

 

C#设计模式(13)-Proxy Pattern 

 

一、 代理(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",null, null );

    ObjectHandle o = ad.CreateInstance("Proxy_RealWorld", "Math", false,

      System.Reflection.BindingFlags.CreateInstance, null, null, null,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( 4, 2 ) );

    Console.WriteLine( "4 - 2 = {0}", p.Sub( 4, 2 ) );

    Console.WriteLine( "4 * 2 = {0}", p.Mul( 4, 2 ) );

    Console.WriteLine( "4 / 2 = {0}", p.Div( 4, 2 ) );

  }

}

 

 

 

 

 

 

 

设计模式(15)-Facade Pattern 

 

一、 门面(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();

  }

}

 

 

 

 

设计模式(16)-Bridge Pattern

 

、 桥梁(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; }

    get{ return 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()

  {

    foreach( string 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();

  }

}

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

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

 

如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的联系。

设计要求实现化角色的任何改变不应当影响客户端,或者说实现化角色的改变对客户端是完全透明的。

一个构件有多于一个的抽象化角色和实现化角色,系统需要它们之间进行动态耦合。

虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。

 

 

 

 

 

 

设计模式(17)-Chain of Responsibility Pattern 

 

行为模式(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

    int[] requests = { 2, 5, 14, 22, 18, 3, 27, 20 };

 

    foreach( int request in requests )

      h1.HandleRequest( request );

 

  }

}

 

四、 纯的与不纯的责任链模式

一个纯的责任链模式要求一个具体的处理者对象只能在两个行为中选择一个:一个是承担责任,二是把责任推给下家。不允许出现某一个具体处理者对象在承担了一部分责任后又把责任向下传的情况。

 

在一个纯的责任链模式里面,一个请求必须被某一个处理者对象所接收;在一个不纯的责任链模式里面,一个请求可以最终不被任何接收端对象所接收。纯的责任链模式的例子是不容易找到的,一般看到的例子均是不纯的责任链模式的实现。

 

 

五、 责任链模式的实际应用案例

下面的责任链模式代码演示了不同职务的人根据所设定的权限对一个购买请求作出决策或将其交给更高的决策者。

 

// Chain of Responsibility pattern -- Real World example  

using System;

 

// "Handler"

abstract class Approver

{

  // Fields

  protected string name;

  protected Approver successor;

 

  // Constructors

  public Approver( string name )

  {

    this.name = name;

  }

 

  // Methods

  public void SetSuccessor( Approver successor )

  {

    this.successor = successor;

  }

 

  abstract public void ProcessRequest( PurchaseRequest request );

}

 

// "ConcreteHandler"

class Director : Approver

{

  // Constructors

  public Director ( string name ) : base( name ) {}

 

  // Methods

  override public void ProcessRequest( PurchaseRequest request )

  {

    if( request.Amount < 10000.0 )

      Console.WriteLine( "{0} {1} approved request# {2}",

        this, name, request.Number);

    else

      if( successor != null )

      successor.ProcessRequest( request );

  }

}

 

// "ConcreteHandler"

class VicePresident : Approver

{

  // Constructors

  public VicePresident ( string name ) : base( name ) {}

 

  // Methods

  override public void ProcessRequest( PurchaseRequest request )

  {

    if( request.Amount < 25000.0 )

      Console.WriteLine( "{0} {1} approved request# {2}",

        this, name, request.Number);

    else

      if( successor != null )

      successor.ProcessRequest( request );

  }

}

 

// "ConcreteHandler"

class President : Approver

{

  // Constructors

  public President ( string name ) : base( name ) {}

  // Methods

  override public void ProcessRequest( PurchaseRequest request )

  {

    if( request.Amount < 100000.0 )

      Console.WriteLine( "{0} {1} approved request# {2}",

        this, name, request.Number);

    else

      Console.WriteLine( "Request# {0} requires " +

        "an executive meeting!", request.Number );

  }

}

 

// Request details

class PurchaseRequest

{

  // Member Fields

  private int number;

  private double amount;

  private string purpose;

 

  // Constructors

  public PurchaseRequest( int number,  

    double amount, string purpose )

  {

    this.number = number;

    this.amount = amount;

    this.purpose = purpose;

  }

 

  // Properties

  public double Amount

  {

    get{ return amount; }

    set{ amount = value; }

  }

 

  public string Purpose

  {

    get{ return purpose; }

    set{ purpose = value; }

  }

 

  public int Number

  {

    get{ return number; }

    set{ number = value; }

  }

}

 

/// <summary>

///  ChainApp Application

/// </summary>

public class ChainApp

{

  public static void Main( string[] args )

  {

    // Setup Chain of Responsibility

    Director Larry = new Director( "Larry" );

    VicePresident Sam = new VicePresident( "Sam" );

    President Tammy = new President( "Tammy" );

    Larry.SetSuccessor( Sam );

    Sam.SetSuccessor( Tammy );

 

    // Generate and process different requests

    PurchaseRequest rs = new PurchaseRequest( 2034, 350.00, "Supplies" );

    Larry.ProcessRequest( rs );

 

    PurchaseRequest rx = new PurchaseRequest( 2035, 32590.10, "Project X" );

    Larry.ProcessRequest( rx );

 

    PurchaseRequest ry = new PurchaseRequest( 2036, 122100.00, "Project Y" );

    Larry.ProcessRequest( ry );

  }

}

六、 责任链模式的实现

责任链模式并不创建责任链。责任链的创建必须由系统的其它部分创建出来。

 

责任链模式降低了请求的发送端和接收端之间的耦合,使多个对象都有机会处理这个请求。一个链可以是一条线,一个树,也可以是一个环。如下图所示,责任链是一个树结构的一部分。

 

 

 

 

 

设计模式(18)-Command Pattern 

 

 

一、 命令(Command)模式

命令(Command)模式属于对象的行为模式【GOF95】。命令模式又称为行动(Action)模式或交易(Transaction)模式。命 令模式把一个请求或者操作封装到一个对象中。命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功 能。

 

命令模式是对命令的封装。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。

 

每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行操作。命令模式允许请求的一方和接收的一方独立开来,使 得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。

 

 

二、 命令模式的结构

命令模式的类图如下:

 

 

 

命令模式涉及到五个角色,它们分别是:

 

客户(Client)角色:创建了一个具体命令(ConcreteCommand)对象并确定其接收者。

命令(Command)角色:声明了一个给所有具体命令类的抽象接口。这是一个抽象角色。

具体命令(ConcreteCommand)角色:定义一个接受者和行为之间的弱耦合;实现Execute()方法,负责调用接收考的相应操作。Execute()方法通常叫做执方法。

请求者(Invoker)角色:负责调用命令对象执行请求,相关的方法叫做行动方法。

接收者(Receiver)角色:负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。

三、 命令模式的示意性源代码

// Command pattern -- Structural example   

using System; 

 

// "Command" 

abstract class Command 

  // Fields 

  protected Receiver receiver; 

 

  // Constructors 

  public Command( Receiver receiver ) 

  { 

    this.receiver = receiver; 

  } 

 

  // Methods 

  abstract public void Execute(); 

 

// "ConcreteCommand" 

class ConcreteCommand : Command 

  // Constructors 

  public ConcreteCommand( Receiver receiver ) : 

    base ( receiver ) {} 

 

  // Methods 

  public override void Execute() 

  { 

    receiver.Action(); 

  } 

 

// "Receiver" 

class Receiver 

  // Methods 

  public void Action() 

  { 

    Console.WriteLine("Called Receiver.Action()"); 

  } 

 

// "Invoker" 

class Invoker 

  // Fields 

  private Command command; 

 

  // Methods 

  public void SetCommand( Command command ) 

  { 

    this.command = command; 

  } 

 

  public void ExecuteCommand() 

  { 

    command.Execute(); 

  } 

 

/// <summary> 

///  Client test 

/// </summary> 

public class Client 

  public static void Main( string[] args ) 

  { 

    // Create receiver, command, and invoker 

    Receiver r = new Receiver(); 

    Command c = new ConcreteCommand( r ); 

    Invoker i = new Invoker(); 

 

    // Set and execute command 

    i.SetCommand(c); 

    i.ExecuteCommand(); 

  } 

}

四、 玉帝传美猴王上天

命令模式不是新的发明,在美猴王大闹天宫之前就有了。那时玉帝命令太白金星召美猴王上天:"金星径入(水帘洞)当中,面南立定道:'我是西方太白金 星,奉玉帝招安圣旨,下界请你上大,拜受仙录。'"玉帝是系统的客户端,太白金星是命令的发出者,猴王是命令的接收者,圣旨就是命令。玉帝的这一道命令就 是要求猴王到上界报到。玉帝只管发出命令,而不管命令是怎样传达到美猴王的。太白金星负责将圣旨传到,可是美猴王怎么执行圣旨、何时执行圣旨是美猴王自己 的事。果不然,个久美猴王就大闹了天宫。

 

这个模拟系统的设计如下:

 

 

 

 

五、 命令模式的实现

首先命令应当"重"一些还是"轻"一些。在不同的情况下,可以做不同的选择。如果把命令设计得"轻",那么它只是提供了一个请求者和接收者之间的耦合而己,命令代表请求者实现请求。

 

相反,如果把命令设计的"重",那么它就应当实现所有的细节,包括请求所代表的操作,而不再需要接收者了。当一个系统没有接收者时,就可以采用这种做法。

 

更常见的是处于最"轻"和最"重"的两个极端之间时情况。命令类动态地决定调用哪一个接收者类。

 

其次是否支持undo和redo。如果一个命令类提供一个方法,比如叫unExecute(),以恢复其操作的效果,那么命令类就可以支持undo和redo。具体命令类需要存储状态信息,包括:

 

1. 接收者对象实际上实施请求所代表的操作; 

2. 对接收者对象所作的操作所需要的参数; 

3. 接收者类的最初的状态。接收者必须提供适当的方法,使命令类可以通过调用这个方法,以便接收者类恢复原有状态。

 

如果只需要提供一层的undo和redo,那么系统只需要存储最后被执行的那个命令对象。如果需要支持多层的undo和redo,那么系统就需要存 储曾经被执行过的命令的清单,清单能允许的最大的长度便是系统所支持的undo和redo的层数。沿着清单逆着执行清单上的命令的反命令 (unExecute())便是undo;沿着清单顺着执行清单上的命令便是redo。

 

 

六、 命令模式的实际应用案例

下面的代码使用命令模式演示了一个简单的计算器,并允许执行undo与redo。注意:"operator"在C#中是关键词,所以在前面添加一个"@"将其变为标识符。

 

// Command pattern -- Real World example   

using System; 

using System.Collections; 

 

// "Command" 

abstract class Command 

  // Methods 

  abstract public void Execute(); 

  abstract public void UnExecute(); 

 

// "ConcreteCommand" 

class CalculatorCommand : Command 

  // Fields 

  char @operator; 

  int operand; 

  Calculator calculator; 

 

  // Constructor 

  public CalculatorCommand( Calculator calculator, 

    char @operator, int operand ) 

  { 

    this.calculator = calculator; 

    this.@operator = @operator; 

    this.operand = operand; 

  } 

 

  // Properties 

  public char Operator 

  { 

    set{ @operator = value; } 

  } 

 

  public int Operand 

  { 

    set{ operand = value; } 

  } 

 

  // Methods 

  override public void Execute() 

  { 

    calculator.Operation( @operator, operand ); 

  } 

 

  override public void UnExecute() 

  { 

    calculator.Operation( Undo( @operator ), operand ); 

  } 

 

  // Private helper function 

  private char Undo( char @operator ) 

  { 

    char undo = ' '; 

    switch( @operator ) 

    { 

      case '+': undo = '-'; break; 

      case '-': undo = '+'; break; 

      case '*': undo = '/'; break; 

      case '/': undo = '*'; break; 

    } 

    return undo; 

  } 

 

// "Receiver" 

class Calculator 

  // Fields 

  private int total = 0; 

 

  // Methods 

  public void Operation( char @operator, int operand ) 

  { 

    switch( @operator ) 

    { 

      case '+': total += operand; break; 

      case '-': total -= operand; break; 

      case '*': total *= operand; break; 

      case '/': total /= operand; break; 

    } 

    Console.WriteLine( "Total = {0} (following {1} {2})", 

      total, @operator, operand ); 

  } 

 

// "Invoker" 

class User 

  // Fields 

  private Calculator calculator = new Calculator(); 

  private ArrayList commands = new ArrayList(); 

  private int current = 0; 

 

  // Methods 

  public void Redo( int levels ) 

  { 

    Console.WriteLine( "---- Redo {0} levels ", levels ); 

    // Perform redo operations 

    for( int i = 0; i < levels; i++ ) 

      if( current < commands.Count - 1 ) 

        ((Command)commands[ current++ ]).Execute(); 

  } 

 

  public void Undo( int levels ) 

  { 

    Console.WriteLine( "---- Undo {0} levels ", levels ); 

    // Perform undo operations 

    for( int i = 0; i < levels; i++ ) 

      if( current > 0 ) 

        ((Command)commands[ --current ]).UnExecute(); 

  } 

 

  public void Compute( char @operator, int operand ) 

  { 

    // Create command operation and execute it 

    Command command = new CalculatorCommand( 

      calculator, @operator, operand ); 

    command.Execute(); 

 

    // Add command to undo list 

    commands.Add( command ); 

    current++; 

  } 

 

/// <summary> 

/// CommandApp test 

/// </summary> 

public class Client 

  public static void Main( string[] args ) 

  { 

    // Create user and let her compute 

    User user = new User(); 

 

    user.Compute( '+', 100 ); 

    user.Compute( '-', 50 ); 

    user.Compute( '*', 10 ); 

    user.Compute( '/', 2 ); 

 

    // Undo and then redo some commands 

    user.Undo( 4 ); 

    user.Redo( 3 ); 

  } 

}

 

七、 在什么情况下应当使用命令模式

在下面的情况下应当考虑使用命令模式:

 

1、使用命令模式作为"CallBack"在面向对象系统中的替代。"CallBack"讲的便是先将一个函数登记上,然后在以后调用此函数。

 

2、需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令 对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。

 

3、系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。

 

4、如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用Execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。

 

5、一个系统需要支持交易(Transaction)。一个交易结构封装了一组数据更新命令。使用命令模式来实现交易结构可以使系统增加新的交易类型。

 

 

八、 使用命令模式的优点和缺点

命令允许请求的一方和接收请求的一方能够独立演化,从而且有以下的优点:

 

命令模式使新的命令很容易地被加入到系统里。

允许接收请求的一方决定是否要否决(Veto)请求。

能较容易地设计-个命令队列。

可以容易地实现对请求的Undo和Redo。

在需要的情况下,可以较容易地将命令记入日志。

命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。

命令类与其他任何别的类一样,可以修改和推广。

你可以把命令对象聚合在一起,合成为合成命令。比如宏命令便是合成命令的例子。合成命令是合成模式的应用。

由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。

命令模式的缺点如下:

 

使用命令模式会导致某些系统有过多的具体命令类。某些系统可能需要几十个,几百个甚至几千个具体命令类,这会使命令模式在这样的系统里变得不实际。

 

 

 

设计模式(19)-Observer Pattern 

 

一、 观察者(Observer)模式

观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

 

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

 

一个软件系统常常要求在某一个对象的状态发生变化的时候,某些其它的对象做出相应的改变。做到这一点的设计方案有很多,但是为了使系统能够易于复 用,应该选择低耦合度的设计方案。减少对象之间的耦合有利于系统的复用,但是同时设计师需要使这些低耦合度的对象之间能够维持行动的协调一致,保证高度的 协作(Collaboration)。观察者模式是满足这一要求的各种设计方案中最重要的一种。

 

 

二、 观察者模式的结构

观察者模式的类图如下:

 

 

 

可以看出,在这个观察者模式的实现里有下面这些角色:

 

抽象主题(Subject)角色:主题角色把所有对观察考对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,主题角色又叫做抽象被观察者(Observable)角色,一般用一个抽象类或者一个接口实现。

抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者角色一般用一个抽象类或者一个接口实现。在这个示意性的实现中,更新接口只包含一个方法(即Update()方法),这个方法叫做更新方法。

具体主题(ConcreteSubject)角色:将有关状态存入具体现察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者角色(Concrete Observable)。具体主题角色通常用一个具体子类实现。

具体观察者(ConcreteObserver)角色:存储与主题的状态自恰的状态。具体现察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体现察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。

从具体主题角色指向抽象观察者角色的合成关系,代表具体主题对象可以有任意多个对抽象观察者对象的引用。之所以使用抽象观察者而不是具体观察者,意 味着主题对象不需要知道引用了哪些ConcreteObserver类型,而只知道抽象Observer类型。这就使得具体主题对象可以动态地维护一系列 的对观察者对象的引用,并在需要的时候调用每一个观察者共有的Update()方法。这种做法叫做"针对抽象编程"。

 

 

三、 观察者模式的示意性源代码

// Observer pattern -- Structural example  

using System;

using System.Collections;

 

// "Subject"

abstract class Subject

{

  // Fields

  private ArrayList observers = new ArrayList();

 

  // Methods

  public void Attach( Observer observer )

  {

    observers.Add( observer );

  }

 

  public void Detach( Observer observer )

  {

    observers.Remove( observer );

  }

 

  public void Notify()

  {

    foreach( Observer o in observers )

      o.Update();

  }

}

 

// "ConcreteSubject"

class ConcreteSubject : Subject

{

  // Fields

  private string subjectState;

 

  // Properties

  public string SubjectState

  {

    get{ return subjectState; }

    set{ subjectState = value; }

  }

}

 

// "Observer"

abstract class Observer

{

  // Methods

  abstract public void Update();

}

 

// "ConcreteObserver"

class ConcreteObserver : Observer

{

  // Fields

  private string name;

  private string observerState;

  private ConcreteSubject subject;

 

  // Constructors

  public ConcreteObserver( ConcreteSubject subject,  

    string name )

  {

    this.subject = subject;

    this.name = name;

  }

 

  // Methods

  override public void Update()

  {

    observerState = subject.SubjectState;

    Console.WriteLine( "Observer {0}'s new state is {1}",

      name, observerState );

  }

 

  // Properties

  public ConcreteSubject Subject

  {

    get { return subject; }

    set { subject = value; }

  }

}

 

/// <summary>

/// Client test

/// </summary>

public class Client

{

  public static void Main( string[] args )

  {

    // Configure Observer structure

    ConcreteSubject s = new ConcreteSubject();

    s.Attach( new ConcreteObserver( s, "1" ) );

    s.Attach( new ConcreteObserver( s, "2" ) );

    s.Attach( new ConcreteObserver( s, "3" ) );

 

    // Change subject and notify observers

    s.SubjectState = "ABC";

    s.Notify();

  }

}

 

四、 C#中的Delegate与Event

实际上在C#中实现Observer模式没有这么辛苦,.NET中提供了Delegate与Event机制,我们可以利用这种机制简化Observer模式。关于Delegate与Event的使用方法请参考相关文档。改进后的Observer模式实现如下:

 

// Observer pattern -- Structural example  

using System;

 

//Delegate

delegate void UpdateDelegate(); 

 

//Subject

class Subject

{

  public event UpdateDelegate UpdateHandler;

 

  // Methods

  public void Attach( UpdateDelegate ud )

  {

    UpdateHandler += ud;

  }

 

  public void Detach( UpdateDelegate ud )

  {

    UpdateHandler -= ud;

  }

 

  public void Notify()

  {

    if(UpdateHandler != null) UpdateHandler();

  }

 

}

 

//ConcreteSubject

class ConcreteSubject : Subject

{

  // Fields

  private string subjectState;

 

  // Properties

  public string SubjectState

  {

    get{ return subjectState; }

    set{ subjectState = value; }

  }

}

 

// "ConcreteObserver"

class ConcreteObserver

{

  // Fields

  private string name;

  private string observerState;

  private ConcreteSubject subject;

 

  // Constructors

  public ConcreteObserver( ConcreteSubject subject,  

    string name )

  {

    this.subject = subject;

    this.name = name;

  }

 

  // Methods

  public void Update()

  {

    observerState = subject.SubjectState;

    Console.WriteLine( "Observer {0}'s new state is {1}",

      name, observerState );

  }

 

  // Properties

  public ConcreteSubject Subject

  {

    get { return subject; }

    set { subject = value; }

  }

}

 

// "ConcreteObserver"

class AnotherObserver

{

  // Methods

  public void Show()

  {

    Console.WriteLine("AnotherObserver got an Notification!");

  }

}

 

public class Client

  public static void Main(string[] args)

  { 

    ConcreteSubject s = new ConcreteSubject();

    ConcreteObserver o1 = new ConcreteObserver(s, "1");

    ConcreteObserver o2 = new ConcreteObserver(s, "2");

    AnotherObserver o3 = new AnotherObserver();

 

    s.Attach(new UpdateDelegate(o1.Update));

    s.Attach(new UpdateDelegate(o2.Update));

    s.Attach(new UpdateDelegate(o3.Show));

 

    s.SubjectState = "ABC";

    s.Notify();

 

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

    s.Detach(new UpdateDelegate(o1.Update));

 

    s.SubjectState = "DEF";

    s.Notify();

  }

}

其中,关键的代码如下:

 

delegate void UpdateDelegate(); 

定义一个Delegate,用来规范函数结构。不管是ConcreteObserver类的Update方法还是AnotherObserver类 的Show方法都符合该Delegate。这不象用Observer接口来规范必须使用Update方法那么严格。只要符合Delegate所指定的方法 结构的方法都可以在后面被事件所处理。

 

public event UpdateDelegate UpdateHandler;

定义一个事件,一旦触发,可以调用一组符合UpdateDelegate规范的方法。

 

  public void Attach( UpdateDelegate ud )

  {

    UpdateHandler += ud;

  }

订阅事件。只要是一个满足UpdateDelegate的方法,就可以进行订阅操作(如下所示)。

 

    s.Attach(new UpdateDelegate(o1.Update));

    s.Attach(new UpdateDelegate(o2.Update));

    s.Attach(new UpdateDelegate(o3.Show));

在Notify方法中:

 

  public void Notify()

  {

    if(UpdateHandler != null) UpdateHandler();

  }

只要UpdateHandler != null(表示有订阅者),就可以触发事件(UpdateHandler()),所有的订阅者便会接到通知。

 

 

五、 一个实际应用观察者模式的例子

该例子演示了注册的投资者在股票市场发生变化时,可以自动得到通知。该例子仍然使用的是传统的Observer处理手段,至于如何转换成Delegate与Event留给读者自己考虑。

 

// Observer pattern -- Real World example  

using System;

using System.Collections;

 

// "Subject"

abstract class Stock

{

  // Fields

  protected string symbol;

  protected double price;

  private ArrayList investors = new ArrayList();

 

  // Constructor

  public Stock( string symbol, double price )

  {

    this.symbol = symbol;

    this.price = price;

  }

 

  // Methods

  public void Attach( Investor investor )

  {

    investors.Add( investor );

  }

 

  public void Detach( Investor investor )

  {

    investors.Remove( investor );

  }

 

  public void Notify()

  {

    foreach( Investor i in investors )

      i.Update( this );

  }

 

  // Properties

  public double Price

  {

    get{ return price; }

    set

    {

      price = value;

      Notify(); 

    }

  }

 

  public string Symbol

  {

    get{ return symbol; }

    set{ symbol = value; }

  }

}

 

// "ConcreteSubject"

class IBM : Stock

{

  // Constructor

  public IBM( string symbol, double price )

    : base( symbol, price ) {}

}

 

// "Observer"

interface IInvestor

{

  // Methods

  void Update( Stock stock );

}

 

// "ConcreteObserver"

class Investor : IInvestor

{

  // Fields

  private string name;

  private string observerState;

  private Stock stock;

 

  // Constructors

  public Investor( string name )

  {

    this.name = name;

  }

 

  // Methods

  public void Update( Stock stock )

  {

    Console.WriteLine( "Notified investor {0} of {1}'s change to {2:C}", 

      name, stock.Symbol, stock.Price );

  }

 

  // Properties

  public Stock Stock

  {

    get{ return stock; }

    set{ stock = value; }

  }

}

 

/// <summary>

/// ObserverApp test

/// </summary>

public class ObserverApp

{

  public static void Main( string[] args )

  {

    // Create investors

    Investor s = new Investor( "Sorros" );

    Investor b = new Investor( "Berkshire" );

 

    // Create IBM stock and attach investors

    IBM ibm = new IBM( "IBM", 120.00 );

    ibm.Attach( s );

    ibm.Attach( b );

 

    // Change price, which notifies investors

    ibm.Price = 120.10;

    ibm.Price = 121.00;

    ibm.Price = 120.50;

    ibm.Price = 120.75;

  }

}

 

六、 观察者模式的优缺点

Observer模式的优点是实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,类别清晰,并抽象了更新接口,使得可以有各种各样不同的表示层(观察者)。

 

但是其缺点是每个外观对象必须继承这个抽像出来的接口类,这样就造成了一些不方便,比如有一个别人写的外观对象,并没有继承该抽象类,或者接口不 对,我们又希望不修改该类直接使用它。虽然可以再应用Adapter模式来一定程度上解决这个问题,但是会造成更加复杂烦琐的设计,增加出错几率。

 

观察者模式的效果有以下几个优点:

 

(1)观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体现察者聚集,每一个具体现察者都符合一个抽象观察者 的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象 化层次。

 

(2)观察者模式支持广播通信。被观察者会向所有的登记过的观察者发出通知。

 

观察者模式有下面的一些缺点:

 

(1)如果一个被观察者对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

 

(2)如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察考模式时要特别注意这一点。

 

(3)如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。

 

(4)虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。

 

 

 

 

 

设计模式(20)-Visitor Pattern 

 

 

一、 访问者(Visitor)模式

访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。

 

问题提出

 

System.Collection命名空间下提供了大量集合操作对象。但大多数情况下处理的都是同类对象的聚集。换言之,在聚集上采取的操作都是一些针对同类型对象的同类操作。但是如果针对一个保存有不同类型对象的聚集采取某种操作该怎么办呢?

 

粗看上去,这似乎不是什么难题。可是如果需要针对一个包含不同类型元素的聚集采取某种操作,而操作的细节根据元素的类型不同而有所不同时,就会出现必须对元素类型做类型判断的条件转移语句。这个时候,使用访问者模式就是一个值得考虑的解决方案。

 

访问者模式

 

访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。

 

数据结构的每一个节点都可以接受一个访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作。这样的过程叫做"双重分派"。节点调用访问者,将它自己传入,访问者则将某算法针对此节点执行。

 

双重分派意味着施加于节点之上的操作是基于访问者和节点本身的数据类型,而不仅仅是其中的一者。

 

 

二、 访问者模式的结构

如下图所示,这个静态图显示了有两个具体访问者和两个具体节点的访问者模式的设计,必须指出的是,具体访问者的数目与具体节点的数目没有任何关系,虽然在这个示意性的系统里面两者的数目都是两个。

 

 

 

访问者模式涉及到抽象访问者角色、具体访问者角色、抽象节点角色、具体节点角色、结构对象角色以及客户端角色。

 

抽象访问者(Visitor)角色:声明了一个或者多个访问操作,形成所有的具体元素角色必须实现的接口。

具体访问者(ConcreteVisitor)角色:实现抽象访问者角色所声明的接口,也就是抽象访问者所声明的各个访问操作。

抽象节点(Node)角色:声明一个接受操作,接受一个访问者对象作为一个参量。

具体节点(Node)角色:实现了抽象元素所规定的接受操作。

结构对象(ObiectStructure)角色:有如下的一些责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如列(List)或集合(Set)。

 

三、 示意性源代码

// Visitor pattern -- Structural example  

using System;

using System.Collections;

 

// "Visitor"

abstract class Visitor

{

  // Methods

  abstract public void VisitConcreteElementA(

    ConcreteElementA concreteElementA );

  abstract public void VisitConcreteElementB(

    ConcreteElementB concreteElementB );

}

 

// "ConcreteVisitor1"

class ConcreteVisitor1 : Visitor

{

  // Methods

  override public void VisitConcreteElementA(

    ConcreteElementA concreteElementA )

  {

    Console.WriteLine( "{0} visited by {1}",

      concreteElementA, this );

  }

 

  override public void VisitConcreteElementB(

    ConcreteElementB concreteElementB )

  {

    Console.WriteLine( "{0} visited by {1}",

      concreteElementB, this );

  }

}

 

// "ConcreteVisitor2"

class ConcreteVisitor2 : Visitor

{

  // Methods

  override public void VisitConcreteElementA(

    ConcreteElementA concreteElementA )

  {

    Console.WriteLine( "{0} visited by {1}",

      concreteElementA, this );

  }

  override public void VisitConcreteElementB(

    ConcreteElementB concreteElementB )

  {

    Console.WriteLine( "{0} visited by {1}",

      concreteElementB, this );

  }

}

 

// "Element"

abstract class Element

{

  // Methods

  abstract public void Accept( Visitor visitor );

}

 

// "ConcreteElementA"

class ConcreteElementA : Element

{

  // Methods

  override public void Accept( Visitor visitor )

  {

    visitor.VisitConcreteElementA( this );

  }

 

  public void OperationA()

  {

  }

}

 

// "ConcreteElementB"

class ConcreteElementB : Element

{

  // Methods

  override public void Accept( Visitor visitor )

  {

    visitor.VisitConcreteElementB( this );

  }

 

  public void OperationB()

  {

  }

}

 

// "ObjectStructure"

class ObjectStructure

{

  // Fields

  private ArrayList elements = new ArrayList();

 

  // Methods

  public void Attach( Element element )

  {

    elements.Add( element );

  }

 

  public void Detach( Element element )

  {

    elements.Remove( element );

  }

 

  public void Accept( Visitor visitor )

  {

    foreach( Element e in elements )

      e.Accept( visitor );

  }

}

 

/// <summary>

/// Client test

/// </summary>

public class Client

{

  public static void Main( string[] args )

  {

    // Setup structure

    ObjectStructure o = new ObjectStructure();

    o.Attach( new ConcreteElementA() );

    o.Attach( new ConcreteElementB() );

 

    // Create visitor objects

    ConcreteVisitor1 v1 = new ConcreteVisitor1();

    ConcreteVisitor2 v2 = new ConcreteVisitor2();

 

    // Structure accepting visitors

    o.Accept( v1 );

    o.Accept( v2 );

  }

}

结构对象会遍历它自己所保存的聚集中的所有节点,在本系统中就是节点ConcreteElementA和节点ConcreteElementB。首先ConcreteElementA会被访问到,这个访问是由以下的操作组成的:

 

ConcreteElementA对象的接受方法被调用,并将VisitorA对象本身传入;

ConcreteElementA对象反过来调用VisitorA对象的访问方法,并将ConcreteElementA对象本身传入;

VisitorA对象调用ConcreteElementA对象的商业方法operationA( )。

从而就完成了双重分派过程,接着,ConcreteElementB会被访问,这个访问的过程和ConcreteElementA被访问的过程是一样的。

 

因此,结构对象对聚集元素的遍历过程就是对聚集中所有的节点进行委派的过程,也就是双重分派的过程。换言之,系统有多少个节点就会发生多少个双重分派过程。

 

 

四、 一个实际应用Visitor模式的例子

以下的例子演示了Employee对象集合允许被不同的Visitor(IncomeVisitor与VacationVisitor)访问其中的内容。

 

// Visitor pattern -- Real World example  

using System;

using System.Collections;

 

// "Visitor"

abstract class Visitor

{

  // Methods

  abstract public void Visit( Element element );

}

 

// "ConcreteVisitor1"

class IncomeVisitor : Visitor

{

  // Methods

  public override void Visit( Element element )

  {

    Employee employee = ((Employee)element);

 

    // Provide 10% pay raise

    employee.Income *= 1.10;

    Console.WriteLine( "{0}'s new income: {1:C}",

      employee.Name, employee.Income );

  }

}

 

// "ConcreteVisitor2"

class VacationVisitor : Visitor

{

  public override void Visit( Element element )

  {

    Employee employee = ((Employee)element);

 

    // Provide 3 extra vacation days

    employee.VacationDays += 3;

    Console.WriteLine( "{0}'s new vacation days: {1}",

      employee.Name, employee.VacationDays );

  }

}

 

// "Element"

abstract class Element

{

  // Methods

  abstract public void Accept( Visitor visitor );

}

 

// "ConcreteElement"

class Employee : Element

{

  // Fields

  string name;

  double income;

  int vacationDays;

 

  // Constructors

  public Employee( string name, double income,

    int vacationDays )

  {

    this.name = name;

    this.income = income;

    this.vacationDays = vacationDays;

  }

 

  // Properties

  public string Name

  {

    get{ return name; }

    set{ name = value; }

  }

 

  public double Income

  {

    get{ return income; }

    set{ income = value; }

  }

 

  public int VacationDays

  {

    get{ return vacationDays; }

    set{ vacationDays = value; }

  }

 

  // Methods

  public override void Accept( Visitor visitor )

  {

    visitor.Visit( this );

  }

}

 

// "ObjectStructure"

class Employees

{

  // Fields

  private ArrayList employees = new ArrayList();

 

  // Methods

  public void Attach( Employee employee )

  {

    employees.Add( employee );

  }

 

  public void Detach( Employee employee )

  {

    employees.Remove( employee );

  }

 

  public void Accept( Visitor visitor )

  {

    foreach( Employee e in employees )

      e.Accept( visitor );

  }

}

 

/// <summary>

/// VisitorApp test

/// </summary>

public class VisitorApp

{

  public static void Main( string[] args )

  {

    // Setup employee collection

    Employees e = new Employees();

    e.Attach( new Employee( "Hank", 25000.0, 14 ) );

    e.Attach( new Employee( "Elly", 35000.0, 16 ) );

    e.Attach( new Employee( "Dick", 45000.0, 21 ) );

 

    // Create two visitors

    IncomeVisitor v1 = new IncomeVisitor();

    VacationVisitor v2 = new VacationVisitor();

 

    // Employees are visited

    e.Accept( v1 );

    e.Accept( v2 );

  }

}

五、 在什么情况下应当使用访问者模式

有意思的是,在很多情况下不使用设计模式反而会得到一个较好的设计。换言之,每一个设计模式都有其不应当使用的情况。访问者模式也有其不应当使用的情况,让我们

先看一看访问者模式不应当在什么情况下使用。

 

倾斜的可扩展性

 

访问者模式仅应当在被访问的类结构非常稳定的情况下使用。换言之,系统很少出现需要加入新节点的情况。如果出现需要加入新节点的情况,那么就必须在每一个访问对象里加入一个对应于这个新节点的访问操作,而这是对一个系统的大规模修改,因而是违背"开一闭"原则的。

 

访问者模式允许在节点中加入新的方法,相应的仅仅需要在一个新的访问者类中加入此方法,而不需要在每一个访问者类中都加入此方法。

 

显然,访问者模式提供了倾斜的可扩展性设计:方法集合的可扩展性和类集合的不可扩展性。换言之,如果系统的数据结构是频繁变化的,则不适合使用访问者模式。

 

"开一闭"原则和对变化的封装

 

面向对象的设计原则中最重要的便是所谓的"开一闭"原则。一个软件系统的设计应当尽量做到对扩展开放,对修改关闭。达到这个原则的途径就是遵循"对变化的封装"的原则。这个原则讲的是在进行软件系统的设计时,应当设法找出一个软件系统中会变化的部分,将之封装起来。

 

很多系统可以按照算法和数据结构分开,也就是说一些对象含有算法,而另一些对象含有数据,接受算法的操作。如果这样的系统有比较稳定的数据结构,又有易于变化的算法的话,使用访问者模式就是比较合适的,因为访问者模式使得算法操作的增加变得容易。

 

反过来,如果这样一个系统的数据结构对象易于变化,经常要有新的数据对象增加进来的话,就不适合使用访问者模式。因为在访问者模式中增加新的节点很困难,要涉及到在抽象访问者和所有的具体访问者中增加新的方法。

 

 

六、 使用访问者模式的优点和缺点

访问者模式有如下的优点:

 

访问者模式使得增加新的操作变得很容易。如果一些操作依赖于一个复杂的结构对象的话,那么一般而言,增加新的操作会很复杂。而使用访问者模式,增加新的操作就意味着增加一个新的访问者类,因此,变得很容易。

访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。

访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类。迭代子只能访问属于同一个类型等级结构的成员对象,而不能访问属于不同等级结构的对象。访问者模式可以做到这一点。

积累状态。每一个单独的访问者对象都集中了相关的行为,从而也就可以在访问的过程中将执行操作的状态积累在自己内部,而不是分散到很多的节点对象中。这是有益于系统维护的优点。

访问者模式有如下的缺点:

 

增加新的节点类变得很困难。每增加一个新的节点都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作。

破 坏封装。访问者模式要求访问者对象访问并调用每一个节点对象的操作,这隐含了一个对所有节点对象的要求:它们必须暴露一些自己的操作和内部状态。不然,访 问者的访问就变得没有意义。由于访问者对象自己会积累访问操作所需的状态,从而使这些状态不再存储在节点对象中,这也是破坏封装的。

 

 

 

 

 

 

设计模式(21)-Template Method Pattern 

、 模板方法(Template Method)模式

 

准备一个抽象类,将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模版方法模式的用意。

 

很多人可能没有想到,模版方法模式实际上是所有模式中最为常见的几个模式之一,而且很多人可能使用过模版方法模式而没有意识到自己已经使用了这个模式。模版方法模式是基于继承的代码复用的基本技术,模版方法模式的结构和用法也是面向对象设计的核心。

 

模版方法模式需要开发抽象类和具体子类的设计师之间的协作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步 骤。代表这些具体逻辑步骤的方法称做基本方法(primitive method);而将这些基本法方法总汇起来的方法叫做模版方法(template method),这个设计模式的名字就是从此而来。

 

 

二、 模版方法模式的结构

模版方法模式的静态结构如下图所示。

 

 

 

这里涉及到两个角色:

 

抽象模版(AbstractClass)角色有如下的责任:

定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤。

 

定义并实现了一个模版方法。这个模版方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。

 

具体模版(ConcreteClass)角色有如下的责任:

实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。

 

每一个抽象模版角色都可以有任意多个具体模版角色与之对应,而每一个具体模版角色都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。

 

 

三、 模板方法模式的示意性代码

// Template Method pattern -- Structural example  

using System;

 

// "AbstractClass"

abstract class AbstractClass

{

  // Methods

  abstract public void PrimitiveOperation1();

  abstract public void PrimitiveOperation2();

 

  // The Template method

  public void TemplateMethod()

  {

    Console.WriteLine("In AbstractClass.TemplateMethod()");

    PrimitiveOperation1();

    PrimitiveOperation2();

  }

}

 

// "ConcreteClass"

class ConcreteClass : AbstractClass

{

  // Methods

  public override void PrimitiveOperation1()

  {

    Console.WriteLine("Called ConcreteClass.PrimitiveOperation1()");

  }

 

  public override void PrimitiveOperation2()

  {

    Console.WriteLine("Called ConcreteClass.PrimitiveOperation2()");

  }

}

 

/// <summary>

/// Client test

/// </summary>

public class Client

{

  public static void Main( string[] args )

  {

    // Create instance and call template method

    ConcreteClass c = new ConcreteClass();

    c.TemplateMethod();

  }

}

 

四、 继承作为复用的工具

使用继承作为复用的手段必须慎重,C#语言的设计师对使用继承作为复用的工具有着不同层次上的认识。

 

不知其一

 

首先,初学C#的程序员可能不知道什么是继承,或者认为"继承"是高深的工具。那时候,大部分的功能复用都是通过委派进行的。

 

知其一、不知其二

 

然后慢慢地,他们发现在C#语言里实现继承并不困难,并且初步认识到继承可以使子类一下子得到基类的行为。这时他们就会跃跃欲试了,试图使用继承作为功能复用的主要工具,并把原来应当使用委派的地方,改为使用继承,这时继承就有被滥用的危险。

 

知其二

 

很多面向对象的设计专家从1986年就开始警告继承关系被滥用的可能。有一些面向对象的编程语言,如SELF语言,甚至将类的继承关系从语言的功能中取消掉,改为完全使用委派。

 

其他的设计师虽然不提倡彻底取消继承,但无一例外地鼓励在设计中尽可能使甩委派关系代替继承关系。比如在【GOF95】一书中,状态模式、策略模 式、装饰模式、桥梁模式以及抽象工厂模式均是将依赖于继承的实现转换为基于对象的组合和聚合的实现,这些模式的要点就是使用委派关系代替继承关系。

 

知其三

 

是不是继承就根本不该使用呢?事实上对数据的抽象化、继承、封装和多态性并称C#和其他绝大多数的面向对象语言的几项最重要的特性。继承不应当被滥用,并不意味着继承根本就不该使用。因为继承容易被滥用就彻底抛弃继承,无异于因噎废食。

 

继承使得类型的等级结构易于理解、维护和扩展,而类型的等级结构非常适合于抽象化的设计、实现和复用。尽管【GOF95】所给出的设计模式基本上没 有太多基于继承的模式,很多模式都是用继承的办法定义、实现接口的。多数的设计模式都描写一个以抽象类作为基类,以具体类作为实现的等级结构,比如适配器 模式、合成模式、桥梁模式、状态模式等。

 

模版方法模式则更进了一步:此模式鼓励恰当地使用继承。此模式可以用来改写一些拥有相同功能的相关的类,将可复用的一般性的行为代码移到基类里面,而把特殊化的行为代码移到子类里面。

 

因此,熟悉模版方法模式便成为一个重新学习继承的好地方。

 

 

五、 一个实际应用模板方法的例子

下面的例子演示了数据库访问的模板方法。实际应用时,请确保C盘根目录下有nwind.mdb这个Access数据库(可以从Office的安装目录下找到。中文版用户的请注意字段名可能有所不同)。

 

// Template Method pattern -- Real World example  

using System;

using System.Data;

using System.Data.OleDb;

 

// "AbstractClass"

abstract class DataObject

{

  // Methods

  abstract public void Connect();

  abstract public void Select();

  abstract public void Process();

  abstract public void Disconnect();

 

  // The "Template Method"

  public void Run()

  {

    Connect();

    Select();

    Process();

    Disconnect();

  }

}

 

// "ConcreteClass"

class CustomerDataObject : DataObject

{

  private string connectionString =

    "provider=Microsoft.JET.OLEDB.4.0; "

    + "data source=c://nwind.mdb";

  private string commandString;

  private DataSet dataSet;

 

  // Methods

  public override void Connect( )

  {

    // Nothing to do

  }

 

  public override void Select( )

  {

    commandString = "select CompanyName from Customers";

    OleDbDataAdapter dataAdapter = new OleDbDataAdapter(

      commandString, connectionString );

    dataSet = new DataSet();

    dataAdapter.Fill( dataSet, "Customers" );

  }

 

  public override void Process()

  {

    DataTable dataTable = dataSet.Tables["Customers"];

    foreach( DataRow dataRow in dataTable.Rows )

      Console.WriteLine( dataRow[ "CompanyName" ] );

  }

 

  public override void Disconnect()

  {

    // Nothing to do

  }

}

 

/// <summary>

///  TemplateMethodApp test

/// </summary>

public class TemplateMethodApp

{

  public static void Main( string[] args )

  {

    CustomerDataObject c = new CustomerDataObject( );

    c.Run();

  }

}

六、 模版方法模式中的方法

模版方法中的方法可以分为两大类:模版方法(Template Method)和基本方法(Primitive Method)。

 

模版方法

 

一个模版方法是定义在抽象类中的,把基本操作方法组合在一起形成一个总算法或一个总行为的方法。这个模版方法一般会在抽象类中定义,并由子类不加以修改地完全继承下来。

 

基本方法

 

基本方法又可以分为三种:抽象方法(Abstract Method)、具体方法(Concrete Method)和钩子方法(Hook Method)。

 

抽象方法:一个抽象方法由抽象类声明,由具体子类实现。在C#语言里一个抽象方法以abstract关键字标示出来。

 

具体方法:一个具体方法由抽象类声明并实现,而子类并不实现或置换。在C#语言里面,一个具体方法没有abstract关键字。

 

钩子方法:一个钩子方法由抽象类声明并实现,而子类会加以扩展。通常抽象类给出的实现是一个空实现,作为方法的默认实现。(Visual FoxPro中项目向导建立的项目会使用一个AppHook类实现监视项目成员变化,调整系统结构的工作。)钩子方法的名字通常以do开始。

 

 

七、 重构的原则

在对一个继承的等级结构做重构时,一个应当遵从的原则便是将行为尽量移动到结构的高端,而将状态尽量移动到结构的低端。

 

1995年,Auer曾在文献【AUER95】中指出:

 

应当根据行为而不是状态定义一个类。也就是说,一个类的实现首先建立在行为的基础之上,而不是建立在状态的基础之上。

在实现行为时,是用抽象状态而不是用具体状态。如果一个行为涉及到对象的状态时,使用间接的引用而不是直接的引用。换言之,应当使用取值方法而不是直接引用属性。

给操作划分层次。一个类的行为应当放到一个小组核心方法(Kernel Methods)里面,这些方法可以很方便地在子类中加以置换。

 将状态属性的确认推迟到子类中。不要在抽象类中过早地声明属性变量,应将它们尽量地推迟到子类中去声明。在抽象超类中,如果需要状态属性的话,可以调用抽象的取值方法,而将抽象的取值方法的实现放到具体子类中。

如果能够遵从这样的原则,那么就可以在等级结构中将接口与实现分隔开来,将抽象与具体分割开来,从而保证代码可以最大限度地被复用。这个过程实际上是将设计师引导到模版方法模式上去。

 

 

 

 

 

 

 

设计模式(22)-Strategy Pattern 

 

一、 策略(Strategy)模式

策略模式的用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。

 

假设现在要设计一个贩卖各类书籍的电子商务网站的购物车(Shopping Cat)系统。一个最简单的情况就是把所有货品的单价乘上数量,但是实际情况肯定比这要复杂。比如,本网站可能对所有的教材类图书实行每本一元的折扣;对 连环画类图书提供每本7%的促销折扣,而对非教材类的计算机图书有3%的折扣;对其余的图书没有折扣。由于有这样复杂的折扣算法,使得价格计算问题需要系 统地解决。

 

使用策略模式可以把行为和环境分割开来。环境类负责维持和查询行为类,各种算法则在具体策略类(ConcreteStrategy)中提供。由于算 法和环境独立开来,算法的增减、修改都不会影响环境和客户端。当出现新的促销折扣或现有的折扣政策出现变化时,只需要实现新的策略类,并在客户端登记即 可。策略模式相当于"可插入式(Pluggable)的算法"。

 

二、 策略模式的结构

策略模式是对算法的包装,是把使用算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是:"准备一组算法,并将每一个算法封装起来,使得它们可以互换。"

 

策略又称做政策(Policy)模式【GOF95】。下面是一个示意性的策略模式结构图:

 

 

 

这个模式涉及到三个角色:

 

环境(Context)角色:持有一个Strategy类的引用。

抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。

具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。

 

三、 示意性源代码

// Strategy pattern -- Structural example  

using System;

 

// "Strategy"

abstract class Strategy

{

  // Methods

  abstract public void AlgorithmInterface();

}

 

// "ConcreteStrategyA"

class ConcreteStrategyA : Strategy

{

  // Methods

  override public void AlgorithmInterface()

  {

    Console.WriteLine("Called ConcreteStrategyA.AlgorithmInterface()");

  }

}

 

// "ConcreteStrategyB"

class ConcreteStrategyB : Strategy

{

  // Methods

  override public void AlgorithmInterface()

  {

    Console.WriteLine("Called ConcreteStrategyB.AlgorithmInterface()");

  }

}

 

// "ConcreteStrategyC"

class ConcreteStrategyC : Strategy

{

  // Methods

  override public void AlgorithmInterface()

  {

    Console.WriteLine("Called ConcreteStrategyC.AlgorithmInterface()");

  }

}

 

// "Context"

class Context

{

  // Fields

  Strategy strategy;

 

  // Constructors

  public Context( Strategy strategy )

  {

    this.strategy = strategy;

  }

 

  // Methods

  public void ContextInterface()

  {

    strategy.AlgorithmInterface();

  }

}

 

/// <summary>

/// Client test

/// </summary>

public class Client

{

  public static void Main( string[] args )

  {

    // Three contexts following different strategies

    Context c = new Context( new ConcreteStrategyA() );

    c.ContextInterface();

 

    Context d = new Context( new ConcreteStrategyB() );

    d.ContextInterface();

 

    Context e = new Context( new ConcreteStrategyC() );

    e.ContextInterface();

  }

}

 

四、 何时使用何种具体策略角色

在学习策略模式时,学员常问的一个问题是:为什么不能从策略模式中看出哪一个具体策略适用于哪一种情况呢?

 

答案非常简单,策略模式并不负责做这个决定。换言之,应当由客户端自己决定在什么情况下使用什么具体策略角色。策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中"退休"的方便,策略模式并不决定在何时使用何种算法。

 

 

五、 一个实际应用策略模式的例子

下面的例子利用策略模式在排序对象中封装了不同的排序算法,这样以便允许客户端动态的替换排序策略(包括Quicksort、Shellsort和Mergesort)。

 

// Strategy pattern -- Real World example  

using System;

using System.Collections;

 

// "Strategy"

abstract class SortStrategy

{

  // Methods

  abstract public void Sort( ArrayList list );

}

 

// "ConcreteStrategy"

class QuickSort : SortStrategy

{

  // Methods

  public override void Sort(ArrayList list )

  {

    list.Sort(); // Default is Quicksort

    Console.WriteLine("QuickSorted list ");

  }

}

 

// "ConcreteStrategy"

class ShellSort : SortStrategy

{

  // Methods

  public override void Sort(ArrayList list )

  {

    //list.ShellSort();

    Console.WriteLine("ShellSorted list ");

  }

}

 

// "ConcreteStrategy"

class MergeSort : SortStrategy

{

  // Methods

  public override void Sort( ArrayList list )

  {

    //list.MergeSort();

    Console.WriteLine("MergeSorted list ");

  }

}

 

// "Context"

class SortedList

{

  // Fields

  private ArrayList list = new ArrayList();

  private SortStrategy sortstrategy;

 

  // Constructors

  public void SetSortStrategy( SortStrategy sortstrategy )

  {

    this.sortstrategy = sortstrategy;

  }

 

  // Methods

  public void Sort()

  {

    sortstrategy.Sort( list );

  }

 

  public void Add( string name )

  {

    list.Add( name );

  }

 

  public void Display()

  {

    foreach( string name in list )

      Console.WriteLine( " " + name );

  }

}

 

/// <summary>

/// StrategyApp test

/// </summary>

public class StrategyApp

{

  public static void Main( string[] args )

  {

    // Two contexts following different strategies

    SortedList studentRecords = new SortedList( );

    studentRecords.Add( "Samual" );

    studentRecords.Add( "Jimmy" );

    studentRecords.Add( "Sandra" );

    studentRecords.Add( "Anna" );

    studentRecords.Add( "Vivek" );

 

    studentRecords.SetSortStrategy( new QuickSort() );

    studentRecords.Sort();

    studentRecords.Display();

  }

}

 

六、 在什么情况下应当使用策略模式

在下面的情况下应当考虑使用策略模式:

 

1. 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。

 

2. 一个系统需要动态地在几种算法中选择一种。那么这些算法可以包装到一个个的具体算法类里面,而这些具体算法类都是一个抽象算法类的子类。换言 之,这些具体算法类均有统一的接口,由于多态性原则,客户端可以选择使用任何一个具体算法类,并只持有一个数据类型是抽象算法类的对象。

 

3. 一个系统的算法使用的数据不可以让客户端知道。策略模式可以避免让客户端涉及到不必要接触到的复杂的和只与算法有关的数据。

 

4. 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句,并体现面向对象设计的概念。

 

 

七、 策略模式的优点和缺点

策略模式有很多优点和缺点。它的优点有:

 

1. 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免重复的代码。

 

2. 策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类, 每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法 或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。

 

3. 使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。

 

策略模式的缺点有:

 

1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。

 

2. 策略模式造成很多的策略类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。

 

 

八、 其它

策略模式与很多其它的模式都有着广泛的联系。Strategy很容易和Bridge模式相混淆。虽然它们结构很相似,但它们却是为解决不同的问题而 设计的。Strategy模式注重于算法的封装,而Bridge模式注重于分离抽象和实现,为一个抽象体系提供不同的实现。Bridge模式与 Strategy模式都很好的体现了"Favor composite over inheritance"的观点。

 

推荐大家读一读《IoC 容器和Dependency Injection 模式》,作者Martin Fowler。网上可以找到中文版的PDF文件。为策略模式的实施提供了一个非常好的方案。

 

 

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值