在项目开发中,有时候我们会遇到将一个对象组合成一个树形结构,来表示“整体-部分”的层次关系,但用户在操作的时候,不需要区分组合对象(树枝节点,包含子节点)或单个对象(叶子节点,不包含子节点),保持一致的操作体验。这时候我们就可以利用组合模式来实现我们的应用。
定义:将对象组合成树形结构以表示“部分-整体”的层次结构, 使得用户对单个对象和组合对象的使用具有一致性。
一、透明实现
1、在组合模式中首先我们要定义一个抽象类,实现树枝节点和叶子节点的公共属性及方法(例如下面代码中的name,position,salary属性及getInfo()方法),并且定义树枝节点,叶子节点的公共抽象方法(例如下例中的:addSubordinete()方法,getAllSubordinates()方法),用来给树枝节点和叶子节点个性实现。
package composite.transparent;
import java.util.ArrayList;
/**
* 抽象类,定义树枝节点和叶子节点的公共属性,方法
* 定义抽象方法,供树枝节点和叶子节点实现
*/
public abstract class AbstractCorp {
private String name;//姓名
private String position;//职位
private String salary;//薪水
//构造方法
public AbstractCorp(String name,String position,String salary){
this.name=name;
this.position=position;
this.salary=salary;
}
//抽象方法 增加一个子节点,可能是树枝节点,也可能是树叶节点
public abstract void addSubordinate(AbstractCorp corp);
//抽象方法 返回所有的子节点
public abstract ArrayList getAllSubordinates();
//返回信息
protected void getInfo(){
System.out.println("姓名:"+this.name+" 职位:"+this.position+" 薪资:"+this.salary);
}
}
2、然后我们再定义树枝节点类和叶子节点类继承抽象类,使得树枝节点类和叶子节点类不仅继承抽象类的公共方法和属性,还可以个性化的实现抽象类的抽象方法。
树枝节点类:
package composite.transparent;
import java.util.ArrayList;
import composite.transparent.AbstractCorp;
/**
* 树枝节点类
*/
public class Branch extends AbstractCorp{
private ArrayList subordinateList=new ArrayList();//子节点集合
//构造方法,调用父类的构造方法
public Branch(String name, String position, String salary) {
super(name, position, salary);
// TODO Auto-generated constructor stub
}
//实现抽象类的方法 增加一个子节点,可能是树枝节点,也可能是树叶节点
@Override
public void addSubordinate(AbstractCorp corp) {
// TODO Auto-generated method stub
if(this.subordinateList!=null){
this.subordinateList.add(corp);
}
}
//实现抽象类的方法 返回所有的子节点
@Override
public ArrayList getAllSubordinates() {
// TODO Auto-generated method stub
return this.subordinateList;
}
}
叶子节点类:
package composite.transparent;
import java.util.ArrayList;
/**
* 叶子节点
*/
public class Leaf extends AbstractCorp{
//构造方法,调用父类的构造方法
public Leaf(String name, String position, String salary) {
super(name, position, salary);
// TODO Auto-generated constructor stub
}
/**
* 实现抽象类的方法,叶子节点不能再添加子节点,所以
* @Deprecated注解,表示“不建议使用”,
* 方法空实现抛出一个UnsupportedOperationException异常
*/
@Deprecated
@Override
public void addSubordinate(AbstractCorp corp){
// TODO Auto-generated method stub
throw new UnsupportedOperationException();////空实现,直接抛弃一个"不支持请求"异常
}
/**
* 实现抽象类的方法,叶子节点不包含任何子节点,所以
* @Deprecated注解,表示“不建议使用”,
* 方法空实现抛出一个UnsupportedOperationException异常
*/
@Deprecated
@Override
public ArrayList getAllSubordinates() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException();////空实现,直接抛弃一个"不支持请求"异常
}
}
场景类:
package composite.transparent;
import java.util.ArrayList;
/**
* 场景类
*/
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
AbstractCorp ceo=new Branch("章建丰","总经理","200w");//总经理
AbstractCorp techDirector=new Branch("周勇军","技术总监","100w");//技术总监
techDirector.addSubordinate(new Leaf("张涛","IOS开发工程师","20k"));//ios开发工程师
techDirector.addSubordinate(new Leaf("魏庭聪","Android开发工程师","20k"));//android开发工程师
techDirector.addSubordinate(new Leaf("冯适","PHP开发工程师","20k"));//php开发工程师
AbstractCorp marketDirector=new Branch("宗玺","市场总监","100w");//市场总监
marketDirector.addSubordinate(new Leaf("张敏","市场营销","30k"));//市场营销
marketDirector.addSubordinate(new Leaf("史帅","市场营销","30k"));//市场营销
marketDirector.addSubordinate(new Leaf("王耀","市场营销","30k"));//市场营销
ceo.addSubordinate(techDirector);
ceo.addSubordinate(marketDirector);
print(ceo);//遍历所有节点信息
}
/**
* 打印所有节点信息
* @param root 根节点
*/
private static void print(AbstractCorp root){
ArrayList subordinates=root.getAllSubordinates();
for(AbstractCorp subordinate:subordinates){
if(subordinate instanceof Leaf){//叶子节点
subordinate.getInfo();//打印叶子节点的信息
}else{
subordinate.getInfo();//打印树枝节点信息
print((Branch)subordinate);//递归遍历打印
}
}
}
}
所有对象声明为AbstractCorp类型,用户不需要知道自己调用的是组合对象还是单个对象,保持一致性。
如果我们创建一个叶子节点,调用addSubordinate()方法
AbstractCorp leaf=new Leaf("史帅","市场营销","30k");//创建一个叶子节点
leaf.addSubordinate(new Leaf("王耀","市场营销","30k"));//给叶子节点在添加一个子节点
运行,报错:
这是正确的,因为叶子节点不能再添加子节点。但同样这也是不安全的,可能会在运行时出现错误,所以还有另外一种安全的组合模式。
二、安全实现
1、同样首先我们要定义一个抽象类,定义树枝节点和叶子节点的公共属性和方法。
package composite.safe;
import java.util.ArrayList;
/**
* 抽象类,定义树枝节点和叶子节点的公共属性,方法
*/
public abstract class AbstractCorp {
private String name;//姓名
private String position;//职位
private String salary;//薪水
//构造方法
public AbstractCorp(String name,String position,String salary){
this.name=name;
this.position=position;
this.salary=salary;
}
//返回信息
protected void getInfo(){
System.out.println("姓名:"+this.name+" 职位:"+this.position+" 薪资:"+this.salary);
}
}
2、树枝节点
package composite.safe;
import java.util.ArrayList;
/**
* 树枝节点类
*/
public class Branch extends AbstractCorp{
private ArrayList subordinateList=new ArrayList();//子节点集合
//构造方法,调用父类的构造方法
public Branch(String name, String position, String salary) {
super(name, position, salary);
// TODO Auto-generated constructor stub
}
//增加一个子节点,可能是树枝节点,也可能是树叶节点
public void addSubordinate(AbstractCorp corp){
if(this.subordinateList!=null){
this.subordinateList.add(corp);
}
}
//返回所有的子节点
public ArrayList getAllSubordinates(){
return this.subordinateList;
}
}
继承AbstractCorp类,并且添加了addSubordinate()和getAllSubordinates()方法。
3、叶子节点
package composite.safe;
/**
* 树叶节点
*/
public class Leaf extends AbstractCorp{
//构造方法,调用父类的构造方法
public Leaf(String name, String position, String salary) {
super(name, position, salary);
// TODO Auto-generated constructor stub
}
}
叶子节点没有子节点,所以没有addSubordinate()和getAllSubordinates()方法。没有添加子节点方法,也就不会出现错误了。
4、场景类
package composite.safe;
import java.util.ArrayList;
/**
* 场景类
*/
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
Branch ceo=new Branch("章建丰","总经理","200w");//总经理
Branch techDirector=new Branch("周勇军","技术总监","100w");//技术总监
techDirector.addSubordinate(new Leaf("张涛","IOS开发工程师","20k"));//ios开发工程师
techDirector.addSubordinate(new Leaf("魏庭聪","Android开发工程师","20k"));//android开发工程师
techDirector.addSubordinate(new Leaf("冯适","PHP开发工程师","20k"));//php开发工程师
Branch marketDirector=new Branch("宗玺","市场总监","100w");//市场总监
marketDirector.addSubordinate(new Leaf("张敏","市场营销","30k"));//市场营销
marketDirector.addSubordinate(new Leaf("史帅","市场营销","30k"));//市场营销
marketDirector.addSubordinate(new Leaf("王耀","市场营销","30k"));//市场营销
ceo.addSubordinate(techDirector);
ceo.addSubordinate(marketDirector);
print(ceo);//遍历所有节点信息
}
/**
* 打印所有节点信息
* @param root 根节点
*/
private static void print(Branch root){
ArrayList subordinates=root.getAllSubordinates();
for(AbstractCorp subordinate:subordinates){
if(subordinate instanceof Leaf){//叶子节点
subordinate.getInfo();//打印叶子节点的信息
}else{
subordinate.getInfo();//打印树枝节点信息
print((Branch)subordinate);//递归遍历树枝节点
}
}
}
}
但是,对象无法统一指定为抽象类型,也就是用户需要区分对象是树枝节点还是叶子节点,这与组合模式的定义相违背。
优点:
● 高层模块调用简单
一棵树形机构中的所有节点都是Component, 局部和整体对调用者来说没有任何区别,也就是说, 高层模块不必关心自己处理的是单个对象还是整个组合结构, 简化了高层模块的代码。
● 节点自由增加
使用了组合模式后, 我们可以看看, 如果想增加一个树枝节点、 树叶节点是不是都很容易, 只要找到它的父节点就成, 非常容易扩展, 符合开闭原则, 对以后的维护非常有利。
项目地址