设计模式之Composite(组合)模式
1、出现原因
1、在面向对象系统中,我们常会遇到一类具有“容器”特征的对象——即它们在充当对象的同时,又是其他对象的容器。
如何将“客户代码与复杂的对象容器结构”解耦(将这种组合容器对象设计成树形结构,从而可以对下面所有的容器都可以通过最上层 的根对象 实现 统一 的调用,进而客户端就不在乎 其内部是怎么实现的,耦合 降低了)?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?
2、意图
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。(通过 树根对象的 执行方法,从而调用 他下面的所有的子节点)
3、结构图
4、代码演示
业务实现:
1 /// <summary> 2 /// 抽象的统一 入口 3 /// </summary> 4 public abstract class AbsCompany 5 { 6 protected string Name { set; get; } 7 8 public AbsCompany(string str) 9 { 10 this.Name = str; 11 } 12 13 public abstract void Add(AbsCompany item); 14 15 public abstract void Remove(AbsCompany item); 16 17 public abstract void Show(); 18 19 public abstract void DoDuty(); 20 21 //增加子节点的 方式可以 设置 父节点属性 22 public AbsCompany Father { set; get; } 23 24 } 25 26 /// <summary> 27 /// 叶子节点1 28 /// </summary> 29 public class HRPart : AbsCompany 30 { 31 public HRPart(string name) 32 : base(name) 33 { 34 } 35 //叶子节点不能实现 增删节点操作 36 public override void Add(AbsCompany item) 37 { 38 throw new InvalidOperationException("叶子节点不允许操作"); 39 } 40 41 public override void Remove(AbsCompany item) 42 { 43 throw new InvalidOperationException("叶子节点不允许操作"); 44 } 45 46 public override void Show() 47 { 48 Console.WriteLine("人力资源部:"+base.Name); 49 } 50 51 public override void DoDuty() 52 { 53 Console.WriteLine(base.Name+":致力于招募人才"); 54 } 55 } 56 57 58 /// <summary> 59 /// 叶子节点2 60 /// </summary> 61 public class MoneyPart : AbsCompany 62 { 63 public MoneyPart(string name) 64 : base(name) 65 { 66 } 67 68 //叶子节点不能实现 增删节点的操作 69 public override void Add(AbsCompany item) 70 { 71 throw new InvalidOperationException("叶子节点不允许操作"); 72 } 73 74 public override void Remove(AbsCompany item) 75 { 76 throw new InvalidOperationException("叶子节点不允许操作"); 77 } 78 79 public override void Show() 80 { 81 Console.WriteLine("财务部分:"+base.Name); 82 } 83 84 public override void DoDuty() 85 { 86 Console.WriteLine(base.Name+":致力于管理好公司的财务"); 87 } 88 } 89 90 /// <summary> 91 /// 树形节点(Composite) 92 /// </summary> 93 public class Company : AbsCompany 94 { 95 public Company(string name) 96 : base(name) 97 { 98 } 99 100 //对应 上级节点是 聚合关系 101 private List<AbsCompany> items = new List<AbsCompany>(); 102 103 104 public override void Add(AbsCompany item) 105 { 106 items.Add(item); 107 108 //父节点方式:增加子节点 109 item.Father = this; 110 } 111 112 public override void Remove(AbsCompany item) 113 { 114 items.Remove(item); 115 116 //父节点 方式 :删除子节点: 117 item.Father = null;//此节点没有父节点了,因此它的父节点下面就没有这个子节点了 118 } 119 /// <summary> 120 /// 实现了加载 次节点下面的所有节点:递归 121 /// (**)调用的顺序:当 调用到一个子节点,会将他的下面 子节点全都调用完成之后,才调用他的兄弟节点 122 /// </summary> 123 /// <param name="item"></param> 124 public override void Show() 125 { 126 //先显示自己 127 Console.WriteLine("公司名称:"+base.Name); 128 129 //显示下面所有的子节点的 名称 130 foreach (var item in items) 131 { 132 item.Show(); 133 } 134 135 //注意:要是使用 父节点的方式 实现树形组合:这个时候递归遍历下面所有的子节点的方式就不能执行了 136 } 137 138 public override void DoDuty() 139 { 140 //先执行自己的职责 141 Console.WriteLine(base.Name+":致力于管理好整个公司"); 142 143 //在执行 他的节点下面的所有的节点的 所有的职责 144 foreach (var item in items) 145 { 146 item.DoDuty(); 147 } 148 149 } 150 }
客户端调用代码:
1 //都是通过AbsCompany,后面创建的过程 可以通过 反射进行创建。所以客户 就可以直接 通过树形节点的 最顶层节点 就行创建下面一系列的节点,不用关心具体的实现 (耦合 大大降低) 2 AbsCompany HeadCompany = new Company("阿里巴巴"); 3 AbsCompany hr1 = new HRPart("总公司人力资源部"); 4 AbsCompany money1 = new MoneyPart("总公司财务部"); 5 AbsCompany company1 = new Company("上海分公司"); 6 AbsCompany company2 = new Company("北京办事处"); 7 AbsCompany hr2 = new HRPart("上海分公司人力资源部"); 8 AbsCompany money2 = new MoneyPart("上海分公司财务部"); 9 AbsCompany hr3 = new HRPart("北京人力资源部"); 10 AbsCompany money3 = new MoneyPart("北京财务部门"); 11 AbsCompany company3 = new Company("深圳办事处"); 12 AbsCompany hr4 = new HRPart("深圳人力资源部"); 13 AbsCompany money4 = new MoneyPart("深圳财务部"); 14 HeadCompany.Add(company1); 15 HeadCompany.Add(hr1); 16 HeadCompany.Add(money1); 17 company1.Add(hr2); 18 company1.Add(money2); 19 company1.Add(company2); 20 company1.Add(company3); 21 company2.Add(hr3); 22 company2.Add(money3); 23 company3.Add(hr4); 24 company3.Add(money4); 25 HeadCompany.Show(); 26 HeadCompany.DoDuty();
每个对象的创建可以通过反射的方式进行创建
5、实现Composite模式两种方式
透明方式和安全模式
7、适用性
两个或者多个类有相似的形式,或者共同代表某个完整的概念,(所有的类都有几乎相同的成员,并且叶子节点可以含有多个)外界的用户也希望他们合而为一(通过一个抽象的基类和接口 就可以 指向所有的类,用户不用关心具体的实现),就可以把这几个类“组合”起来,成为一个新的类,用户只需要调用这个新的类就可以了。
8、总结
1、Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致地处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。(因为这些对象都有几乎相同的结构,所以可以通过抽象基类或接口 进行 调用)
2、将“客户代码与复杂的对象容器结构”解耦(因为所有的类含有 几乎相同的 结构,所以可以通过抽象基类或 接口进行调用,所以用户 只需直接 调用,不许关心 其内部是怎么实现的,耦合大大降低)是Composite模式的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的内部实现结构——发生依赖关系,从而更能“应对变化”。(客户端和抽象接口发生依赖关系)
3、Composite模式中,是将“Add和Remove等和对象容器相关的方法”定义在“表示抽象对象的Component类”中,还是将其定义在“表示对象容器的Composite类”中,是一个关乎“透明性”和“安全性”的两难问题,需要仔细权衡。这里有可能违背面向对象的“单一职责原则”(基于透明性:则违背;基于安全性:则遵守),但是对于这种特殊结构,这又是必须付出的代价。
4、Composite模式在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率。
5、Composite模式一般都 和 Builder 模式组合起来使用(Builder模式:http://www.cnblogs.com/xiaoxiaogogo/p/3572618.html),每个对象的创建通过Builder模式进行创建(将表示和创建隔离,达到解耦的目的)