《Head First Design Patterns》:第9章,组合(Composite)模式

组合模式的定义

组合模式允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合,而忽略对象组合和个别对象之间的差异。

场景

紧接迭代器模式,又有咖啡厅被合并进来。

场景描述

咖啡厅被合并进来用来供应晚餐,下面是咖啡菜单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CafeMenu implements Menu {
Hashtable menuItems = new Hashtable();
public CafeMenu() {
addItem(“Veggie Burger and Air Fries”,
“Veggie burger on a whole wheat bun, lettuce, tomato, and fries”,
true, 3.99);
addItem(“Soup of the day”,
“A cup of the soup of the day, with a side salad”,
false, 3.69);
addItem(“Burrito”,
“A large burrito, with whole pinto beans, salsa, guacamole”,
true, 4.29);
}
public void addItem(String name, String description,
boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
menuItems.put(menuItem.getName(), menuItem);
}
public Hashtable getItems() {
return menuItems;
}
}

依靠已经介绍过的迭代器模式,我们可以很容易的将这个菜单整合进我们的框架,但是我们面临的问题是:每加入一种新菜单就需要改动Waitress类,并且printMenu(Iterator iterator) 会被每种菜单都调用一次,如下:

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
public class Waitress {
Menu pancakeHouseMenu;
Menu dinerMenu;
Menu cafeMenu;

public Waitress(Menu pancakeHouseMenu, Menu dinerMenu, Menu cafeMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
this.cafeMenu = cafeMenu;
}

public void printMenu() {
Iterator pancakeIterator = pancakeHouseMenu.createIterator();
Iterator dinerIterator = dinerMenu.createIterator();
Iterator cafeIterator = cafeMenu.createIterator();
System.out.println(“MENU\n----\nBREAKFAST”);
printMenu(pancakeIterator);
System.out.println(“\nLUNCH”);
printMenu(dinerIterator);
System.out.println(“\nDINNER”);
printMenu(cafeIterator);
}

private void printMenu(Iterator iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = (MenuItem)iterator.next();
System.out.print(menuItem.getName() + “, “);
System.out.print(menuItem.getPrice() + “ -- “);
System.out.println(menuItem.getDescription());
}
}
}

并且,餐厅还希望能够加上一份餐后甜点的“子菜单”,这样就像餐厅菜单都会有一个的甜点子菜单。

方案

事情的发展已经到达了一个复杂级别,不可避免地要对菜单进行重新设计了。我们需要:

  1. 某种树形结构,可以容纳菜单、子菜单和菜单项;

  2. 需要确保我们有一种方法可以遍历到菜单中每个菜单项,至少要像现在迭代器一样方便;

  3. 希望能有一个更为灵活的遍历菜单项的方式:比如,我们可能需要仅迭代晚餐的甜点菜单,或者我们可能需要迭代晚餐的整个菜单(包含甜点菜单);

这意味着:如果我们有了一个树形结构的菜单、子菜单和可能还带有菜单项子菜单,那么任何一个菜单都是一种“组合”。

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
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();
}
}
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
public class MenuItem extends MenuComponent {
String name;
String description;
boolean vegetarian;
double price;

public MenuItem(String name,
String description,
boolean vegetarian,
double price) {
this.name = name;
this.description = description;
this.vegetarian = vegetarian;
this.price = price;
}

public String getName() {
return name;
}
public String getDescription() {
return description;
}
public double getPrice() {
return price;
}
public boolean isVegetarian() {
return vegetarian;
}

public void print() {
System.out.print(" " + getName());
if (isVegetarian()) {
System.out.print("(v)");
}
System.out.println(", " + getPrice());
System.out.println(" -- " + getDescription());
}
}
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
public class Menu extends MenuComponent {
ArrayList menuComponents = new ArrayList();
String name;
String description;

public Menu(String name, String description) {
this.name = name;
this.description = description;
}

public void add(MenuComponent menuComponent) {
menuComponents.add(menuComponent);
}
public void remove(MenuComponent menuComponent) {
menuComponents.remove(menuComponent);
}
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("---------------------");
}
}
1
2
3
4
5
6
7
8
9
public class Waitress {
MenuComponent allMenus;
public Waitress(MenuComponent allMenus) {
this.allMenus = allMenus;
}
public void printMenu() {
allMenus.print();
}
}

组合迭代器

TODO