合成模式/Composite

意图/适用场景:

合成模式又叫做部分-整体模式。它将对象组织到树结构中,可以用来描述整体与部分的关系。最显著的特点就是把整体与部分都抽象出统一的接口,这样客户端就可以把单纯元素与复合元素同等看待。

看下图所示的树状结构图。图中有两种节点,一种是树枝节点,一种是树叶节点。树根也是一种树枝节点,只不过它比较特殊,它没有父节点。

在构建这样的一种树状结构里,合成模式提供了三种元素:

  1. Component: 它是所有“节点”的抽象接口,无论是树枝还是树叶,首先都是Component。
  2. Leaf: 它是“树叶”的实现类,实现Component接口。
  3. Composite: 它是“树枝”的实现类,实现Component接口。它也是合成模式的关键元素,因为它可以使得整棵树无限地扩展开来。
合成模式/Composite_设计模式

树状结构的一个关键的问题是父节点对子节点的管理问题。父节点需要能够添加、删除和遍历子节点。管理子节点的方法只对树枝节点有效,所以可以选择只为树枝节点定义子节点管理方法,也可以选择把管理方法定义为所有节点的公共接口以保持接口的统一。 根据这两种选择的不同,合成模式分为安全式和透明式。

安全式

如果管理子节点的方法只在Composite/树枝节点中定义,这样的做法就是安全的做法,因为树叶类型的对象根本没有管理子节点的方法。如果客户端对树叶节点使用这些方法,程序会在编译期报错。 这种方式的缺点是不够透明,树叶类与树枝类具有不同的接口。

UML:

合成模式/Composite_设计模式_02

参与者: 1. Component: 所有节点的公共接口,只定义公共方法,不定义管理子节点的方法。 2. Leaf: 没有下级子对象的对象,是树状结构中的树叶。 3. Composite: 有下级子对象的对象,是树状结构的树枝。拥有管理子对象的方法。

示例代码:

   [java]
// Source code from file:  Component.javapackage designPatterns.Composite.Type1;

public interface Component {
public void sampleOperation();
public Composite getComposite();
}

// Source code from file:  Leaf.java

package designPatterns.Composite.Type1;

public class Leaf implements Component {

public void sampleOperation() {
System.out.println("Leaf.sampleOperation()");
}

public Composite getComposite() {
return null;
}
}

// Source code from file:  Composite.java

package designPatterns.Composite.Type1;

import java.util.*;

public class Composite implements Component {

@SuppressWarnings("unchecked")
private Vector componentVector = new Vector();

@SuppressWarnings("unchecked")
public void sampleOperation() {
System.out.println("Composite.sampleOperation()");
Enumeration enu = getChilds();

while(enu.hasMoreElements()) {
((Component)enu.nextElement()).sampleOperation();
}
}

public Composite getComposite() {
return this;
}

@SuppressWarnings("unchecked")
public void addChild(Component component) {
componentVector.addElement(component);
}

public void removeChild(Component component) {
componentVector.removeElement(component);
}

@SuppressWarnings("unchecked")
public Enumeration getChilds() {
return componentVector.elements();
}
}

[/java]

透明式

如果管理子节点的方法(add()、remove()和getChild()等)是在Component接口中定义的,这样的做法就是透明的做法。 这样做的好处是所有的构件类都有相同的接口,客户端可以同等地对待所有的对象。

这种方式的缺点是不够安全,因为管理子节点的方法其实对树叶节点并不适用,它们并没有子节点,这些方法没有意义,在逻辑上也说不通。

UML:

合成模式/Composite_设计模式_03

参与者:

  1. Component: 所有节点的公共接口,只定义公共方法,定义了管理子节点的方法。
  2. Leaf: 没有下级子对象的对象,是树状结构中的树叶。由于在Component中定义了管理子节点的方法,所以这里也要实现这些方法的空的实现。
  3. Composite: 有下级子对象的对象,是树状结构的树枝。拥有管理子对象的方法。

示例代码:

   [java]
// Source code from file:  Component.java

package designPatterns.Composite.Type2;

import java.util.Enumeration;

public interface Component {

public void sampleOperation();
public Composite getComposite();
public void addChild(Component component);
public void removeChild(Component component);
@SuppressWarnings("unchecked")
public Enumeration getChilds();
}

// Source code from file:  Leaf.java

package designPatterns.Composite.Type2;

import java.util.Enumeration;

public class Leaf implements Component {

@Override
public void sampleOperation() {
System.out.println("Leaf.sampleOperation()");
}

@Override
public Composite getComposite() {
return null;
}

@Override
public void addChild(Component component) {
// Do nothing
}

@Override
public void removeChild(Component component) {
// Do nothing
}

@SuppressWarnings("unchecked")
@Override
public Enumeration getChilds() {
return null;
}

}

// Source code from file:  Composite.java

package designPatterns.Composite.Type2;

import java.util.Enumeration;
import java.util.Vector;

public class Composite implements Component {

@SuppressWarnings("unchecked")
private Vector componentVector = new Vector();

@SuppressWarnings("unchecked")
public void sampleOperation() {
System.out.println("Composite.sampleOperation()");
Enumeration enu = getChilds();

while(enu.hasMoreElements()) {
((Component)enu.nextElement()).sampleOperation();
}
}

public Composite getComposite() {
return this;
}

@SuppressWarnings("unchecked")
public void addChild(Component component) {
componentVector.addElement(component);
}

public void removeChild(Component component) {
componentVector.removeElement(component);
}

@SuppressWarnings("unchecked")
public Enumeration getChilds() {
return componentVector.elements();
}

}
[/java]

要点:

在两种方式的示例代码中,接口Component接口都定义了一个有趣的方法public Composite getComposite()。在这个方法的实现中,Leaf类对象返回null,而Composite类对象返回this。

我想这个方法的作用有两个:

  1. 一是可以区分对象的类型,如果返回值是null,说明对象是Leaf型;如果返回值不是null,说明对象是Composite型。
  2. 二是方便类型转换。这仅对Composite类型的意义。客户端如果确定了某个Component对象为Composite类型,接下来如果要调用Composite的某个方法(特别是它专有的方法)时,就需要先对这个Component作类型转换,转成Composite型,这并不能说是个好办法。而如果调用它的getComposite()方法,就可以直接拿到一个Composite对象,避免了直接类型转换。