组合模式
组合模式(部分-整体模式):将对象组合成树形结构以表示 ‘ 部分-整体 ’ 的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
以淘宝首页为例,假设服装节点下包含男装和女装,而男装作为节点又包含了夹克和衬衣,女装包含了裙子和套装,怎么使用代码体现呢?
这种类似于树的结构,可以使用组合模式来实现。
先来看一下以该例子写的组合模式的UML类图
其中clothes作为抽象类存在,定义了节点的删除增加和展示的方法。
composite继承了Component组件类,用来表示非叶子节点。
leaf同样继承了Component组件类,用来表示叶子节点。
只看UML类图就可以看出来该模式跟装饰模式有异曲同工之妙,装饰模式是具体的组件类和装饰类同级,可以借助接口进行相互调用,而组合模式是叶子类和非叶子类同级,也可以相互调用。
叶子节点和非叶子节点的唯一区别就是能不能继续添加节点,能不能对其子节点进行删除。
来看一下示例性代码:
package ch13.sjms.composite;
public abstract class clothes {//抽象的服装组件类
protected String name;
public clothes(String name){
this.name=name;
}
public abstract void Add(clothes c);
public abstract void Remove(clothes c);
public abstract void Display(int depth);
}
package ch13.sjms.composite;
import java.util.ArrayList;
import java.util.List;
public class composite extends clothes{//非叶子节点
//由于非叶子节点可能有多个子节点,所以此处使用了List类型
private List<clothes>children = new ArrayList<clothes>();
//由于非叶子节点的子节点可能是叶子节点也可能是非叶子节点,所以List类型使用了clothes抽象的组件类
//用来存放其子节点,保证List中既可以添加叶子节点也可以添加非叶子节点
public composite(String name){
super(name);
}
@Override
public void Add(clothes c) {
children.add(c);
}
@Override
public void Remove(clothes c) {
children.remove(c);
}
@Override
public void Display(int depth) {
for (int i = 0; i < depth; i++) {
System.out.print("-");
}
System.out.println(name);//对非叶子节点进行输出的时候,先输出该节点
for (clothes child : children) {//然后遍历其子节点
child.Display(depth+2);
}
}
}
package ch13.sjms.composite;
public class leaf extends clothes{//叶子节点
public leaf(String name){
super(name);
}
@Override
public void Add(clothes c) {
System.out.println("不能添加树叶了!");
}
@Override
public void Remove(clothes c) {
System.out.println("不能移除树叶了");
}
@Override
public void Display(int depth) {
for (int i = 0; i < depth; i++) {
System.out.print("-");
}
System.out.println(name);
}
}
package ch13.sjms.composite;
public class client {
public static void main(String[] args) {
clothes root = new composite("服装");
clothes clothes1 = new composite("男装");
clothes clothes2 = new composite("女装");
clothes1.Add(new leaf("夹克"));
clothes1.Add(new leaf("衬衣"));
clothes2.Add(new leaf("裙子"));
clothes2.Add(new leaf("套装"));
root.Add(clothes1);
root.Add(clothes2);
root.Display(1);
}
}
看完UML类图再看代码,不难看出,UML类图中leaf类明明没有Add和Remove方法,虽然这两个方法对于叶子节点没有什么实际意义,可是代码中还是写了出来。
在上述代码中体现的组合模式的方式叫做透明方式。也就是说在Component中生命所有用来管理子对象的方法,其中包括Add、Remove等。这样实现Component接口的所有子类都具备了Add和Remove。这样做的好处就是叶节点和枝节点对于外界没有区别,它们具备完全一样的行为接口。但问题也很明显,因为leaf类本身不具备Add、Remove方法,所以实现它们是没有意义的。
和透明方式相对的就是安全方式。也就是在Component接口中不去声明Add和Remove方法,那么子类leaf也不需要去实现它,而是在Composite声明所有用来管理子类对象的方法,这样就不会出现透明方式提到的问题。不过由于不够透明,所以树叶和树枝类将不具有相同的接口,客户端调用还需要做相应的判断,带来了不便。
在开发的时候,应该结合实际情况而作出选择。
需求中是体现部分与整体层次的结构时,希望用户可以忽略组合对象与单个对象的不同,统一使用组合结构中的所有对象时,就应该考虑用组合模式。
组合模式定义了包含基本对象和组合对象的类层次结构。基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断地递归下去,客户代码中,任何用到基本对象地地方都可以使用组合对象了。
用户是不用关心到底是处理一个叶节点还是处理一个组合组件,也就用不着为定义组合而写一些选择判断语句了。也就是说,组合模式让客户可以一致地使用组合结构和单个对象。
组合模式的缺点:很难限制组合中的组件类型。