【设计模式】10. 组合模式

引子

考虑这么一个场景:

需要某种树形结构来容纳菜单、子菜单、菜单项;

并能在每个菜单的每个项之间游走;


定义

Composite Pattern (又叫 部分-整体模式,Part-Whole),

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

将对象组合成树形结构来表现“部分-整体”的层次结构,使客户以一致的方式来处理单个对象组合对象


类图

【设计模式】10. 组合模式_第1张图片

1、Component:抽象组件

public abstract class MenuComponent {

  /******以下是“组合”方法********/
  //默认运行时异常,如果子类不重写 则表示不支持这个操作
  public void add(MenuComonent comp) { throw new UnsopportOperationException();}
  public void remove(MenuComponent comp) { }
  public MenuComponent getChild(int i) { }

  /******以下是“操作”方法********/
  public String getName() { }
  public String getPrice() { }
  public void print() { }

}
需要一个抽象组件来作为叶子和树枝的共同接口(或父类),客户端不用知道具体的某个节点到底是叶子还是树枝。


2、Leaf:叶子组件

public class MenuItem extends MenuComponent {   
   /****叶子支持的操作:重写****/
   public String getName() {return name; }
   public void print() {
      ...//打印名称、价格等
   }

   /****叶子不支持的操作:不重写*****/
   // add()
   // remove()
   // public MenuComponent getChild(int i) {}

}

3、Composite:树枝组件

public class Menu extends MenuComponent {
   List<MenuComponent> children = new ArrayList<MenuComponent>();

   public String getName() {return name; }
   public void print() {
      ...//打印名称等
      //接着打印子节点信息
      Iterator iter = children.iterator();
      while (iter.hasNext()) {
         MenuComponent comp = iter.next();
         comp.print(); //递归
      }
   }

   public add(MenuComponent comp){ children.add(comp); }
   public remove(MenuComponent comp) { children.remove(comp); }
   public MenuComponent getChild(int i) { return children.get(i); }

}

优点

  1. 叶子节点(单个对象)、树枝节点(组合对象)对用户透明;即可以将相同的操作应用在组合对象单个对象上。
  2. 高层模块调用简单;局部和整体对调用者来说没有任何区别。
  3. 节点增加自由


缺点

  1. 组合模式以牺牲单一职责原则来换取透明性:同一个节点既负责“组合方法”、又负责“操作方法”


使用场景

  1. 维护和展示 部分-整体 关系的场景,如树形菜单、组织结构、文件系统
  2. 从一个整体中能够独立出部分模块或功能的场景


“安全版本”的组合模式

上面讲的是“透明版本”的组合模式。


安全版本:

在抽象组件中,只包含操作方法,不包含组合方法;或者让叶子节点、树枝节点分别实现不同的接口。可以防止用户在叶子节点上调用getChild()这种不被支持的操作。

缺点:用户必须首先检查节点类型,然后才能进行方法调用。


组合模式的遍历

上例中的print()方法,通过递归已经实现了对树的遍历。但是比较单一。


Q:要求对已知的某个节点,找到他的所有上级、下级

A:可以在Component中增加一个指向parent的引用


更具有扩展性的解决办法是,将组合模式、迭代器模式结合起来:

1、在抽象组件中增加createIterator()方法

2、叶子组件中,实现createIterator(),返回一个NullIterator,其hasNext()总是false

3、树枝组件中,实现createIterator(),返回一个CompositeIterator

//叶子组件返回NullIterator
public class NullIterator implements Iterator {
  public Object next() { return null;}
  public boolean hasNext() { return false; }
  public void remove() {throw new UnsupportedOperationException(); }
}

//树枝组件返回CompositeIterator
public class CompositeIterator implements Iterator {

  Stack stack = new Stack(); //何用?存放组件的迭代器

  public CompositeIterator(Iterator iter) {
    stack.push(iter); 
  }

  public boolean hasNext(){
    //堆栈中无迭代器,则表示没有下一个元素
    if (stack.empty) {
       return false;
    } else {
       //从stack中取出迭代器
       Iterator iter = stack.peek();
       //如果该迭代器中中没有下一个元素,则将其弹出堆栈,判断下一个迭代器
       if (! iter.hasNext()) {
          stack.pop();
          hasNext();
       }
       //如果该迭代器中有下一个元素,返回true
       else {
          return true;
       }      
    }
  }

  public Object next(){
    if (hasNext()){
       //如果有下一个元素,则直接取出该元素
       Iterator iter = stack.peek();
       MenuComponent comp = iter.next();
       //如果该元素是一个菜单,则我们就有了另一个需要被包含进遍历中的组合,丢进堆栈中
       if (comp instanceof Menu) {
          stack.push(comp.createIterator());
       }
       return comp;
    } else {
       return null;
    }

  }

}







你可能感兴趣的:(设计模式)