合成模式/Composite
意图/适用场景:
合成模式又叫做部分-整体模式。它将对象组织到树结构中,可以用来描述整体与部分的关系。最显著的特点就是把整体与部分都抽象出统一的接口,这样客户端就可以把单纯元素与复合元素同等看待。
看下图所示的树状结构图。图中有两种节点,一种是树枝节点,一种是树叶节点。树根也是一种树枝节点,只不过它比较特殊,它没有父节点。
在构建这样的一种树状结构里,合成模式提供了三种元素:
- Component: 它是所有“节点”的抽象接口,无论是树枝还是树叶,首先都是Component。
- Leaf: 它是“树叶”的实现类,实现Component接口。
- Composite: 它是“树枝”的实现类,实现Component接口。它也是合成模式的关键元素,因为它可以使得整棵树无限地扩展开来。
树状结构的一个关键的问题是父节点对子节点的管理问题。父节点需要能够添加、删除和遍历子节点。管理子节点的方法只对树枝节点有效,所以可以选择只为树枝节点定义子节点管理方法,也可以选择把管理方法定义为所有节点的公共接口以保持接口的统一。 根据这两种选择的不同,合成模式分为安全式和透明式。
安全式
如果管理子节点的方法只在Composite/树枝节点中定义,这样的做法就是安全的做法,因为树叶类型的对象根本没有管理子节点的方法。如果客户端对树叶节点使用这些方法,程序会在编译期报错。 这种方式的缺点是不够透明,树叶类与树枝类具有不同的接口。
UML:
参与者:
示例代码:
[java]
// Source code from file:Component.javapackage designPatterns.Composite.Type1;
publicinterface Component {
publicvoid sampleOperation();
publicComposite getComposite();
}
// Source code from file:Leaf.java
packagedesignPatterns.Composite.Type1;
publicclass Leaf implements Component {
publicvoid sampleOperation() {
System.out.println("Leaf.sampleOperation()");
}
publicComposite getComposite() {
returnnull;
}
}
// Source code from file:Composite.java
packagedesignPatterns.Composite.Type1;
import java.util.*;
publicclass Composite implements Component {
@SuppressWarnings("unchecked")
privateVector componentVector = new Vector();
@SuppressWarnings("unchecked")
publicvoid sampleOperation() {
System.out.println("Composite.sampleOperation()");
Enumerationenu = getChilds();
while(enu.hasMoreElements()){
((Component)enu.nextElement()).sampleOperation();
}
}
publicComposite getComposite() {
returnthis;
}
@SuppressWarnings("unchecked")
publicvoid addChild(Component component) {
componentVector.addElement(component);
}
publicvoid removeChild(Component component) {
componentVector.removeElement(component);
}
@SuppressWarnings("unchecked")
publicEnumeration getChilds() {
returncomponentVector.elements();
}
}
[/java]
透明式
如果管理子节点的方法(add()、remove()和getChild()等)是在Component接口中定义的,这样的做法就是透明的做法。 这样做的好处是所有的构件类都有相同的接口,客户端可以同等地对待所有的对象。
这种方式的缺点是不够安全,因为管理子节点的方法其实对树叶节点并不适用,它们并没有子节点,这些方法没有意义,在逻辑上也说不通。
UML:
参与者:
- Component: 所有节点的公共接口,只定义公共方法,定义了管理子节点的方法。
- Leaf: 没有下级子对象的对象,是树状结构中的树叶。由于在Component中定义了管理子节点的方法,所以这里也要实现这些方法的空的实现。
- Composite: 有下级子对象的对象,是树状结构的树枝。拥有管理子对象的方法。
示例代码:
[java]
// Source code from file:Component.java
packagedesignPatterns.Composite.Type2;
import java.util.Enumeration;
publicinterface Component {
publicvoid sampleOperation();
publicComposite getComposite();
publicvoid addChild(Component component);
publicvoid removeChild(Component component);
@SuppressWarnings("unchecked")
publicEnumeration getChilds();
}
// Source code from file:Leaf.java
packagedesignPatterns.Composite.Type2;
import java.util.Enumeration;
publicclass Leaf implements Component {
@Override
publicvoid sampleOperation() {
System.out.println("Leaf.sampleOperation()");
}
@Override
publicComposite getComposite() {
returnnull;
}
@Override
publicvoid addChild(Component component) {
// Do nothing
}
@Override
publicvoid removeChild(Component component) {
// Do nothing
}
@SuppressWarnings("unchecked")
@Override
publicEnumeration getChilds() {
returnnull;
}
}
// Source code from file:Composite.java
packagedesignPatterns.Composite.Type2;
import java.util.Enumeration;
import java.util.Vector;
publicclass Composite implements Component {
@SuppressWarnings("unchecked")
privateVector componentVector = new Vector();
@SuppressWarnings("unchecked")
publicvoid sampleOperation() {
System.out.println("Composite.sampleOperation()");
Enumerationenu = getChilds();
while(enu.hasMoreElements()){
((Component)enu.nextElement()).sampleOperation();
}
}
publicComposite getComposite() {
returnthis;
}
@SuppressWarnings("unchecked")
publicvoid addChild(Component component) {
componentVector.addElement(component);
}
publicvoid removeChild(Component component) {
componentVector.removeElement(component);
}
@SuppressWarnings("unchecked")
publicEnumeration getChilds() {
returncomponentVector.elements();
}
}
[/java]
要点:
在两种方式的示例代码中,接口Component接口都定义了一个有趣的方法public Composite getComposite()。在这个方法的实现中,Leaf类对象返回null,而Composite类对象返回this。
我想这个方法的作用有两个:
- 一是可以区分对象的类型,如果返回值是null,说明对象是Leaf型;如果返回值不是null,说明对象是Composite型。
- 二是方便类型转换。这仅对Composite类型的意义。客户端如果确定了某个Component对象为Composite类型,接下来如果要调用Composite的某个方法(特别是它专有的方法)时,就需要先对这个Component作类型转换,转成Composite型,这并不能说是个好办法。而如果调用它的getComposite()方法,就可以直接拿到一个Composite对象,避免了直接类型转换。