组合模式
例子
公司管理结构图。
public 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;
}
}
public class Branch extends Corp{
ArrayList<Corp> subordinateList = new ArrayList<>();
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;
}
}
public class Leaf extends Corp {
public Leaf(String name, String position, int salary) {
super(name, position, salary);
}
}
public class Client {
public static void main(String[] args) {
Branch ceo = compositeCorpTree();
System.out.println(getTreeInfo(ceo));
}
public static Branch compositeCorpTree() {
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);
//把所有的小兵都产生出来
Leaf a = new Leaf("a", "开发人员", 2000);
Leaf b = new Leaf("b", "开发人员", 2000);
Leaf c = new Leaf("c", "开发人员", 2000);
Leaf d = new Leaf("d", "开发人员", 2000);
Leaf e = new Leaf("e", "开发人员", 2000);
Leaf f = new Leaf("f", "开发人员", 2000);
Leaf g = new Leaf("g", "开发人员", 2000);
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);
Leaf zhengLaoLiu = new Leaf("郑老六", "研发部副经理", 20000);
root.addSubordinate(k);
root.addSubordinate(developDep);
root.addSubordinate(salesDep);
root.addSubordinate(financeDep);
developDep.addSubordinate(zhengLaoLiu);
developDep.addSubordinate(firstDevGroup);
developDep.addSubordinate(secondDevGroup);
firstDevGroup.addSubordinate(a);
firstDevGroup.addSubordinate(b);
firstDevGroup.addSubordinate(c);
secondDevGroup.addSubordinate(d);
secondDevGroup.addSubordinate(e);
secondDevGroup.addSubordinate(f);
salesDep.addSubordinate(h);
salesDep.addSubordinate(i);
financeDep.addSubordinate(j);
return root;
}
public static String getTreeInfo(Branch root) {
ArrayList<Corp> subordinateList = root.subordinateList;
StringBuilder info = new StringBuilder();
for (Corp s : subordinateList) {
if (s instanceof Leaf) {
info.append(s.getInfo()).append("\n");
} else {
info.append(s.getInfo()).append("\n").append(getTreeInfo((Branch) s));
}
}
return info.toString();
}
}
定义
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
- Component抽象构件角色
定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性,比如我们例子中的getInfo就封装到了抽象类中。 - Leaf叶子构件
叶子对象,其下再也没有其他的分支,也就是遍历的最小单位。 - Composite树枝构件
树枝对象,它的作用是组合树枝节点和叶子节点形成一个树形结构。
public abstract class Component {
void doSomething(){
// 业务
}
}
public class Composite extends Component {
private final ArrayList<Component> componentArrayList = new ArrayList<>();
public void add(Component component) {
this.componentArrayList.add(component);
}
public void remove(Component component) {
this.componentArrayList.remove(component);
}
public ArrayList<Component> getChildren() {
return this.componentArrayList;
}
}
public class Leaf extends Component{
}
public class Client {
public static void main(String[] args) {
Composite root = new Composite();
root.doSomething();
Composite branch = new Composite();
Leaf leaf = new Leaf();
root.add(branch);
branch.add(leaf);
}
/**
* 通过递归遍历树
* @param root
*/
public static void display(Composite root) {
for (Component c : root.getChildren()) {
if (c instanceof Leaf) {
//叶子节点
c.doSomething();
} else { //树枝节点
display((Composite) c);
}
}
}
}
优点
- 高层模块调用简单
一棵树形机构中的所有节点都是Component,局部和整体对调用者来说没有任何区别, 也就是说,高层模块不必关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。 - 节点自由增加
使用了组合模式后,我们可以看看,如果想增加一个树枝节点、树叶节点都很容易,只要找到它的父节点就成,非常容易扩展,符合开闭原则,对以后的维护非常有利。
缺点
直接使用了实现类!这在面向接口编程上是很不恰当的,与依赖倒置原则冲突。
使用场景
- 维护和展示部分-整体关系的场景,如树形菜单、文件和文件夹管理。
- 从一个整体中能够独立出部分模块或功能的场景。
注意
只要是树形结构,就要考虑使用组合模式,这个一定要记住,只要是要体现局部和整体的关系的时候,而且这种关系还可能比较深,考虑一下组合模式吧。
扩展
真正的组合模式,一般都是通故数据表形成树形结构,它依靠了关系数据库的非对象存储性能。
透明模式
透明模式是把用来组合使用的方法放到抽象类中。
public class Leaf extends Component {
@Override
@Deprecated
public void add(Component component) {
throw new UnsupportedOperationException();
}
@Override
@Deprecated
public void remove(Component component) {
throw new UnsupportedOperationException();
}
@Override
@Deprecated
public ArrayList<Component> getChildren() {
throw new UnsupportedOperationException();
}
}
public class Client {
public static void display(Component root){
for (Component c : root.getChildren()) {
if (c instanceof Leaf){
c.doSomething();
}else {
display(c);
}
}
}
}
组合模式的遍历
添加getParent
setParent
方法。
public abstract class Corp {
private String name = "";
private String position = "";
private int salary = 0;
private Corp parent = null;
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;
}
public Corp getParent() {
return parent;
}
public void setParent(Corp parent) {
this.parent = parent;
}
}
public class Branch extends Corp{
private ArrayList<Corp> subordinateList = new ArrayList<>();
public Branch(String name, String position, int salary) {
super(name, position, salary);
}
public void addSubordinate(Corp corp){
corp.setParent(this);
this.subordinateList.add(corp);
}
public ArrayList<Corp> getSubordinate(){
return this.subordinateList;
}
}
最佳实践
组合模式在项目中到处都有,比如现在的页面结构一般都是上下结构,上面放系统的 Logo,下边分为两部分:左边是导航菜单,右边是展示区,左边的导航菜单一般都是树形的 结构,比较清晰,有非常多的JavaScript源码实现了类似的树形菜单,大家可以到网上搜索一下。
还有,大家常用的XML结构也是一个树形结构,根节点、元素节点、值元素这些都与我们的组合模式相匹配,之所以本章节不以XML为例子讲解,是因为很少有人还直接读写 XML文件,一般都是用JDOM或者DOM4J了。