定义
组合模式(composite pattern)允许你将对象组合成树形结构来表现“整体/部分“层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
代码实现
本章使用组合模式来实现下图的树状菜单。
首先为菜单和菜单项创建一个共同的接口作为组件接口,让我们能够用统一的做法来处理菜单和菜单项。换句话说,客户(Waitress)可以针对菜单或菜单项调用相同的方法。
组件接口MenuComponent.java
import java.util.*;
// MenuComponent对每个方法都提供了默认的实现,
// 菜单和菜单项的角色不同,所以有些方法可能并不适合某种节点。
// 面对这种情况,默认实现抛出UnsupportedOperationException,当菜单或菜单项不支持某个操作时,继承默认实现即可
public abstract class MenuComponent {
// Composite应该实现的方法
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();
}
}
Leaf节点MenuItem.java
import java.util.Iterator;
import java.util.ArrayList;
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());
}
}
Composite节点Menu.java
import java.util.Iterator;
import java.util.ArrayList;
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;
}
// 对菜单的每一项进行遍历:
// 使用menuComponents生成迭代器,
// 通过迭代器遍历menuComponents的每一项,
// 直接调用MenuItem.print或者递归调用Menu.print
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();
}
}
}
客户Waitress的代码
import java.util.Iterator;
public class Waitress {
MenuComponent allMenus;
public Waitress(MenuComponent allMenus) {
this.allMenus = allMenus;
}
public void printMenu() {
allMenus.print();
}
}
测试驱动代码
import java.util.*;
public class MenuTestDrive {
public static void main(String args[]) {
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 allMenus = new Menu("ALL MENUS", "All menus combined");
allMenus.add(pancakeHouseMenu);
allMenus.add(dinerMenu);
allMenus.add(cafeMenu);
pancakeHouseMenu.add(new MenuItem(
"K&B's Pancake Breakfast",
"Pancakes with scrambled eggs, and toast",
true,
2.99));
pancakeHouseMenu.add(new MenuItem(
"Regular Pancake Breakfast",
"Pancakes with fried eggs, sausage",
false,
2.99));
pancakeHouseMenu.add(new MenuItem(
"Blueberry Pancakes",
"Pancakes made with fresh blueberries, and blueberry syrup",
true,
3.49));
pancakeHouseMenu.add(new MenuItem(
"Waffles",
"Waffles, with your choice of blueberries or strawberries",
true,
3.59));
dinerMenu.add(new MenuItem(
"Vegetarian BLT",
"(Fakin') Bacon with lettuce & tomato on whole wheat",
true,
2.99));
dinerMenu.add(new MenuItem(
"BLT",
"Bacon with lettuce & tomato on whole wheat",
false,
2.99));
dinerMenu.add(new MenuItem(
"Soup of the day",
"A bowl of the soup of the day, with a side of potato salad",
false,
3.29));
dinerMenu.add(new MenuItem(
"Hotdog",
"A hot dog, with saurkraut, relish, onions, topped with cheese",
false,
3.05));
dinerMenu.add(new MenuItem(
"Steamed Veggies and Brown Rice",
"Steamed vegetables over brown rice",
true,
3.99));
dinerMenu.add(new MenuItem(
"Pasta",
"Spaghetti with Marinara Sauce, and a slice of sourdough bread",
true,
3.89));
dinerMenu.add(dessertMenu);
dessertMenu.add(new MenuItem(
"Apple Pie",
"Apple pie with a flakey crust, topped with vanilla icecream",
true,
1.59));
dessertMenu.add(new MenuItem(
"Cheesecake",
"Creamy New York cheesecake, with a chocolate graham crust",
true,
1.99));
dessertMenu.add(new MenuItem(
"Sorbet",
"A scoop of raspberry and a scoop of lime",
true,
1.89));
cafeMenu.add(new MenuItem(
"Veggie Burger and Air Fries",
"Veggie burger on a whole wheat bun, lettuce, tomato, and fries",
true,
3.99));
cafeMenu.add(new MenuItem(
"Soup of the day",
"A cup of the soup of the day, with a side salad",
false,
3.69));
cafeMenu.add(new MenuItem(
"Burrito",
"A large burrito, with whole pinto beans, salsa, guacamole",
true,
4.29));
Waitress waitress = new Waitress(allMenus);
waitress.printMenu();
}
}
该模式体现了哪些OO原则
-
Waitress和MenuTestDrive都是针对MenuComponent的接口编程,不关心具体实现。
本章总结
组合模式以单一责任设计原则换取透明性(transparency)。透明性是指通过让组件接口Component同时包含一些管理子节点和叶节点操作,客户就可以将组合和叶节点一视同仁。也就是说,一个元素究竟是组合还是叶节点,对客户是透明的。现在MenuComponent类中同时具有两种类型的操作。因为客户有机会对一个元素做一些不恰当或者没有意义的操作(如试图把菜单添加到菜单项),所以我们失去了一些安全性。这是设计上的抉择,我们也可以采用另外一种方向的设计,将责任区分开来放在不同的接口中。这样一来,设计上就比较安全,但是我们失去了透明性,客户的代码将必须用条件语句和instanceof操作符处理不同类型的节点。这是一个典型的折衷案例。尽管我们受到设计原则的指导,但是我们总是需要观察某原则对我们的设计所造成的影响。
组合结构内任意对象称为组件,组件可以是组合,也可以是叶节点