15.组合模式(Composite Pattern)
1.定义
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
组合模式又叫做树形模式。其在项目开发中涉及树的结构都会想到组合模式。
2.引子
我们计算机的文件系统就是一个典型组合模式的使用,文件分为两种:一、文件夹,二、文件。其中文件夹可以包含文件,也可以包含子文件夹。如果我们用资源管理器打开某个文件夹,发现它展开在左侧的是一个树形结构。
而我们的组合模式就是为了解决这种树形结构的递归问题。这种问题在生活中很常见,比如某个机关的人事关系图。
3.组合模式的使用场景
- 维护和展示部分-整体关系的场景,比如树形菜单、文件和文件夹管理。
- 从一个整体中能独立出部分模块或功能的场景。
下面以人事关系的代码为例讲解该模式的实现:
package _15CompositePattern; /** * 某公司的抽象员工类 * 无论领导或者普通职员都有的属性 */ public abstract class Corp { private String name;// 姓名 private int salary;// 薪水 public Corp(String name, int salary) { this.name = name; this.salary = salary; } public void prinInfo() { System.out.println("Name: " + name + ", Salary:" + salary); } }
package _15CompositePattern; import java.util.ArrayList; import java.util.List; /** * 领导类 * 出了拥有普通员工的权限,偶尔还要关怀一下下属,要不他们不干活 */ public class Branch extends Corp { // 手下的所有下属 private List<Corp> subList = new ArrayList<Corp>(); public Branch(String name, int salary) { super(name, salary); } // 增加一个下属,这个下属可能是小兵,也可能是领导(当然职位比我低) public void addSub(Corp sub) { subList.add(sub); } // 获得我的所有下属 public List<Corp> getSubList() { return subList; } }
package _15CompositePattern; /** * 普通员工类 * 除了自己不需要关心任何别人的事情 */ public class Leaf extends Corp { public Leaf(String name, int salary) { super(name, salary); } }
package _15CompositePattern; public class Client { /** * @param args */ public static void main(String[] args) { Corp bossCorp = new Branch("张三", 100000); // 设置了张三是一个boss,给他加小弟的代码就不详细写了 // ... printAllCorpInfo(bossCorp); } // 给我根节点,我打印出全部信息 public static void printAllCorpInfo(Corp root) { if(root instanceof Leaf) { root.prinInfo(); } else { for(Corp sub : ((Branch)root).getSubList()) { printAllCorpInfo(sub); } } } }
4.组合模式的三个角色
- Component抽象构件角色:定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性,比如我们例子中的name属性,prinInfo()方法就封装在抽象类中
- Leaf叶子构件:叶子对象,其下再也没有其他的分支,也就是遍历的最小单位
- Composite树枝构件:树枝对象,它的作用是组合树枝节点和叶子节点形成一个树状结构
5.组合模式的类型
组合模式中必须提供对子对象的管理方法,不然无法完成对子对象的添加删除等等操作,也就失去了灵活性和扩展性。但是管理方法是在Component中就声明还是在Composite中声明呢?
上面例子中给出的代码叫做安全模式,只在Composite里面声明所有的用来管理子类对象的方法(如下图所示)。这样就避免了上一种方式的安全性问题,但是由于叶子和分支有不同的接口,所以又失去了透明性。
另一种方式是在Component里面声明所有的用来管理子类对象的方法,以达到Component接口的最大化(如下图所示)。目的就是为了使客户看来在接口层次上树叶和分支没有区别——透明性。但树叶是不存在子类的,因此Component声明的一些方法对于树叶来说是不适用的。这样也就带来了一些安全性问题。
《设计模式》一书认为:在这一模式中,相对于安全性,我们比较强调透明性。对于第一种方式中叶子节点内不需要的方法可以使用空处理或者异常报告的方式来解决。
参考一下Java中File类型的实现,它同时包含了文件和文件夹的操作方法,可以看出是一种透明类型的组合模式。
6.组合模式的优点
- 高层模块调用简单:一棵树形机构中的所有节点都是Component,局部和整体对调用者来说没有任何区别,也就是说,高层模块不必关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。
- 节点*增加:想要增加一个节点,只需要找到它的父节点就行了,非常方便。
7.组合模式的缺点
组合模式有一个非常明显的缺点,看到我们场景类中,树枝和树叶直接使用了实现类。这在面向接口编程上是很不恰当的,与依赖倒置原则冲突。
8.组合模式的注意事项
只要是树形结构就要考虑使用组合模式,只要是要体现局部和整体的关系的时候,而且这种关系还比较深,考虑一下组合模式吧。