接上篇
在我们新的设计中,真正需要做些什么呢?
1.我们需要某种树形结构,可以容纳菜单、子菜单、菜单项
2.我们需要确定那个在每个菜单的各个项之间游走,而且至少要像在用迭代器一样方便
3.我们要能够更有弹性的在菜单项之间游走,比如说可能只需要遍历甜点菜单,或者可以遍历餐厅的整个菜单
管理菜单的问题已经到了一个迭代器无法解决问题的新维度,我们改用“组合模式”来实现这部分
组合模式允许你讲对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合
使用组合结构,我们能把相同的操作应用在组合和个别对象上。换句话说,在大多数情况下,我们可以忽略对象组合和个别对象之间的差别。
我们要如何在菜单上应用组合模式呢?一开始,我们需要创建一个组件接口来作为菜单和菜单项的共同接口,让我们能够用统一的做法来处理菜单和菜单项。换句话说,我们可以针对菜单或菜单项调用相同的方法。

//实现菜单组件的抽象类,请记住,菜单组件的角色是为夜节点和组合节点提供一个共同接口
public abstract class MenuComponent {
	//我们把“组合”方法组织在一起,包括新增、删除、取得菜单组件
	public void add(MenuComponent menuComponent) {
		//默认实现是抛出UnsupportedOperationException异常,
		//这样如果菜单或菜单项不支持某个操作,他们就不需要做任何事情
		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();
	}
	//print方法同时被菜单和菜单项所实现
	public void print() {
		throw new UnsupportedOperationException();
	}
}
//实现菜单项,首先扩展MenuComponent接口
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());
	}
}
//实现组合菜单,和菜单项一样,继承MenuComponent
public class Menu extends MenuComponent {
	//菜单可以有任意数目的子菜单,这些子菜单都必须属于MenuComponent类型
	ArrayList menuComponents = new ArrayList();
	String name;
	String description;
	//和以前也不一样,我们给每个菜单一个名字和描述,之前每个菜单的类名称就是此菜单的名字
	public Menu(String name, String description) {
		this.name = name;
		this.description = description;
	}
	//这里将菜单项和其他菜单加入到菜单中,因为菜单和菜单项都是menuComponent
	//所以我们只需要一个方法就可以两者兼顾,remove和getChild同理
	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();
		}
	}
}
//java版的服务员代码变的很简单,我们只要将最顶层的菜单组件交给它就可以了
import java.util.Iterator;
public class Waitress {
	MenuComponent allMenus;//最顶层的菜单
	public Waitress(MenuComponent allMenus) {
		this.allMenus = allMenus;
	}
	//她只需要调用最顶层菜单的print,就可以打印整个菜单层次,包括所有的菜单和菜单项
	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 ");
		//我们需要一个最顶层的菜单allMenus
		MenuComponent allMenus = new Menu("ALL MENUS", "All menus combined");
		//使用组合的add方法,将菜单加入到顶层菜单中
		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));
		dinerMenu.add(new MenuItem(
			"Vegetarian BLT",
			"(Fakin') Bacon with lettuce & tomato on whole wheat", 
			true, 
			2.99));
		dinerMenu.add(new MenuItem(
			"Hotdog",
			"A hot dog, with saurkraut, relish, onions, topped with cheese",
			false, 
			3.05));
		dinerMenu.add(dessertMenu);  //菜单中加入另一个菜单
		dessertMenu.add(new MenuItem(
			"Apple Pie",
			"Apple pie with a flakey crust, topped with vanilla icecream",
			true,
			1.59));
		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(coffeeMenu);
		coffeeMenu.add(new MenuItem(
			"Coffee Cake",
			"Crumbly cake topped with cinnamon and walnuts",
			true,
			1.59));
		//一旦我们将整个菜单层次构造完毕把它交给服务员,
		//你会发现服务员要将整份菜单打印出来很容易
		Waitress waitress = new Waitress(allMenus);
		waitress.printMenu();
	}
}

现在,服务员想要游走整个菜单,挑出所有的素食项,我们可能想到实现一个组合迭代器,让我们为每个组件都加上createIterator()方法,从抽象的MenuComponent类下手:

public abstract class MenuComponent {
	//每个菜单和菜单项都需要实现这个方法
	public void createIterator() {
		throw new UnsupportedOperationException();
	}
	//other
}
//菜单修改
public class Menu extends MenuComponent {
	//这里使用一个新的CompositeIterator迭代器,这个迭代器知道如何遍历任何组合
	public Iterator createIterator() {
		return new CompositeIterator(menuComponents.iterator());
	}
	//其他部分不需要修改
}
//菜单项修改
public class MenuItem extends MenuComponent {
	public Iterator createIterator() {
		//噢噢,这里怎么是个空迭代器?
		return new NullIterator();
	}
	//其他部分不需要修改
}
/**
*空迭代器,这么说好了,菜单项内没什么可以遍历的,那么如何实现菜单项的createIterator呢?
*有2个选择:1.返回null,这么做就需要条件语句来判断返回值是否为null
*	2.返回一个迭代器,而这个迭代器的hasNext永远返回false,这个方式应该是比较好的
*/
import java.util.Iterator;
public class NullIterator implements Iterator {
	//基本什么事情都不做的迭代器
	public Object next() {
		return null;
	}
	public boolean hasNext() {
		return false;
	}
	public void remove() {//空迭代器当然不支持remove
		throw new UnsupportedOperationException();
	}
}
//最复杂的组合迭代器
public class CompositeIterator implements Iterator {
	Stack stack = new Stack();
	//将我们要遍历的顶层组合的迭代器传入,我们把它抛进一个堆栈数据结构中
	public CompositeIterator(Iterator iterator) {
		stack.push(iterator);
	}
	//好了,当客户想要取得下一个元素时,我们先调用hasNext来确定是否还有下一个
	public Object next() {
		if (hasNext()) {
			Iterator iterator = (Iterator) stack.peek();
			//如果还有下一个元素,我们就从堆栈中取出目前的迭代器,然后取得它的下一个元素
			MenuComponent component = (MenuComponent) iterator.next();
			if (component instanceof 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;//否则表示还有下个元素,返回true
			}
		}
	}
	//不支持romove,只要遍历
	public void remove() {
		throw new UnsupportedOperationException();
	}
}
//现在有了这个方法遍历菜单的每个项,我们给服务员加上一个可以确切告诉耳麦哪些项目是素食的方法
import java.util.Iterator;
public class Waitress {
	MenuComponent allMenus;
	public Waitress(MenuComponent allMenus) {
		this.allMenus = allMenus;
	}
	public void printMenu() {
		allMenus.print();
	}
	//printVegetarianMenu取得allMenus的组合并得到他的迭代器来作为我们的CompositeIterator
	public void printVegetarianMenu() {
		Iterator iterator = allMenus.createIterator();

		System.out.println("\nVEGETARIAN MENU\n----");
		while (iterator.hasNext()) {
			MenuComponent menuComponent = (MenuComponent)iterator.next();
			//我们在菜单上实现isVegetarian方法,让它永远抛出异常
			//如果异常发生,我们就扑捉这个异常,然后继续遍历
			try {
				//调用每个元素的isVegetarian,如果true就调用它的print方法
				if (menuComponent.isVegetarian()) {
					menuComponent.print();
				}
			} catch (UnsupportedOperationException e) {
				//只有菜单项的print方法可以被调用,绝对不能调用菜单的print方法
			}
		}
	}
}

 

评论
发表评论

您还没有登录,请登录后发表评论

blank
搜索本博客
我的相册
5c2c51d9-1a15-3713-8084-e1742c808801-thumb
P1080256
共 30 张
存档
最新评论