CSDN博客

img ddarkelf

C#设计模式

发表于2004/9/16 11:10:00  1896人阅读

分类: c# 软件工程

课本:《C#设计模式》,电子工业出版社,ISBN 7-5053-8979-3。33元含光盘。

课程内容:设计模式

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

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


一、 C# 面向对象程序设计复习

  点击http://www.cnblogs.com/Files/zhenyulu/CSharp.rar下载,内容包括:

  字段与属性.cs
  属性、方法作用范围.cs
  一加到一百.cs
  使用接口排序(2).cs
  使用接口排序(1).cs
  求质数.cs
  冒泡法排序.cs
  九九表.cs
  静态与非静态.cs
  构造函数.cs
  方法重载.cs
  多态性.cs
  递归求阶乘.cs
  打印三角形.cs
  传值调用与引用调用.cs

 

二、 设计模式举例

在设计模式中有一种模式叫Builder模式,其原理如下:

我们可以将Builder理解成电饭锅,给这个Builder放进去米和水,经过Builder的Build后,我们就可以取出香喷喷的米饭了。
C#中有一个类叫StringBuilder,输入必要的信息后,就可以取出对应的String。其使用方法如下:

using System;
using System.Text;

class Exam
{
 
public static void Main()
 
{
  StringBuilder sb 
= new StringBuilder();
  sb.Append(
'a',2);
  sb.Append(
'b',3);
  sb.Append(
'c',4);
  Console.WriteLine(sb.ToString()); 
//打印出 aabbbcccc
  sb.Remove(0, sb.Length); //清除sb中的所有信息
 }

}

程序执行结果为: aabbbcccc
请使用StringBuilder对以下打印三角型的程序进行改写,写出新程序。

using System;
public class Exam
{
 
public static void Main()
 
{
  Console.Write(
"请输入行数:");
  
int lines = int.Parse(Console.ReadLine());
  Console.WriteLine(
"");
  
for(int i=0; i<=lines ; i++)
  
{
   
for(int k=1; k<= lines-i; k++)
    Console.Write(
" ");
   
for(int j=1; j<=i*2+1; j++)
    Console.Write(
"*");
   Console.WriteLine(
"");
  }

}

}

答:

using System;
using System.Text;
class Exam
{
 
public static void Main()
 
{
  Console.Write(
"请输入行数:");
  
int lines = int.Parse(Console.ReadLine());
  Console.WriteLine(
"");

  StringBuilder sb 
= new StringBuilder();

  
for(int i=0; i<=lines ; i++)
  
{
   sb.Append(
' ', lines-i);
   sb.Append(
'*', i*2+1);
   Console.WriteLine(sb.ToString());
   sb.Remove(
0, sb.Length);
  }

 }

}

 

三、 先有鸡还是先有蛋?

到底是先有鸡还是先有蛋?看下面的代码:

using System;

class Client
{
   
public static void Main ()
   
{
      Base b 
= new Base();
      Derived d 
= new Derived();
      b.d 
= d;
      Console.WriteLine(b.d.m);      
   }

}


class Base
{
   
public int n = 9;
   
public Derived d;
}


class Derived : Base
{
   
public int m = 10;   
}

Derived继承自Base,可以说没有Base就没有Derived,可Base里面有一个成员是Derived类型。到底是先有鸡还是先有蛋?这个程序可以正常编译执行并打印结果10。

 

四、 大瓶子套小瓶子还是小瓶子套大瓶子?

另外一个例子:

using System;

class Client
{
   
public static void Main ()
   
{
      A a 
= new A();
      B b 
= new B();
      a.b 
= b;
      b.a 
= a;
   }

}


class A
{
   
public B b;
}


class B
{
   
public A a; 
}

上面的代码似乎描述了"a包含b,b包含a"的关系,到底是大瓶子套小瓶子还是小瓶子套大瓶子呢?

 

五、 .net本质

关于"先有鸡还是先有蛋"的程序,系统运行后,内存结构如下:
 

由图中可以看出,根本不存在鸡与蛋的问题,而是型与值的问题以及指针引用的问题。

关于"大瓶子套小瓶子还是小瓶子套大瓶子"问题,系统运行后,内存结构如下:


 
由于是指针引用,所以也无所谓大瓶子还是小瓶子了。

关于更多内容可以参考《.NET本质论 第1卷:公共语言运行库》。

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

* 软件腐化的原因:

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

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

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

* 对可维护性的支持:


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

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

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

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

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

 

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


二、 里氏代换原则(LSP)

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

白马、黑马
 

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

 

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

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

   
public long getWidth()
   
{
      
return this.width;
   }

   
public void setHeight(long height)
   
{
      
this.height = height;
   }

   
public long getHeight()
   
{
      
return this.height;
   }

}


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


   
public long getSide()
   
{
      
return side;
   }

}


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

using System;

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

   
public long getWidth()
   
{
      
return this.width;
   }

   
public void setHeight(long height)
   
{
      
this.height = height;
   }

   
public long getHeight()
   
{
      
return this.height;
   }

}


public class Square : Rectangle
{
   
private long side;

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


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


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


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


   
public long getSide()
   
{
      
return side;
   }


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

}


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

   }

}

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

代码重构

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


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

   
public long getWidth()
   
{
      
return this.width;
   }

   
public void setHeight(long height)
   
{
      
this.height = height;
   }

   
public long getHeight()
   
{
      
return this.height;
   }

}


public class Square : Quadrangle 
{
   
private long side;

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


   
public long getSide()
   
{
      
return side;
   }


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


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

}


三、 依赖倒置原则(DIP)

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

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

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

反面例子:

 

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

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

 

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

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

解决方法二:
 

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

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

四、 接口隔离原则(ISP)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

例如:
 

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

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

 

六、 迪米特法则(LoD)

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

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

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

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

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

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

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

 

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

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

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


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



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

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

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


三、 程序举例:


using System;

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


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


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

}


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


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

}


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

}


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

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

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

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

}



四、 Simple Factory模式演化

Simple Factory模式演化(一)

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

程序举例:

using System;

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


   
public virtual void TurnOff()
   
{
   }


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

}


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


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

}


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


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

}


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

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

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

}


Simple Factory模式演化(二)

三个角色全部合并:

 

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


五、 优点与缺点:

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

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

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

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

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

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

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

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

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


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

 

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

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

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

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


三、 程序举例:

using System;

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


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

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


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

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


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


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


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


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

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

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

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

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

}

工厂方法的活动序列图

 

活动过程包括:

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


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

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

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

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

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


五、 Factory Method模式演化

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

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

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

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

 

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

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

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


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

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


七、 另外一个例子

// Factory Method pattern -- Real World example  

using System;
using System.Collections;

// "Product"
abstract class Page
{
}


// "ConcreteProduct"
class SkillsPage : Page
{
}


// "ConcreteProduct"
class EducationPage : Page
{
}


// "ConcreteProduct"
class ExperiencePage : Page
{
}


// "ConcreteProduct"
class IntroductionPage : Page
{
}


// "ConcreteProduct"
class ResultsPage : Page
{
}


// "ConcreteProduct"
class ConclusionPage : Page
{
}


// "ConcreteProduct"
class SummaryPage : Page
{
}


// "ConcreteProduct"
class BibliographyPage : Page
{
}


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

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


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


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


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

}


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

}


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

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

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

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

  }

}

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

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

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

 

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

引进抽象工厂模式

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

 

二、 Abstract Factory模式的结构:

 

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

 


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

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

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

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


三、 程序举例:

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

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

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


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

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

}


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


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

}


// "AbstractProductA"
abstract class AbstractProductA
{
}


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


// "ProductA1"
class ProductA1 : AbstractProductA
{
}


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

}


// "ProductA2"
class ProductA2 : AbstractProductA
{
}


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

}


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

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

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

}


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

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

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

}

 


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

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

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

五、 抽象工厂的起源

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

 

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

 

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

 

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

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


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

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

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

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

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


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

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


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

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


// "AbstractProductA"
abstract class Herbivore
{
}


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


// "ProductA1"
class Wildebeest : Herbivore
{
}


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

}


// "ProductA2"
class Bison : Herbivore
{
}


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

}


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

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


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


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

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

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

}

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

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


七、 "开放-封闭"原则

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

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

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

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

一、 单例(Singleton)模式

单例模式的特点:

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

单例模式应用:

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


二、 Singleton模式的结构:

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


三、 程序举例:

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

// Singleton pattern -- Structural example  
using System;

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

  
// Constructor
  protected Singleton() {}

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

    
return instance;
  }

}


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

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

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

}



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

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

注意:

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

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


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

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

// Singleton pattern -- Real World example  

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

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

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


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

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

      mutex.Close();
    }

    
return balancer;
  }


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

  }

}


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

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

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

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

}



六、 C#中的Singleton模式

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

方法一:

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

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

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

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

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

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

方法二:

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

public sealed class Singleton
{
  Singleton()
  
{
  }


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

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


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

}

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

一、 建造者(Builder)模式

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

对象性质的建造

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

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

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

命名的考虑

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


二、 Singleton模式的结构:

 

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

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

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

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

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

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


三、 程序举例:

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

// Builder pattern -- Structural example  

using System;
using System.Collections;

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

}


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


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

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


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


  
override public Product GetResult()
  
{
    
return product;
  }

}


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

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


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


  
override public Product GetResult()
  
{
    
return product;
  }

}


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


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

}


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

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

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

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

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

}



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

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


五、 建造者模式的实现:

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

// Builder pattern -- Real World example  

using System;
using System.Collections;

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

}


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

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


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


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


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


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


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

}


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


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


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


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

}


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


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


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


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

}


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

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


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


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

}


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

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

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

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

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

}


六、 建造者模式的演化

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

省略抽象建造者角色

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

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

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

}


省略指导者角色

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

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

  
public void BuildPartA()
  

    
//Some code here
  }


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


  
public Product GetResult()
  
{
    
return product;
  }


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

}

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

public class Client
{
  
private static Builder builder;

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

}

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


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

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

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

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

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

一、 原型(Prototype)模式

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

从孙大圣的手段谈起

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

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

C#对原型模式的支持

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

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


二、 Prototype模式的结构:

 

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


三、 程序举例:

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

// Prototype pattern -- Structural example  
using System;

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

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


  
public string Id
  
{
    
getreturn id; }
  }


  
// Methods
  abstract public Prototype Clone();
}


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

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

}


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

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

}


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

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

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

}

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


四、 带Prototype Manager的原型模式

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

 

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


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

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

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


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

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


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


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

}


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

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

}


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

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

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

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

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

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

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

}



五、 浅拷贝与深拷贝

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

浅拷贝:

using System;

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

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


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

}


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

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

}


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

深拷贝:

using System;

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

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


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


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


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

}


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

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

}


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


六、 Prototype模式的优点与缺点

Prototype模式的优点包括

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

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

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

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


Prototype模式的缺点:

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

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

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

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


一、 适配器(Adapter)模式

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

名称由来

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

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

适配器模式的两种形式

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


二、 类的Adapter模式的结构:

 

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

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

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

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


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

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

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

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


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

}


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

}


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

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

}



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

 

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

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

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


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

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

// Adapter pattern -- Structural example  
using System;

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

}


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

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

}


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

}


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

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

}



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

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

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


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

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

// Example of implementing the Adapter pattern
using System;

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


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

}


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

}


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


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

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


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


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

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

}


八、 关于Adapter模式的讨论

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

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

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 );
  }

}


/// 
/// Client test
/// 

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 ); }
}


/// 
/// Client test
/// 

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

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

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

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

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

}


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

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


八、 和尚的故事


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

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

// Composite pattern -- Real World example  

using System;
using System.Collections;

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

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


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

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

}


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

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

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

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

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

}

 
/// 
///  CompositeApp test
/// 

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 );
  }

}

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


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

0 0

相关博文

我的热门文章

img
取 消
img