定义:将对象组合成树形结构以表示 “部分 — 整体” 的层次结构。Composite模式使得用户对单个对象和组合对象的使用具有一致性。
类型:对象结构型模式
类图:
组合模式的结构
- 抽象构件角色(component):是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。
- 这个接口可 以用来管理所有的子对象。(可选)在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。
- 树叶构件角色(Leaf):在组合树中表示叶节点对象,叶节点没有子节点。并在组合中定义图元对象的行为。
- 树枝构件角色(Composite):定义有子部件的那些部件的行为。存储子部件。在Component接口中实现与子部件有关的操作。
- 客户角色(Client):通过component接口操纵组合部件的对象。
组合模式(Composite)有两种实现方式:透明方式和安全方式
透明方式:在Component中声明所有用来管理子对象的方法,如Add()方法,Remove()方法及GetChild()方法,所有实现Component接口的子类都具备这些方法,这使得Component和子类具备一致的行为接口,使得对客户端无需区别树叶和树枝对象。
大家可以回忆一下代理模式(Proxy)中,Proxy,RealSubject类和Subject接口具备一致的行为接口,从而使得被代理者对于客户端是透明的。
正由于我们的Composite和Leaf都具备一致的接口行为,但我们知道Leaf不应该具有Add(),Remove()及GetChild()方法,因为我们叶子节点不能再添加和移除节点了。
安全模式:在透明模式基础上把Component中声明所有用来管理子对象的方法移到Composite中,在Composite实现子对象的管理方法,那么Leaf就没有子对象管理方法,这使得Composite和Leaf的行为接口不一致,所以客户端在调用时要知道树叶和树枝对象存在。
Composite模式透明方式实现代码
首先我们定义一个抽象类Componet,在其中声明add(),remove()等子对象操作
// 抽象类接口 Component
abstract class Component {
public abstract int getChildNum();
public abstract String getName();
public abstract String getType();
public abstract void add(Component c);
public abstract void remove(Component c);
}
接着我们定义Composite类继承于Component,增加childList保存Component对象的引用,从而建立起由Component到Composite的聚集关系(Has-a关系),并且实现Component抽象类中的抽象方法。
// 树枝节点 Composite类继承于Component
class Composite extends Component {
private String nodeName;
private List<Component> childList; //保存孩子节点
public Composite(String nodeName) {
this.nodeName = nodeName;
childList = new ArrayList<Component>();
}
@Override
public int getChildNum() {
return childList.size();
}
@Override
public String getName() {
return this.nodeName;
}
@Override
public String getType() {
return "Composite";
}
@Override
public void add(Component c) {
childList.add(c);
}
@Override
public void remove(Component c) {
childList.remove(c);
}
}
最后我们定义Leaf类它也是继承于Component,在其中我们实现Component定义的行为接口,这使得Composite和Leaf类具有统一的接口行为,但我们并没有在Leaf的行为方法中给出具体的实现,因为叶子节点并没有后继,所以没有必要实现Add()和Remove()等行为。
// 叶子节点 Leaf类 也继承于Component
class Leaf extends Component {
private String nodeName;
public Leaf(String nodeName) {
this.nodeName = nodeName;
}
@Override
public int getChildNum() {
return 0;
}
@Override
public String getName() {
return this.nodeName;
}
@Override
public String getType() {
return "Leaf";
}
@Override
public void add(Component c) {
System.err.println("ERROR ! Leaf Not supported add method!");
}
@Override
public void remove(Component c) {
System.err.println("ERROR ! Leaf Not supported remove method!");
}
}
客户调用
//客户 Client
public class CompositeClient {
/**
* @param args
*/
public static void main(String[] args) {
Component root = new Composite("root"); //根节点
Component composite = new Composite("composite"); //树枝节点
//叶节点
Component leaf1 = new Leaf("leaf1");
Component leaf2 = new Leaf("leaf2");
composite.add(leaf2);
root.add(leaf1);
root.add(composite);
String str = "Leaf1's size is " + leaf1.getChildNum();
str += "\nleaf2's size is " + leaf2.getChildNum();
str += "\nComposite's size is " + composite.getChildNum();
str += "\nRoot's size is " + root.getChildNum();
System.out.println(str);
}
}
Composite适用场景
- 想表示对象的 部分 — 整体 层次结构。
- 希望用户忽略 组合对象与单个对象的不同,用户将统一的使用组合结构中的所有对象。
Composite组合模式的几个要点:
- Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致的处理对象和对象容器,无需关心处理的是单个对象,还是组合的对象容器。
- 将“客户代码与复杂的对象容器结构”解耦是Composite模式的核心思想,解耦之后,客户代码将与纯粹的对象接口——而非对象容器的复杂内部实现结构——发生依赖关系,从而更能“应对变化”。
- Composite模式中,是将“Add和Remove的和对象容器相关的方法”定义在“表示抽象对象的Component类”中,还是将其定义在“表示对象容器的Composite类”中,是一个关乎“透明性”和“安全性”的两难问题,需要仔细权衡结构,这又是必须付出的代价。
- Composite模式在具体实现中,可以让父对象中的字对象反向追溯:如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率
总结
- 组合模式(Composite)采用树形层次结构来实现对象容器,如:绘图程序例子我们把各种各样的图形添加的Shape中,从而构造起一条对象链,把“一对多”的关系转化“一对一”的层次关系,使得客户代码可以一致地处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。
- 组合模式(Composite)中,透明方式和安全方式的使用抉择,虽然透明方式有可能违背面向对象的SRP原则(单一职责),而安全方式没有很好的封装变化,但在实际开发时我们要根据具体的情况权衡利弊。