作者: 创建于:2006-03-11 出处: 收录于:2013-02-28
结构图
意图
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite模式使得用户对单个对象和组合对象的使用具有一致性。
适用性
- 你想表示对象的部分-整体层次结构。
- 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
实现代码
这里我们用绘图这个例子来说明Composite模式,通过一些基本图像元素(直线、圆等)以及一些复合图像元素(由基本图像元素组合而成)构建复杂的图形树。在设计中我们对每一个对象都配备一个Draw()方法,在调用时,会显示相关的图形。可以看到,这里复合图像元素它在充当对象的同时,又是那些基本图像元素的一个容器。先看一下基本的类结构图:
1 using System; 2 using System.Collections; 3 public abstract class Graphics 4 { 5 protected string _name; 6 public Graphics(string name) 7 { 8 this._name = name; 9 }10 public abstract void Draw();11 public abstract void Add();12 public abstract void Remove();13 }14 public class Picture : Graphics15 {16 protected ArrayList picList = new ArrayList();17 public Picture(string name) : base(name)18 { }19 public override void Draw()20 {21 Console.WriteLine("Draw a" + _name.ToString());22 foreach (Graphics g in picList)23 {24 g.Draw();25 }26 }27 public override void Add(Graphics g)28 {29 picList.Add(g);30 }31 public override void Remove(Graphics g)32 {33 picList.Remove(g);34 }35 }36 public class Line : Graphics37 {38 public Line(string name) : base(name)39 { }40 public override void Draw()41 {42 Console.WriteLine("Draw a" + _name.ToString());43 }44 public override void Add(Graphics g)45 {46 //抛出一个我们自定义的异常47 }48 public override void Remove(Graphics g)49 {50 //抛出一个我们自定义的异常51 }52 }53 public class Circle : Graphics54 {55 public Circle(string name): base(name)56 { }57 public override void Draw()58 {59 Console.WriteLine("Draw a" + _name.ToString());60 }61 public override void Add(Graphics g)62 {63 //抛出一个我们自定义的异常64 }65 public override void Remove(Graphics g)66 {67 //抛出一个我们自定义的异常68 }69 }70 public class Rectangle : Graphics71 {72 public Rectangle(string name): base(name)73 { }74 public override void Draw()75 {76 Console.WriteLine("Draw a" + _name.ToString());77 }78 public override void Add(Graphics g)79 {80 //抛出一个我们自定义的异常81 }82 public override void Remove(Graphics g)83 {84 //抛出一个我们自定义的异常85 }86 }
因为Line,Rectangle,Circle已经没有了子对象,它是一个基本图像元素,因此Add(),Remove()的方法对于它来说没有任何意义,而且把这种错误不会在编译的时候报错,所以需要抛出异常。
这样改进以后,我们可以捕获可能出现的错误,做进一步的处理。上面的这种实现方法属于透明式的Composite模式,如果我们想要更安全的一种做法,就需要把管理子对象的方法声明在树枝构件Picture类里面,这样如果叶子节点Line,Rectangle,Circle使用这些方法时,在编译期就会出错,看一下类结构图:
1 using System; 2 using System.Collections; 3 public abstract class Graphics 4 { 5 protected string _name; 6 public Graphics(string name) 7 { 8 this._name = name; 9 }10 public abstract void Draw();11 }12 public class Picture : Graphics13 {14 protected ArrayList picList = new ArrayList();15 public Picture(string name): base(name)16 { }17 public override void Draw()18 {19 Console.WriteLine("Draw a" + _name.ToString());20 foreach (Graphics g in picList)21 {22 g.Draw();23 }24 }25 public void Add(Graphics g)26 {27 picList.Add(g);28 }29 public void Remove(Graphics g)30 {31 picList.Remove(g);32 }33 }34 public class Line : Graphics35 {36 public Line(string name): base(name)37 { }38 public override void Draw()39 {40 Console.WriteLine("Draw a" + _name.ToString());41 }42 }43 public class Circle : Graphics44 {45 public Circle(string name) : base(name)46 { }47 public override void Draw()48 {49 Console.WriteLine("Draw a" + _name.ToString());50 }51 }52 public class Rectangle : Graphics53 {54 public Rectangle(string name) : base(name)55 { }56 public override void Draw()57 {58 Console.WriteLine("Draw a" + _name.ToString());59 }60 }
这种方式属于安全式的Composite模式,在这种方式下,虽然避免了前面所讨论的错误,但是它也使得叶子节点和树枝构件具有不一样的接口。这种方式和透明式的Composite各有优劣,具体使用哪一个,需要根据问题的实际情况而定。
以下是客户端代码
1 public class App 2 { 3 public static void Main() 4 { 5 Picture root = new Picture("Root"); 6 root.Add(new Line("Line")); 7 root.Add(new Circle("Circle")); 8 Rectangle r = new Rectangle("Rectangle"); 9 root.Add(r);10 root.Draw();11 }12 }
效果及实现要点
1.Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化“一对一”的关系,使得客户代码可以一致地处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。
2.将“客户代码与复杂的对象容器结构”解耦是Composite模式的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的复内部实现结构——发生依赖关系,从而更能“应对变化”。
3.Composite模式中,是将“Add和Remove等和对象容器相关的方法”定义在“表示抽象对象的Component类”中,还是将其定义在“表示对象容器的Composite类”中,是一个关乎“透明性”和“安全性”的两难问题,需要仔细权衡。这里有可能违背面向对象的“单一职责原则”,但是对于这种特殊结构,这又是必须付出的代价。ASP.NET控件的实现在这方面为我们提供了一个很好的示范。
4.Composite模式在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率。