A composite is a group of objects in which some objects may contain others; thus, one object may represent groups, and another may represent an individual item, or leaf. When you model a composite, two powerful concepts emerge. One important modeling idea is to design groups so that they can contain either individual items or other groups. (A common error is to define groups so that they can contain only leaves.) A second powerful concept is to define common behaviors for individual objects and for compositions. You can bring these ideas together by defining a common type for groups and items and by modeling groups as containing a collection of objects of this type. This fulfills the intent of the COMPOSITE pattern: COMPOSITE lets clients treat individual objects and compositions of objects uniformly.
Composite组合模式主要是应对这样的问题:一类具有“容器特征”的对象——即他们在充当对象的同时,又是其他对象的容器的情况。在编写时我们常常会造成:客户代码过多地依赖于对象容器复杂的内部实现,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性、扩展性的弊端。
GoF《设计模式》中说到:将对象组合成树形结构以表示“部分-整体”的层次结构。Composite模式使得客户对单个对象和组合对象的使用具有一致性。
Composite组合模式结构如下:
说道这,我觉得有一个编程中常见的场景,就是对于树的实现,很符合这个模式。下面我就用这个例子作一下。
首先,我们先分析对于一棵树所包含的部分,树干、树枝、树叶,其中树干可以看成一个树枝(就是粗了点)。那么我们就应该有两种类实现Leaf(树叶)和Limb(树枝)。对于叶子节点和枝节点的不同在于枝节点有子树,而叶子节点没有子树。为了使单个对象和组合对象的使用具有一致性,我可以将叶子节点想象成没有子树的枝节点。这样我就可以得到一个抽象类,代码如下:
public abstract class AbstractClass { public string name; public ArrayList list; public abstract void Add(AbstractClass item); //增加一个子节点 public abstract void Remove(AbstractClass item); //去掉一个子节点 public abstract string Print(); //打印当前节点 }
然后,我在对叶子节点和枝节点作不同的实现:
枝节点:
public class Limb:AbstractClass { public Limb() { list = new ArrayList(); } public override void Add(AbstractClass item) { list.Add(item); } public override void Remove(AbstractClass item) { if(list.Contains(item)) list.Remove(item); } public override string Print() { Console.Write(name + "\n"); if(list.Count != 0) { for(int i = 0;i<list.Count;i++) { Console.Write("(Parent is " + name + ")"); ((AbstractClass)list[i]).Print(); } } return name; } }
叶子节点:
public class Leaf:AbstractClass { public Leaf() { list = null; } public override void Add(AbstractClass item){} public override void Remove(AbstractClass item){} public override string Print() { Console.Write(name + ","); return this.name; } }
对于叶子节点来说,不需要子节点,当然也就不需要添加和删除子节点的方法。
好,接下来,我们可以在客户程序中组建一棵树,来测试一下:
static void Main(string[] args) { AbstractClass Tree = new Limb(); GetTree(Tree); PrintTree(Tree); Console.Read(); } public static void GetTree(AbstractClass Tree) { Tree.name = "1"; AbstractClass leaf2 = new Leaf(); leaf2.name = "2"; Tree.Add(leaf2); AbstractClass limb3 = new Limb(); limb3.name = "3"; Tree.Add(limb3); AbstractClass leaf4 = new Leaf(); leaf4.name = "4"; limb3.Add(leaf4); AbstractClass leaf5 = new Leaf(); leaf5.name = "5"; limb3.Add(leaf5); } public static void PrintTree(AbstractClass Tree) { Tree.Print(); }
输出结果如下:
1
(Parent is 1)2,(Parent is 1)3
(Parent is 3)4,(Parent is 3)5,
在组织这个树时,的确能感觉到GoF《设计模式》中的那句话:单个对象和组合对象的使用具有一致性。当然也的确感觉到一点矛盾:对于叶子节点来说,不需要ArrayList和Add()Remove()应该不继承才对,当然如果在代码执行性能可以达到要求的情况下,简化一下编码实现复杂度也是挺好的一件事。
最后在来说说Composite组合模式的几个要点:
1、Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致的处理对象和对象容器,无需关心处理的是单个对象,还是组合的对象容器。
2、将“客户代码与复杂的对象容器结构”解耦是Composite模式的核心思想,解耦之后,客户代码将与纯粹的对象接口——而非对象容器的复杂内部实现结构——发生依赖关系,从而更能“应对变化”。
3、Composite模式中,是将“Add和Remove的和对象容器相关的方法”定义在“表示抽象对象的Component类”中,还是将其定义在“表示对象容器的Composite类”中,是一个关乎“透明性”和“安全性”的两难问题,需要仔细权衡结构,这又是必须付出的代价。
4、Composite模式在具体实现中,可以让父对象中的字对象反向追溯:如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率
Summary:
COMPOSITE contains two powerful, related concepts.One concept is that a group can contain either individual items or other groups. Related to this concept is the idea that groups and individual items may share a common interface. These ideas come together in object modeling, whereby you can create an abstract class or a Java interface that defines the behaviors that are common to groups and to individual objects.
Modeling composites often leads to recursive definition of methods on composite nodes. When recursion is present, a danger exists of writing code that produces an infinite loop. To avoid such problems, you can take steps to guarantee that your composites are always trees. Alternatively, you can allow cycles to occur in a composite, but you have to modify your algorithms to watch for ecursion. You can handle cycles in composites by maintaining a collection of observed nodes.