对象容器的问题:
在面向对象系统中,我们常会遇到一类具有“容器”特征的对象--即它们在充当对象的同时,又是其他对象的容器。
动机(Motivation)
上述描述的问题根源在于:客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性、扩展性等弊端。
如何将“客户代码与复杂的对象容器结构”解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?
意图(Intent)
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
结构(Structure)
代码实现:
1 public interface IBox 2 { 3 void Operation(); 4 void Add(IBox box); 5 void Remove(IBox box); 6 } 7 8 public class SingleBox : IBox 9 { 10 private int _num; 11 12 public int Num 13 { 14 get { return _num; } 15 set { _num = value; } 16 } 17 public SingleBox(int num) 18 { 19 this.Num = num; 20 } 21 public void Operation() 22 { 23 Console.WriteLine("SingleBox"+this.Num); 24 } 25 26 public void Add(IBox box) 27 { 28 throw new NotImplementedException(); 29 } 30 31 public void Remove(IBox box) 32 { 33 throw new NotImplementedException(); 34 } 35 } 36 37 public class ContainerBox : IBox 38 { 39 private int _num; 40 ArrayList list=null; 41 public int Num 42 { 43 get { return _num; } 44 set { _num = value; } 45 } 46 public ContainerBox(int num) 47 { 48 this.list = new ArrayList(); 49 this.Num = num; 50 } 51 52 public void Operation() 53 { 54 Console.WriteLine("Composite"+this.Num); 55 if (list != null) 56 { 57 foreach (IBox item in list) 58 { 59 item.Operation(); 60 } 61 } 62 } 63 64 public void Add(IBox box) 65 { 66 if (list == null) 67 { 68 list = new ArrayList(); 69 } 70 list.Add(box); 71 } 72 73 public void Remove(IBox box) 74 { 75 if (list == null) 76 { 77 throw new Exception(); 78 } 79 list.Remove(box); 80 } 81 }
Composite模式的几个要点:
Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致地处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。
将“客户代码与复杂的对象容器结构”解耦是Composite模式的核心思想,解耦之后,客户代码将与纯粹的抽象接口--而非对象容器的复内部实现结构--发生依赖关系,从而更能“应对变化”。
Composite模式中,是将“Add和Remove等和对象容器相关的方法”定义在“表示抽象对象的Component类中”,还是将其定义在“表示对象容器的Composite类中”,是一个关乎“透明性”和“安全性”的两难问题,需要仔细权衡。这里有可能违背面向对象的“单一职责原则”,但是对于这种特殊结构,这又是必须付出的代价。ASP.NET控件的实现在这方面为我们提供了一个很好的示范。
Composite模式在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率。