设计模式之组合模式 (Composite Pattern)

使用组合模式的目的

GoF 的观点

Composite objects into tree structures to represent part-whole hieraches. Composite lets clients treat individual objects and compositions of objects uniformly.

当有数个对象的集合,它们彼此之间有"部分-整体"的关系,并且你想用一致的方式对待这些对象时,就需要组合模式.  

所谓整体/部分关系 是什么?

比如图形界面,一个顶层组件(Frame or Panel)包含着其他组件(像菜单,文字面板,滚动条…)所以你的GUI包含了若干部分,但是当你显示它的时候,你认为它是一个整体.你告诉顶层的组件显示,然后就放手不管,由顶层组件负责显示所有相关的部分.我们称这种包含其他组件的组件为组合对象(composite)而称没有包含其他组件的组件为叶节点对象.

用一致的方式对待 (treat individual objects and compositions of objecets uniformly)

组合和叶节点之间有共同的方法可以调用(比如,我们可以让组合对象或者叶节点对象显示setVisiable(),而组合对象会让他所有的组件显示setVisiable()).

应用

当有如下需求时使用 composite pattern:  

  • 你需要表示部分/整体结构
  • 你需要让clients能够忽略 组合对象(compositions)和 普通对象(leaf).clients 会用一致的方式处理组合对象和普通对象.

类图

类图

参与者

the key to the Composite pattern is an abstract class that represents both primitives and their containers.

组合模式的关键 在于定义一个抽象类,它定义了leaf 和 composite的共有操作

  • Component
    • 声明了组合模式的接口
    • 在适当的时候实现了接口的实现类的通用行为
    • 声明了用于访问和管理子组件(child component)的接口
    • ( 可选) 在递归结构中定义了访问组件的父组件的接口,并在适当的时候实现它
  • Leaf
    • 代表了叶子对象,叶子对象没有子对象,是组合模式中最基本的单位
    • 定义了基本对象的操作
  • Composite
    • 定义了复合对象,复合对象可以包含其他复合对象和基本对象
    • 存储子复合对象
    • 实现了Composition类中和子对象相关的操作
  • Client
    • 通过Component接口操作组合对象

效果

  • 优点:

    • 使用户代码更简单.clients 能用一致的方法处理组合对象和普通对象.clients不知道(也不应该关心)自己处理的是组合对象还是基本对象.这简化了用户代码,因为定义组合功能的那么些类中不需要写一些充斥着选择语句的函数. 不需要写一大堆if语句来保证对正确的对象调用了正确的方法,通常,只要对整个结构调用一个方法并执行操作就可以了.
    • 使得更容易增加新类型的组件 新定义的Composite或leaf子类自动的与已有的结构和客户代码一起工作,客户程序不需要因为新增加的Component类儿改变
  • 缺点:

    • 由于新的Leaf和composite类都要继承Component类,所以每个对象都有相同的接口.而Component中定义的操作Leaf类的接口对于leaf类没用,而且万一组合中有些对象的行为不太一样,这样的设计就失去了安全性.所以要在 透明性安全性之间做一个 trade-off ,为了保持透明性,组合内所有的对象都必须实现相同的接口,否则客户就必须操心哪个对象是用哪个接口,这就失去了组合模式的意义.

怎么解决这个缺点呢?

  • 可以覆盖这样的方法为空方法,或者返回false/null.
  • 可以直接抛出异常( 如果调用了不支持的操作)
  • 可以对不同的对象使用不同的接口,需要客户先检查每个对象的类型,然后才进行方法调用

示例代码

component接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package boqingan;
import java.util.Iterator;
public abstract class MenuComponent{
public void add(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}
public void remove(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}
public MenuComponent getChild(int i){
throw new UnsupportedOperationException();
}
public String getName(){
throw new UnsupportedOperationException();
}
public String getDescription(){
throw new UnsupportedOperationException();
}
public double getPrice(){
throw new UnsupportedOperationException();
}
public boolean isVegetarian(){
throw new UnsupportedOperationException();
}
public void print(){
throw new UnsupportedOperationException();
}
public Iterator createIterator(){
throw new UnsupportedOperationException();
}
}
Leaf类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package boqingan;
import java.util.Iterator;
// 这是组合模式(Composite pattern 中的 leaf)
public class MenuItem extends MenuComponent{
private String name;
private String description;
private boolean vegetarian;
private double price;
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public boolean isVegetarian() {
return vegetarian;
}
public double getPrice() {
return price;
}
public MenuItem(String name, String description, boolean vegetarian,
double price) {
super();
this.name = name;
this.description = description;
this.vegetarian = vegetarian;
this.price = price;
}
public void print(){
System.out.print(" "+getName());
if(isVegetarian()){
System.out.print("(v)");
}
System.out.println(", "+getPrice());
System.out.println(" ----"+getDescription());
}
public Iterator createIterator(){
return new NullIterator();
}
}
composite类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package boqingan;
import java.util.*;
// 实现组合(Composite)菜单
public class Menu extends MenuComponent{
protected ArrayList menuComponents = new ArrayList();
protected String name;
protected String description;
public Menu(String name ,String description){
this.name = name;
this.description = description;
}
public void add(MenuComponent mc){
menuComponents.add(mc);
}
public void remove(MenuComponent mc){
menuComponents.remove(mc);
}
public MenuComponent getChild(int i){
return (MenuComponent)menuComponents.get(i);
}
public String getName(){
return name;
}
public String getDescription(){
return description;
}
public void print(){
System.out.print(" \n" + getName());
System.out.println(" ," + getDescription());
System.out.println("---------");
Iterator iterator = menuComponents.iterator();
while (iterator.hasNext()){
MenuComponent menuComponent = (MenuComponent)iterator.next();
menuComponent.print();
}
}
public Iterator createIterator(){
return new CompositeIterator(menuComponents.iterator());
}
}
clients类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package boqingan;
import java.util.*;
public class Waitress {
MenuComponent allMenus;
public Waitress(MenuComponent allMenus){
this.allMenus = allMenus;
}
public void printMenu(){
allMenus.print();
}
public void printVegetarianMenu(){
Iterator iterator = allMenus.createIterator();
System.out.println("\n VEGETARIAN MENU \n---");
while( iterator.hasNext()){
MenuComponent menuComponent = (MenuComponent)iterator.next();
try {
if(menuComponent.isVegetarian()){
menuComponent.print();
}
}catch (UnsupportedOperationException e){}
}
}
}

###更多代码详见github

Comments

⬆︎TOP