【HeadFirst设计模式学习笔记】10 组合模式
作者:gnuhpc
出处:http://www.cnblogs.com/gnuhpc/
1.我们接着上次的话题,现在有一个新需求就是子菜单功能。我们在此使用一个新的模式——组合模式,意思是允许你将对象组合成树形结构来表现整体-局部的层级,它能使客户以一致的方式处理个别对象以及对象的组合。你告诉顶层的动作,它会完成所有相关的操作。这也就是这个模式使用的场景。在这个例子中,分为菜单和菜单项,一个菜单中可能还有菜单,而且一个菜单中可能还有多个菜单项,但是菜单项不会再有下一层的包含关系。
2.一开始,我们需要创建一个组件接口,来作为菜单和菜单项的共同接口,让我们能够用统一的做法来处理menu和menuItem。所以我们先创建这样一个类:
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();
}
}
请注意,这里面有些对菜单项有意义,而有些则只对菜单有意义。默认抛出不支持操作的异常方便了后边继承后对某些操作的实现。
我们利用这个抽象类,进一步得到菜单和菜单子项这两个类的设计:
菜单:
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("---------------------");
Iterator iterator = menuComponents.iterator();
while (iterator.hasNext()) {
MenuComponent menuComponent = //这里就体现了为什么菜单和菜单项都要继承自这一个统一的抽象类了。
(MenuComponent)iterator.next();
menuComponent.print();//因为都实现了print方法,所以可以统一操作。
}
}
}
菜单子项:这个是叶子节点,不会再有下一级的叶子节点出现了。
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());
}
}
现在,我们再创建一个女招待,你会发现这时的女招待是如此轻松:
public class Waitress {
MenuComponent allMenus;
public Waitress(MenuComponent allMenus) {
this.allMenus = allMenus;
}
public void printMenu() {
allMenus.print();
}
}
我们写一段测试代码来创建一个带有子菜单的菜单:
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 coffeeMenu = new Menu("COFFEE MENU", "Stuff to go with your afternoon coffee");
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));
cafeMenu.add(coffeeMenu);
coffeeMenu.add(new MenuItem(
"Coffee Cake",
"Crumbly cake topped with cinnamon and walnuts",
true,
1.59));
coffeeMenu.add(new MenuItem(
"Bagel",
"Flavors include sesame, poppyseed, cinnamon raisin, pumpkin",
false,
0.69));
coffeeMenu.add(new MenuItem(
"Biscotti",
"Three almond or hazelnut biscotti cookies",
true,
0.89));
Waitress waitress = new Waitress(allMenus);
waitress.printMenu();
}
}
3.你要是还记得上节我们的单一原则,你就会发现组合模式中一个类有两个职责——管理层次结构,执行菜单操作(可以看我们实现的menucomponent中标有黑体带下划线和普通字体的方法,它们承担着两种完全不同的职责)。这就引出了一个compromise——单一职责和透明性。所谓透明性,也就是说通过让组件的接口同时包含一些管理子节点和叶节点的操作,调用者就可以将组合和叶子节点一视同仁,这就做到了组合和叶子节点对组件的透明。我们也可以将这样的职责分属于组合和叶子节点,不过在操作时我们就需要使用判断语句结合instanceof操作符处理不同类型的节点。
4.最后我们试图在组合中加入迭代器,一方面作为上次迭代器模式的复习演练,另一封面迭代器在组合模式中有很重要的应用。
我们在Menucomponent类中加入createIterator方法,而Menu和MenuItem里也加入相应的方法:
MenuComponent:
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 abstract Iterator createIterator();
public void print() {
throw new UnsupportedOperationException();
}
}
Menu:
public class Menu extends MenuComponent {
ArrayList<MenuComponent> menuComponents = new ArrayList<MenuComponent>();//维护菜单和子菜单
String name;
String description;
public Menu(String name, String description){
this.name = name;
this.description = description;
}
@Override
public void add(MenuComponent menuComponent) {
menuComponents.add(menuComponent);
}
@Override
public void remove(MenuComponent menuComponent) {
menuComponents.remove(menuComponent);
}
@Override
public MenuComponent getChild(int i) {
return menuComponents.get(i);
}
@Override
public String getDescription() {
return description;
}
@Override
public String getName() {
return name;
}
@Override
public Iterator<MenuComponent> createIterator() {
return new CompositeIterator(menuComponents.iterator());
//若使用后边简化版迭代器则直接返回持有引用:return new CompositeIterator(menuComponents);
}
@Override
public void print() {
System.out.print("/n" + getName());
System.out.println(", " + getDescription());
System.out.println("---------------------");
Iterator<MenuComponent> iter = menuComponents.iterator();
while(iter.hasNext()){
MenuComponent menuComponent = iter.next();
menuComponent.print();
}
}
}
我们实现组合模式的一个外部迭代器:这是一个强大的迭代器,它遍历了组件内所有的菜单项,确保所有的子菜单都包括进来。
public class CompositeIterator implements Iterator {
Stack stack = new Stack();
public CompositeIterator(Iterator iterator) {
stack.push(iterator);//我们把要遍历的的顶层组合的迭代器放入一个堆栈。外部迭代器必须维护其在遍历中的位置。
}
public Object next() {
if (hasNext()) {//当调用者要取得下一个元素的时候,我们要确定是不是还有下一个
Iterator iterator = (Iterator) stack.peek();//要是有的话就从栈顶取出这个迭代器
MenuComponent component = (MenuComponent) iterator.next();//取出一个元素
if (component instanceof Menu) {//这个元素若是Menu则需要进一步使用迭代器,所以就放入堆栈中。
stack.push(component.createIterator());
}
return component;
} else {
return null;
}
}
public boolean hasNext() {
if (stack.empty()) {//首先判断堆栈是否清空
return false;
} else {//若没有则反复弹出,这里递归的调用了hasNext
Iterator iterator = (Iterator) stack.peek();
if (!iterator.hasNext()) {
stack.pop();
return hasNext(); //递归调用
} else {
return true;
}
}
}
public void remove() {//暂不支持
throw new UnsupportedOperationException();
}
}
这个实现有些复杂,这是由于menu中还可能有menu,如果我们限定menu中不能有menu这样的简化场景,只能有menuItem那么从构造函数中传递ArrayList<MenuComponent>可以简化实现:
public class CompositeIterator implements Iterator{
private ArrayList<MenuComponent> menuComponent;
private static int position = 0;
public MenuIterator(ArrayList<MenuComponent> menuComponent)
{
this.menuComponent = menuComponent;
}
public MenuComponent next()
{
return menuComponent.get(position);
}
public boolean hasNext()
{
if(position<menuComponent.length()&&menuComponent.get(position)!=null)
{
return true;
}
else
{
return false;
}
}
public void remove()
{//暂不支持
throw new UnsupportedOperationException();
}
}
MenuItem:注意,这里返回了一个叫做NullIterator的对象,因为这是叶子节点。
public class MenuItem extends MenuComponent {
String name;
String description;
boolean vegetarian;
double price;
public MenuItem(String name, String description, boolean vegetarian,
double price) {
super();
this.name = name;
this.description = description;
this.vegetarian = vegetarian;
this.price = price;
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public boolean isVegetarian() {
return vegetarian;
}
@Override
public double getPrice() {
return price;
}
@Override
public Iterator<MenuComponent> createIterator() {
return new NullIterator();
}
public void print() {
System.out.print(" " + getName());
if (isVegetarian()) {
System.out.print("(v)");
}
System.out.print(", " + getPrice());
System.out.println(" --" + getDescription());
}
}
这个空迭代器主要是为了叶子节点停止迭代,我们创建了一个没有什么可以被迭代的迭代器,这样是为了统一操作,实现如下:
public class NullIterator implements Iterator {
public Object next() {
return null;
}
public boolean hasNext() {
return false;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
处理的方式也可以是:传入MenuItem对象,在next中再返回这个对象。
我们利用这个外部的迭代器重新写一个打印所有素食菜单的女招待:
public class Waitress {
MenuComponent allMenus;
public Waitress(MenuComponent allMenus) {
this.allMenus = allMenus;
}
public void printMenu() {
allMenus.print();
}
public void printVegetarianMenu() {
Iterator iterator = allMenus.createIterator(); //获得这个外部迭代器
System.out.println("/nVEGETARIAN MENU/n----");
while (iterator.hasNext()) {
MenuComponent menuComponent =
(MenuComponent)iterator.next();//获得元素
try {
if (menuComponent.isVegetarian()) {//
menuComponent.print();
}
} catch (UnsupportedOperationException e) {}
}
}
}