1.1概述
将对象组合成树形结构以表示“部分-整体”的层次结构。组合(Composite)使用户对单个对象和组合对象的使用具有一致性。这就是组合模式的定义。
如果一个对象包含另一个对象的引用,称这样的对象为组合对象。如果将当前组合对象作为一个整体的话,那么它所包含的对象就是该整体的一部分。如果一个对象不含有其他对象的引用,称这样的对象为个体对象。在编写程序时,我们希望将许多个体对象和组合对象组成树形结构,以此表示“部分-整体”的层次结构,并借助该层次结构使得用户能用一致的方式处理个体对象和组合对象。在组成的树形结构中,个体对象和组合对象都是树中的节点,但是,组合对象拥有其他节点的对象,个体对象是不具有其他子节点的叶节点。也就是说在树形结构中,组合对象所含有的对象将作为该组合对象的子节点被对待。
例如,我们常用软件上的菜单栏,分为一级菜单,二级菜单,三级菜单。其中,一级菜单中包含二级菜单,二级菜单中包含三级菜单。具体示意如下图一:
图一:菜单栏分级示例
组合模式是关于怎样将对象形成树形结构来表现整体和部分的层次结构的成熟模式。使用组合模式,可以让用户以一致的方式处理组合对象和个体对象,组合模式的关键在于无论是个体还是组合对象都实现了相同的接口或都是同一个抽象类的子类。
1.2模式的结构
组合模式包含有以下三种角色:
(1)抽象组件(Component):是一个接口(抽象类),该接口(抽象类)定义了个体对象和组合对象需要实现的关于操作其子节点的方法,比如add()、remove()以及getChild()等方法。抽象组件也可以定义个体对象和组合对象用于操作其自身的方法,比如isLeaf()。
(2)Composite节点(Composite Node):实现Component接口(或者抽象类)类的实例,Composite节点不仅实现Component接口,而且可以含有其他Composite节点或Leaf节点的引用。
(3)Leaf节点(Leaf Node):实现Component接口(或抽象类)类的实例,Leaf节点实现Component接口,不可以含有其他Composite节点或Leaf节点的引用,因此,叶节点在实现Component接口有关操作子节点的方法时,比如add()、remove()和getChild()方法,可让方法抛出一个异常,也可以实现为空操作。
组合模式结构的类图如下图二所示:
图二:组合模式的类图
1.3组合模式的优点
(1)组合模式中包含个体对象和组合对象,并形成树形结构,使用户可以方便地处理个体对象和组合对象。
(2)组合对象和个体对象实现了相同的接口,用户一般无须区分个体对象和组合对象。
(3)当增加新的Composite节点和Leaf节点时,用户的重要代码不需要做出修改。
1.4适合使用组合模式的情景
(1)当想表示对象的部分-整体层次结构。
(2)希望用户用一致的方式处理个体对象和组合对象。
1.5组合模式的使用
以下通过一个简单的问题来描述怎样使用组合模式,这个问题就是用组合墨水描述连队的军士结构,并计算军饷。一个连队由一个连长,2个排长,6个班长和60个士兵所构成,一共69人。连长直接指挥2个排长,每个排长直接指挥3个班长,每个班长直接指挥10个士兵。连长的军饷是每月5000元,排长是4000元,班长是2000元,士兵是1000元。现在使用组合模式,让连队的军士形成树形结构,并计算了一个班的军饷、一个排的军饷和整个连队的军饷。
首先看一下本实例构建框架具体类和1.2模式的结构中类图的对应关系,如下图所示:
(1)抽象组件(Component)
本问题中,抽象组件的名字是MilitaryPerson,MilitaryPerson接口的代码如下:
package com.liuzhen.fifteen_composite; import java.util.Iterator; public interface MilitaryPerson { public void add(MilitaryPerson person); public void remove(MilitaryPerson person); public MilitaryPerson getChild(int index); public Iterator<MilitaryPerson> getAllChildren(); public boolean isLeaf(); public double getSalary(); public void setSalary(double salary); }
(2)Composite节点
对于本问题,Composite节点时MilitaryOfficer类的实例,MilitaryOfficer类的代码如下:
package com.liuzhen.fifteen_composite; import java.util.Iterator; import java.util.LinkedList; public class MilitaryOfficer implements MilitaryPerson { LinkedList<MilitaryPerson> list; String name; double salary; MilitaryOfficer(String name, double salary){ this.name = name; this.salary = salary; list = new LinkedList<MilitaryPerson>(); } public void add(MilitaryPerson person) { // TODO Auto-generated method stub list.add(person); } public void remove(MilitaryPerson person) { // TODO Auto-generated method stub list.remove(person); } public MilitaryPerson getChild(int index) { // TODO Auto-generated method stub return list.get(index); } public Iterator<MilitaryPerson> getAllChildren() { // TODO Auto-generated method stub return list.iterator(); } public boolean isLeaf() { // TODO Auto-generated method stub return false; } public double getSalary() { // TODO Auto-generated method stub return salary; } public void setSalary(double salary) { // TODO Auto-generated method stub this.salary = salary; } }
(3)Leaf节点
对于本问题,Leaf节点是MilitarySoldier类的实例,MilitarySoldier类的代码如下:
package com.liuzhen.fifteen_composite; import java.util.Iterator; public class MilitarySoldier implements MilitaryPerson { double salary; String name; MilitarySoldier(String name, double salary){ this.name = name; this.salary = salary; } public void add(MilitaryPerson person) { // TODO Auto-generated method stub } public void remove(MilitaryPerson person) { // TODO Auto-generated method stub } public MilitaryPerson getChild(int index) { // TODO Auto-generated method stub return null; } public Iterator<MilitaryPerson> getAllChildren() { // TODO Auto-generated method stub return null; } public boolean isLeaf() { // TODO Auto-generated method stub return true; } public double getSalary() { // TODO Auto-generated method stub return salary; } public void setSalary(double salary) { // TODO Auto-generated method stub this.salary = salary; } }
(4)具体使用
通过FifApplication类来具体实现上述相关类和接口,来实现组合模式的运用,其代码如下:
package com.liuzhen.fifteen_composite; public class FifteenApplication { public static void main(String[] args){ MilitaryPerson 连长 = new MilitaryOfficer("连长",5000); MilitaryPerson 排长1 = new MilitaryOfficer("一排长",4000); MilitaryPerson 排长2 = new MilitaryOfficer("二排长",4000); MilitaryPerson 班长11 = new MilitaryOfficer("一班长",2000); MilitaryPerson 班长12 = new MilitaryOfficer("二班长",2000); MilitaryPerson 班长13 = new MilitaryOfficer("三班长",2000); MilitaryPerson 班长21 = new MilitaryOfficer("一班长",2000); MilitaryPerson 班长22 = new MilitaryOfficer("二班长",2000); MilitaryPerson 班长23 = new MilitaryOfficer("三班长",2000); MilitaryPerson[] 士兵 = new MilitarySoldier[60]; for(int i = 0;i < 士兵.length;i++) 士兵[i] = new MilitarySoldier("小兵",1000); 连长.add(排长1); 连长.add(排长2); 排长1.add(班长11); 排长1.add(班长12); 排长1.add(班长13); 排长2.add(班长21); 排长2.add(班长22); 排长2.add(班长23); for(int i = 0;i <= 9;i++){ 班长11.add(士兵[i]); 班长12.add(士兵[i+10]); 班长13.add(士兵[i+20]); 班长21.add(士兵[i+30]); 班长22.add(士兵[i+40]); 班长23.add(士兵[i+50]); } System.out.println("一排的军饷:"+ComputerSalary.computerSalary(排长1)); System.out.println("一班的军饷:"+ComputerSalary.computerSalary(班长11)); System.out.println("全连的军饷:"+ComputerSalary.computerSalary(连长)); } }
其中计算军饷的类 ComputerSalary.java代码如下:
package com.liuzhen.fifteen_composite; import java.util.Iterator; public class ComputerSalary { public static double computerSalary(MilitaryPerson person){ double sum = 0; if(person.isLeaf() == true) sum = sum + person.getSalary(); if(person.isLeaf() == false){ sum = sum + person.getSalary(); Iterator<MilitaryPerson> iterator = person.getAllChildren(); while(iterator.hasNext()){ MilitaryPerson p = iterator.next(); sum = sum + computerSalary(p); } } return sum; } }
运行结果:
一排的军饷:40000.0 一班的军饷:12000.0 全连的军饷:85000.0
参考资料:
1.Java设计模式/耿祥义,张跃平著.——北京:清华大学出版社,2009.5