不管是在生活中仍是项目中,咱们常常会遇到具备“部分-总体”概念的对象,好比员工与团队的关系,这就相似树形结构,可能具备不少的嵌套层次和分支,把这种复杂性直接暴露给调用端是不合适的。编程
借助组合模式,能够将这类具备“部分-总体”的对象组合成树形的层次结构,并使得用户能够对单个对象和组合对象采用相同的使用方式。
GOF对组合模式的描述为:
Compose objects into tree structures to represent part-whole hierarchies.
Compositelets clients treat individual objects and compositions of objects uniformly.
— Design Patterns : Elements of Reusable Object-Oriented Software设计模式
UML类图:
安全
组合模式包含三个角色:ide
在使用组合模式时,根据抽象构件类的定义形式,可将组合模式分为透明模式和安全组合两种形式。this
透明模式中,抽象构件Component中声明了全部用于管理成员对象的方法,包括add()、remove()以及getChildren()等方法,这样作的好处是确保全部的构件类都有相同的接口。在客户端看来,叶子对象与容器对象所提供的方法是一致的,客户端能够相同地对待全部的对象。透明组合模式也是组合模式的标准形式,前面的类图表示的就是透明模式。设计
透明模式的缺点是不够安全,由于叶子对象和容器对象在本质上是有区别的。叶子对象不可能有下一个层次的对象,即不可能包含成员对象,所以为其提供add()、remove()以及getChildren()等方法是没有意义的,这在编译阶段不会出错,但在运行阶段若是调用这些方法就会致使异常。code
透明模式的实现代码以下:orm
public abstract class Component { protected IList<Component> children; public virtual string Name { get; set; } public virtual void Add(Component child) { children.Add(child); } public virtual void Remove(Component child) { children.Remove(child); } public virtual Component this[int index] { get { return children[index]; } } } public class Leaf : Component { public override void Add(Component child) { throw new NotSupportedException(); } public override void Remove(Component child) { throw new NotSupportedException(); } public override Component this[int index] => throw new NotSupportedException(); } public class Composite : Component { public Composite() { base.children = new List<Component>(); } }
安全模式则是将管理成员对象的方法从抽象构件Component转移到了Composite,在抽象构件Component中没有声明任何用于管理成员对象的方法,这样能够保证安全,叶子对象中没法调用到那些管理成员对象的方法。对象
安全模式的缺点是不够透明,由于叶子构件和容器构件具备不一样的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,所以客户端不能彻底针对抽象编程,必须有区别地对待叶子构件和容器构件。blog
将对象组合成树形结构后,要使用这些对象,就须要用遍历树形结构的方式来获取这些对象。
好比对于上面代码中的Component,若是须要获取所有结点的Names属性
实现代码能够为:
public List<string> names = new List<string>(); public virtual IEnumerable<string> GetNameList() { GetNameList(names); return names; } private virtual void GetNameList(List<string> names) { names.Add(this.Name); if (children != null && children.Count > 0) { foreach (Component child in children) { child.GetNameList(names); } } }
但有的时候每每会遇到一些定制化的遍历需求,好比只获取Leaf结点(仅列出一个全部员工的名单),只获取Composite结点(仅列出全部部门领导的信息)等等,对于这些需求若是一一实现比较麻烦,且须要频繁变化,能够采用一种更通用的方式,相似Linq中Where筛选那样,调用的同时把筛选条件也传入。
对GetNameList方法的扩展:
public virtual IEnumerable<string> GetNameList(Func<Component, bool> isMatchFunc) { GetNameList(names, isMatchFunc); return names; } public virtual void GetNameList(List<string> names, Func<Component, bool> isMatchFunc) { if (isMatchFunc == null || isMatchFunc(this)) { names.Add(this.Name); } if (children != null && children.Count > 0) { foreach (Component child in children) { child.GetNameList(names, isMatchFunc); } } }
参考书籍: 王翔著 《设计模式——基于C#的工程化实现及扩展》