我学设计模式之合成模式
1. 简介
合成模式属于对象的结构模式,有时又叫做 部分 – 整体模式。合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。
合成模式把部分和整体的关系用树结构表示出来。合成模式使得客户端把一个个单独的成分对象和有他们复合而成的合成对象同等看待。
2. 组合模式的结构
先给出合成模式的简单类图如下:
从类图中可以看出合成模式涉及3个角色:
抽象构件角色(Component):这是一个抽象角色,他给参加组合的对象规定一个接口,这个角色给出共有的接口及其默认行为。
树叶构件角色(Leaf):代表参加组合的树叶对象。一个树叶没有下级的子对象。定义出参加原始对象的行为。
树枝构件角色(Composite):代表参加组合的有子对象的对象,并给出树枝构件对象的行为。
3. 安全和透明式的合成模式
合成模式的实现根据实现接口的区别分为两种形式,分别为安全式和透明式。他们的类图分别如下:
透明方式:就是在Component里面声明所有的用来管理子对象的方法,包括add()、remove()以及getChild()方法,这样做的好处是所有的构件类都有相同的接口。在客户端看来,树叶类对象与合成类对象的区别起码在接口层次上消失了,客户端可以同等的对待所有的对象。这就是透明形式的合成模式。
这个选择的缺点是不够安全的,因为树叶类对象和合成类对象在本质上是有区别的,树叶类对象不可能有一层的对象,因此add()、remove()以及getChild()方法没有意义,但是在编译时期不会出错,而只会在运行时期才会出错。
安全方式:是在Composite类里面声明所有的用来管理子类对象的方法。这样的做法是安全的做法,因为树叶类型的对象根本就没有管理子对象的方法。因此,如果客户端对树叶对象使用这些方法时会在编译时期出错。编译不通过,就不会出现运行时出错。
这个选择的缺点是不够透明,因为树叶类和合成类将具有不同的接口。
4. 组合模式的用法
在这里使用组合模式对公司人事管理的树状机构进行描述:
首先创建抽象构建,源代码如下:
package com.zsw.composite;
public abstract class Corp {
private String name; //名称 private String position; //职位 private int salary = 0; //薪水
public Corp(String name,String position,int salary){ this.name = name; this.position = position; this.salary = salary; }
public String getInfo(){ String info = ""; info = "姓名:" + this.name; info = info + "/t职位:" + this.position; info = info + "/t薪水: " + this.salary; return info; } } |
创建树叶构件源代码如下:
package com.zsw.composite;
public class Leaf extends Corp {
public Leaf(String name, String position, int salary) { super(name, position, salary); } } |
创建树枝构件源代码如下:
package com.zsw.composite; import java.util.ArrayList; public class Branch extends Corp {
ArrayList<Corp> subordinateList = new ArrayList<Corp>();
public Branch(String name, String position, int salary) { super(name, position, salary); }
public void addSubordinate(Corp corp){ this.subordinateList.add(corp); }
public ArrayList<Corp> getSubordinate(){ return this.subordinateList; } }
|
客户端源代码如下:
package com.zsw.composite; import java.util.ArrayList; public class Client {
public static void main(String[] args) { Branch ceo = compositeCorpTree(); System.out.println(ceo.getInfo()); System.out.println(getTreeInfo(ceo)); }
//把整个树组装出来 public static Branch compositeCorpTree(){ //总经理CEO Branch root = new Branch("王大","总经理",100000);
//三个部门经理 Branch developDep = new Branch("刘大","研发部门经理",10000); Branch salesDep = new Branch("刘大","销售部门经理",20000); Branch financeDep = new Branch("刘大","财务部门经理",30000);
//二个小组长 Branch firstDevGroup = new Branch("杨三","开发一组组长",5000); Branch secondDevGroup = new Branch("吴大","开发二组组长",6000); Branch zhengLaoLiu = new Branch("郑成功","研发部副经理",20000);
//创建所有小兵 Leaf a = new Leaf("a","开发人员",3000); Leaf b = new Leaf("b","开发人员",3000); Leaf c = new Leaf("c","开发人员",3000); Leaf d = new Leaf("d","开发人员",3000); Leaf e = new Leaf("e","开发人员",3000); Leaf f = new Leaf("f","开发人员",3000); Leaf g = new Leaf("g","开发人员",3000);
Leaf h = new Leaf("h","销售人员",5000); Leaf i = new Leaf("i","销售人员",4000); Leaf j = new Leaf("j","财务人员",5000); Leaf k = new Leaf("k","CEO秘书",8000);
//开始组装 //CEO下有三个部门经理和一个秘书 root.addSubordinate(k); root.addSubordinate(developDep); root.addSubordinate(salesDep); root.addSubordinate(financeDep);
//研发部经理 developDep.addSubordinate(zhengLaoLiu); developDep.addSubordinate(firstDevGroup); developDep.addSubordinate(secondDevGroup);
//2个小组 firstDevGroup.addSubordinate(a); firstDevGroup.addSubordinate(b); firstDevGroup.addSubordinate(c); firstDevGroup.addSubordinate(d); secondDevGroup.addSubordinate(e); secondDevGroup.addSubordinate(f); secondDevGroup.addSubordinate(g);
//销售部门下人员情况 salesDep.addSubordinate(h); salesDep.addSubordinate(i);
//财务 financeDep.addSubordinate(j);
return root; }
//遍历整棵树,只要给我根节点,我就能遍历出所有的节点 public static String getTreeInfo(Branch root){ ArrayList<Corp> subordinateList = root.getSubordinate(); String info = "";
for(Corp s : subordinateList){ if(s instanceof Leaf){ info = info + s.getInfo() + "/n"; }else{ info = info + s.getInfo() + "/n" + getTreeInfo((Branch)s); } } return info; } }
|
显示结果如下:
姓名:王大 职位:总经理 薪水: 100000 姓名:k 职位:CEO秘书 薪水: 8000 姓名:刘大 职位:研发部门经理 薪水: 10000 姓名:郑成功 职位:研发部副经理 薪水: 20000 姓名:杨三 职位:开发一组组长 薪水: 5000 姓名:a 职位:开发人员 薪水: 3000 姓名:b 职位:开发人员 薪水: 3000 姓名:c 职位:开发人员 薪水: 3000 姓名:d 职位:开发人员 薪水: 3000 姓名:吴大 职位:开发二组组长 薪水: 6000 姓名:e 职位:开发人员 薪水: 3000 姓名:f 职位:开发人员 薪水: 3000 姓名:g 职位:开发人员 薪水: 3000 姓名:刘大 职位:销售部门经理 薪水: 20000 姓名:h 职位:销售人员 薪水: 5000 姓名:i 职位:销售人员 薪水: 4000 姓名:刘大 职位:财务部门经理 薪水: 30000 姓名:j 职位:财务人员 薪水: 5000 |
5. 组合模式的应用场景
a) 一个文件系统就是一个典型的合成模式系统。可以把目录和文件同等对待和处理,这也就是合成模式的应用。
b) 道士的故事,哄小孩睡觉讲故事,从前有座山、山里有个庙、庙里有个老道,老道讲了一个故事,故事说从前有座山、山里有个庙…..这样循环下去直到小孩睡着。在这个故事中有两种角色:一种没有内部角色,例如山、庙、道士;另一种是有内部角色的的角色,在这个故事中的故事。故事里又有、山、庙、道士….此故事的UML类图如下:
c) 公司的人事管理就是一个典型的树状结构,公司的结构如下图所示,从最高的老大,往下一层的管理,最后到我们这层小兵,很典型的树状结构。
d) ASP.NET的TreeView控件既是典型的组合模式应用,所有WEB控件的基类都是System.Web.UI.Control,而Control基类中就有Add和Remove方法,这就是典型的组合模式的应用。
e) 比如现在页面结构一般都是上下结构,上放系统的Logo,下边分为两部分:左边是导航菜单,右边是展示区,左边的导航菜单一般都是树形结构,比较清晰.
f) AWT库中的例子,AWT和Swing中的图形界面构件是建立在AWT库中的Container类和Component类上的。其中Button和CheckBox是树叶形的构件,而Container则是树枝行的构件。如AWT合成模式的UML类图:
在Container类中,有操作聚集的对应的方法,而在Component类中没有这样的方法。这就是说AWT总使用的合成模式是安全形式的合成模式。
g) 需要描述对象的部分和整体的等级结构。
h) 需要客户端忽略掉个体构件和组合构件的区别。客户端必须平等对待所有的构件,包括个体组件和组合构件。
i) 合成模式可以很容易的增加新种类的构件。
j) 使用合成模式可以是客户端变得很容易设计,因为客户端不需要知道构件时树叶构件还是树枝构件。
6. 组合模式的优缺点
优点:
缺点:
ü 使用合成模式后,控制树枝构件的类型就不太容易。
ü 用继承的方法增加新的行为很困难。
7. 参考文档
a) 《Java与模式》
b) 《大话设计模式》