目录
一.专栏简介
本专栏是我学习《head first》设计模式的笔记。这本书中是用Java语言为基础的,我将用C++语言重写一遍,并且详细讲述其中的设计模式,涉及是什么,为什么,怎么做,自己的心得等等。希望阅读者在读完我的这个专题后,也能在开发中灵活且正确的使用,或者在面对面试官时,能够自信地说自己熟悉常用设计模式。
本章将开始组合模式的学习,是上一篇迭代器模式的后传。
二.背景
书接上回,对象村打算在Diner Menu菜单中添加一个子菜单,我们同样需要支持客户方便地遍历所有菜单。我们就需要用到组合模式。

三.定义组合模式
组合模式允许我们将对象组合成树形结构来表现部分-整体层次结构。组合让客户可以统一处理个别对象和对象组合。
这个模式让我们创建树形结构,嵌套的菜单和菜单项组可以用同一结构来处理。通过把菜单和菜单项放进同一结构,我们创建了一个部分-整体层次,即一棵对象树,它由部件(菜单和菜单项)组成,却可以当成一个整体对待。

类图如下:

四.用组合设计和实现菜单
因此,我们如何把组合模式应用到我们的菜单?一开始,我们需要创建一个组件接口,该接口作为菜单和菜单项的通用接口,允许我们统一地对待它们。换句话说,我们可以在菜单或菜单项中调用同样的方法。
现在,调用菜单或菜单项上的某些方法可能没有意义,但我们可以处理,只需等一会。目前,我们快速浏览一下菜单如何符合组合模式结构:

下面,我们用代码实现,代码如下:
Composite.h:
cpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class MenuComponent
{
public:
virtual void add(MenuComponent* menuComponent) { cerr << "Menu can not add" << endl; }
virtual void remove(MenuComponent* menuComponent) { cerr << "Menu can not remove" << endl; }
virtual MenuComponent* getChild(int i) { cerr << "Menu can not getChild" << endl; return nullptr; }
virtual const string& getName() const { cerr << "MenuItem can not getName" << endl; return nullStr; }
virtual const string& getDescription() const { cerr << "MenuItem can not getDescription" << endl; return nullStr; }
virtual const double getPrice() const { cerr << "MenuItem can not getPrice" << endl; return -1; }
virtual bool isVegeTarian() const { cerr << "MenuItem can not isVegeTarian" << endl; return false; }
virtual void print() const = 0 {}
bool operator==(const MenuComponent* another) { return this == another; }
private:
string nullStr;
};
class MenuItem : public MenuComponent
{
public:
MenuItem(const string& name, const string& description, bool vegetarian, double price):
_name(name), _description(description), _vegetarian(vegetarian), _price(price)
{}
const string& getName() const override { return _name; }
const string& getDescription() const override { return _description; }
bool isVegeTarian() const override { return _vegetarian; }
const double getPrice() const override { return _price; }
void print() const override
{
cout << "菜单项:" << _name;
if (_vegetarian) cout << "(v)";
cout << ", " << _price << " --" << _description << endl;
}
private:
string _name;
string _description;
bool _vegetarian;
double _price;
};
class Menu : public MenuComponent
{
public:
Menu(const string& name, const string& description):
_name(name), _description(description)
{}
void add(MenuComponent* menuComponent) override { menuComponents.push_back(menuComponent); }
void remove(MenuComponent* menuComponent) override
{
auto it = menuComponents.begin();
while (it != menuComponents.end())
{
if (*it == menuComponent)
{
menuComponents.erase(it);
return;
}
it++;
}
}
MenuComponent* getChild(int i) override { return menuComponents[i]; }
const string& getName() const override { return _name; }
const string& getDescription() const override { return _description; }
void print() const override
{
cout << endl << _name << ", " << _description << endl << "-------------------" << endl;
for (const auto& e : menuComponents)
{
e->print();
}
}
private:
vector<MenuComponent*> menuComponents;
string _name;
string _description;
};
class Waitress
{
public:
Waitress(MenuComponent* menuComponent):
allMenus(menuComponent)
{
}
void printMenu()
{
allMenus->print();
}
private:
MenuComponent* allMenus;
};
main.cpp:
cpp
#include "Composite.h"
int main()
{
MenuComponent* pancakeHouseMenu = new Menu("PANCAKE HOUSE MENU", "BREAKFAST");
MenuComponent* dinerMenu = new Menu("DINER MENU", "Lunch");
MenuComponent* cafeMenu = new Menu("CAFE MENU", "Dinner");
MenuComponent* dessertMenu = new Menu("DESSERT MENU", "Dessert of course!");
MenuComponent* allMenu = new Menu("ALL MENUS", "All menus combined");
allMenu->add(pancakeHouseMenu);
allMenu->add(dinerMenu);
allMenu->add(cafeMenu);
dinerMenu->add(new MenuItem("Pasta", "香啊啊啊啊啊啊", true, 3.89));
dinerMenu->add(dessertMenu);
dinerMenu->add(new MenuItem("Apple Pie", "香哈哈哈哈哈哈", true, 1.39));
cafeMenu->add(new MenuItem("抹茶拿铁", "咖咖咖", false, 20));
dessertMenu->add(new MenuItem("哈根达斯", "甜甜甜", false, 100));
Waitress waitress(allMenu);
waitress.printMenu();
return 0;
}
运行结果:

五.总结
- 组合模式允许客户统一地处理组合和个别对象。
- 组合结构内的任意对象称为组件。组件可以是其他组合或者叶子。
- 实现组合模式有许多设计上的折中。你要根据需要平衡透明和安全。