(learn&think)

不浮躁,不自傲,学习,思考,总结

浅谈设计模式十二: 组合模式(Composite)

| Comments

1 实例

上节使用迭代器模式可以很方便访问各个菜单目录下的菜单,易易于菜单目录的扩展。但现在菜单目录下新增加了子菜单,比如 DinerMenu 下有 DessertMenu 的子菜单,那么如何更进一步改进我们的设计,新设计至少需要:

  • 一种树状结构来包含菜单目录,子目录和菜单项。
  • 遍历菜单目录里的每个菜单项如何迭代器一样方便。
  • 遍历菜单更灵活。比如,只遍历 Diner 的 desssert 菜单,或遍历整个 Diner 菜单,包括 dessert 菜单。

2 组合模式(Composite)

2.1 目的

把对象组合成树状结构来表示部分-整体的层次结构。组合模式让用户能一致的对待单个对象和组合对象。

2.2 实现

组合模式能以树状的形式建立对象的结构,并同时包换对象的组合和单个对象作为树的节点。使用一个组合的结构,能对组合和单个对象使用同样的操作。也就是说,大多数情况下,我们能忽略对象组合和单个对象的区别。代码实例在这里 。基本的组合树状结构如下:

按照下面组合模式的结构图具体实现。

2.2.1 定义构件接口

定义 MenuComponent 构件类,为页节点( MenuItem )和组合节点( Menu )声明接口,并为它们实现默认的操作。

class MenuComponent {
 public:
  virtual ~MenuComponent() { }
  virtual void add(MenuComponent *menu_component) {
    throw runtime_error("Unsupported Operation!");
  }
  virtual void remove(MenuComponent *menu_component) {
    throw runtime_error("Unsupported Operation!");
  }
  virtual MenuComponent* getChid(int i) const {
    throw runtime_error("Unsupported Operation!");
  }
  virtual string name() const {
    throw runtime_error("Unsupported Operation!");
  }
  virtual string description() const {
    throw runtime_error("Unsupported Operation!");
  }
  virtual double price() const {
    throw runtime_error("Unsupported Operation!");
  }
  virtual bool isVegetarian() const {
    throw runtime_error("Unsupported Operation!");
  }
  virtual Iterator* createIterator() = 0;
  virtual void print() const {
    throw runtime_error("Unsupported Operation!");
  }
};

2.2.2 实现 MenuItem (页节点)

  • 菜单项直接打印自己的菜单项;
  • 菜单项只有本身,不用遍历,返回空的迭代器实例。
class MenuItem : public MenuComponent {
 public:
  MenuItem(const string &name, const string &description, bool vegetarian,
           double price);
  virtual ~MenuItem();
  string name() const {
    return name_;
  }
  string description() const {
    return description_;
  }
  double price() const {
    return price_;
  }
  bool isVegetarian() const {
    return vegetarian_;
  }
  void print() const;
  Iterator* createIterator();
 private:
  string name_;
  string description_;
  bool vegetarian_;
  double price_;
};

void MenuItem::print() const {
  cout << "  " << name();
  if (isVegetarian()) {
    cout << "(v)";
  }
  cout << ", " << price() << endl;
  cout << "    -- " << description() << endl;
}

Iterator* MenuItem::createIterator() {
  return new NullIterator();
}

2.2.3 实现 Menu (组合节点)

Menu 类和 MenuItem 类都继承于构件接口类,大部分相同,但

  • Menu 有子节点,实现 getChild 功能;
  • Menu 需要遍历所有它的子节点,实现创建组合迭代器;
  • Menu 打印所有它节点下的菜单项,包括自身的和所有子节点的。
class Menu : public MenuComponent {
 public:
  Menu(const string &name, const string &description);
  ~Menu();
  virtual void add(MenuComponent *menu_component);
  virtual void remove(MenuComponent *menu_component);
  virtual MenuComponent* getChild(int i) const;
  virtual string name() const {
    return name_;
  }
  virtual string description() const {
    return description_;
  }
  virtual Iterator* createIterator();
  virtual void print() const;
 private:
  string name_;
  string description_;
  vector<MenuComponent*> *menu_components_;
};

MenuComponent* Menu::getChild(int i) const {
  if (i < 0 || i >= menu_components_->size()) {
    return NULL;
  }
  return menu_components_->at(i);
}

Iterator* Menu::createIterator() {
  return new Composite Iterator(new MenuIterator(menu_components_));
}

void Menu::print() const {
  cout << endl << name();
  cout << ", " << description() << endl;
  cout << "---------------------" << endl;
  for (int i = 0; i < menu_components_->size(); ++i) {
    MenuComponent *item = menu_components_->at(i);
    item->print();
  }
}

2.2.4 实现组合迭代器

组合迭代器 CompositeIterator 继承于迭代器接口 Iterator 。组合迭起器实现起来更复杂,需要考虑树状结构的遍历,这里利用堆栈来保存组合节点,实现深度优先的遍历。

class CompositeIterator : public Iterator {
 public:
  explicit CompositeIterator(Iterator *iterator);
  ~CompositeIterator();
  virtual bool hasNext();
  virtual void* next();
 private:
  stack<Iterator*> *stack_;
};

bool CompositeIterator::hasNext() {
  if (stack_->empty()) {
    return false;
  } else {
    Iterator *iterator = stack_->top();
    if (!iterator->hasNext()) {
      stack_->pop();
      delete iterator;
      return hasNext();
    } else {
      return true;
    }
  }
}

void* CompositeIterator::next() {
  if (hasNext()) {
    Iterator *iterator = stack_->top();
    MenuComponent *component =
        reinterpret_cast<MenuComponent*>(iterator->next());
    if (typeid(*component) == typeid(Menu)) {
      stack_->push(component->createIterator());
    }
    return component;
  } else {
    return NULL;
  }
}

2.2.5 Waitress 访问菜单

  • Waitress 打印菜单直接调用构件的打印功能;
  • 打印蔬菜菜单,利用组合迭代器遍历并判断蔬菜菜单
void Waitress::printMenu() {
  all_menus_->print();
}

void Waitress::printVegetarianMenu() {
  Iterator *iterator = all_menus_->createIterator();
  cout << "\nVEGETARIAN MENU\n----" << endl;
  while (iterator->hasNext()) {
    MenuComponent *menu_component = reinterpret_cast<MenuComponent *>
        (iterator->next());
    try {
      if (menu_component->isVegetarian()) {
        menu_component->print();
      }
    } catch (exception &ex) {
    }
  }
  delete iterator;
}

2.2.6 组合菜单

MenuItem 创建各个菜单项,把各个菜单目录组合只需用构件的 add 函数来完成,如下:

MenuComponent *cafe_menu = new Menu("CAFE MENU", "Dinner");
MenuComponent *coffee_menu =
      new Menu("COFFEE MENU", "Stuff to go with your afternoon coffee");

cafe_menu->add(new MenuItem(
      "Burrito",
      "A large burrito, with whole pinto beans, salsa, guacamole",
      true, 
      4.29));

cafe_menu->add(coffee_menu);

coffee_menu->add(new MenuItem(
    "Coffee Cake",
    "Crumbly cake topped with cinnamon and walnuts",
    true,
    1.59));

2.2.7 总的设计框架

3 总结

3.1 组合模式(Composite)结构

一个典型组合对象结构类似如下图:

3.2 组成

  • 构件类(Component)
    1. 为组合模式中的对象声明接口。
    2. 适当的为所有类共有接口实现默认的行为。
    3. 声明一个接口来访问和管理它的子节点。
    4. (可选的)定义一个接口来访问一个构件在递归结构下的父元件,如果适当的话,并实现这个接口
  • 树叶类(Leaf)
    1. 在组合模式中表示树叶类。一个树叶类没有子节点。
    2. 在组合模式中为基础对象定义行为。
  • 客户(Client) 通过元件接口来操作组合模式中的对象。

3.3 应用场景

  • 你想表示对象的部分-整体层次结构。
  • 你想客户能够忽略对象组合和单个对象的区别。客户能一致的对待组合结构中的所有对象。

3.4 缺点

组合模式其中一个特性是:使得你的设计更具一般性。添加新构件很容易的缺点就是使得很难限制组合模式中的构件。有时你想要一个组合只有某些特性的元件,但是,使用组合模式,你不能依靠类型系统来实施这些限制,你不得不使用运行时检查。

Comments