关于《HeadFirst设计模式》一书中,迭代器和组合模式中有一部分代码是错误的,错误的类是CompositeIterator,使用这个类,再打印素食菜单的时候,有一些菜单会被重复打印。
如以下菜单
菜单:
早餐:[豆浆,馒头,玉米]
午餐:[烧腊,荷叶饭]
晚餐:[面,粉
加料:[萝卜,咸菜,
加料2:[萝卜2,咸菜2]
]
]
在这个菜单中,如果使用书中的代码打印素食,会是这样的结果
我们会发现,加料菜单打印了2次,加料2菜单打印了2+3次。
下面分析这个结果是怎么产生的,我们使用CI符号代表CompositeIterator遍历,经过研究代码发现,由于CI是递归操作,遇见Menu对象,会产生
CI(Menu)=CI(CI(子Menu))+CI(子Menu),
CI(CI(Menu))=CI(CI(CI(子Menu))+CI(子Menu))+CI(子Menu)
而遇见MenuItem对象,会产生
CI(CI(···CI(MenuItem)··))=普通的迭代器(MenuItem)
于是,打印素食菜单的操作过程如下
CI(早餐)+CI(午餐)+CI(晚餐)
打印 .ALL MENUS.早餐菜单.玉米(V)
=CI(午餐)+CI(晚餐)
=CI(晚餐)
=CI(CI(加料))+CI(加料)
打印 .ALL MENUS.晚餐菜单.加料.萝卜粒(V)
打印 .ALL MENUS.晚餐菜单.加料.咸菜(V)
=CI(CI(加料))+CI(CI(加料2))+CI(加料2)
打印 .ALL MENUS.晚餐菜单.加料.加料2.萝卜粒2(V)
打印 .ALL MENUS.晚餐菜单.加料.加料2.咸菜2(V)
=CI(CI(加料))+CI(CI(加料2))
打印 .ALL MENUS.晚餐菜单.加料.萝卜粒2(V)
打印 .ALL MENUS.晚餐菜单.加料.咸菜2(V)
=CI(CI(加料))
打印 .ALL MENUS.晚餐菜单.萝卜粒(V)
打印 .ALL MENUS.晚餐菜单.咸菜(V)
=CI(CI(CI(加料2))+CI(加料2))+CI(加料2)
打印 .ALL MENUS.晚餐菜单.加料2.萝卜粒2(V)
打印 .ALL MENUS.晚餐菜单.加料2.咸菜2(V)
=CI(CI(CI(加料2))+CI(加料2))
打印 .ALL MENUS.晚餐菜单.萝卜粒2(V)
打印 .ALL MENUS.晚餐菜单.咸菜2(V)
=CI(CI(CI(加料2)))
打印 .ALL MENUS.晚餐菜单.萝卜粒2(V)
打印 .ALL MENUS.晚餐菜单.咸菜2(V)
也就是,这个问题是由于Menu菜单的递归使用CI迭代器引起的,只要我们不要在递归中使用CI迭代器就可以了
所以,只要修改两处地方即可
这样,在打印素食的时候,就只有最外层使用了CompositeIterator迭代器,内层遍历还是使用普通的迭代器
最后,贴出完整的代码
package demo.iterator;
import demo.menu.Menu;
import demo.menu.MenuComponent;
import java.util.Iterator;
import java.util.Stack;
public class CompositeIterator implements Iterator {
Stack<Iterator> stack = new Stack();
public CompositeIterator(Iterator iterator){
stack.push(iterator);
}
@Override
public boolean hasNext() {
if(stack.isEmpty())
return false;
Iterator iterator = stack.peek();
if(!iterator.hasNext()){
stack.pop();
return hasNext();
}else
return true;
}
@Override
public Object next() {
if(hasNext()){
Iterator iterator = stack.peek();
MenuComponent menuComponent = (MenuComponent) iterator.next();
if(menuComponent instanceof Menu){
stack.push(menuComponent.createIterator());
}
return menuComponent;
}else
return null;
}
}
package demo.iterator;
import java.util.Iterator;
public class NullIterator implements Iterator {
@Override
public boolean hasNext() {
return false;
}
@Override
public Object next() {
return null;
}
}
package demo.menu;
import java.util.Iterator;
public abstract class MenuComponent {
public void add(MenuComponent menuComponent){
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(String prefx){
throw new UnsupportedOperationException();
}
public abstract Iterator createIterator();
}
package demo.menu;
import demo.iterator.NullIterator;
import lombok.Getter;
import java.util.Iterator;
public class MenuItem extends MenuComponent {
final String name;
final String description;
final boolean vegetarian;
final double price;
public MenuItem(String name, String description, boolean vegetarian, double price) {
this.name = name;
this.description = description;
this.vegetarian = vegetarian;
this.price = price;
}
@Override
public void print(String prefx) {
System.out.print(prefx+getName());
if(isVegetarian()){
System.out.print("(V)");
}
System.out.print(","+getPrice());
System.out.println(" -- "+getDescription());
}
@Override
public Iterator createIterator() {
return new NullIterator();
}
public String getName(){
return name;
}
public String getDescription(){
return description;
}
public boolean isVegetarian(){
return vegetarian;
}
public double getPrice(){
return price;
}
}
package demo.menu;
import java.util.ArrayList;
import java.util.Iterator;
public class Menu extends MenuComponent {
ArrayList<MenuComponent> menuComponents = new ArrayList<>();
final String name;
final String description;
public Menu(String name, String description) {
this.name = name;
this.description = description;
}
@Override
public Iterator createIterator() {
// return new CompositeIterator(menuComponents.iterator());
return menuComponents.iterator();
}
@Override
public void add(MenuComponent menuComponent){
menuComponents.add(menuComponent);
}
@Override
public void print(String prefx) {
System.out.println(prefx+getName()+","+getDescription());
System.out.println(prefx+"-------------------------");
Iterator<MenuComponent> iterator = menuComponents.iterator();
while(iterator.hasNext()){
MenuComponent menuComponent = iterator.next();
menuComponent.print(prefx+"\t");
}
}
@Override
public boolean isVegetarian() {
return false;
}
public String getName(){
return this.name;
}
public String getDescription(){
return this.description;
}
}
package demo;
import demo.iterator.CompositeIterator;
import demo.menu.MenuComponent;
import java.util.Iterator;
public class Waitress {
final MenuComponent menuComponent;
public Waitress(MenuComponent menuComponent) {
this.menuComponent = menuComponent;
}
public void printMenu(){
menuComponent.print("");
}
public void printVegetarianMenu(){
// Iterator iterator = menuComponent.createIterator();
Iterator iterator = new CompositeIterator(menuComponent.createIterator());
System.out.println("\nVEGETARIAN MENU\n----");
while(iterator.hasNext()){
MenuComponent menuComponent = (MenuComponent) iterator.next();
if(menuComponent.isVegetarian()){
menuComponent.print("");
}
}
}
}
package demo;
import demo.menu.Menu;
import demo.menu.MenuComponent;
import demo.menu.MenuItem;
public class Main {
public static void main(String[] args) {
MenuComponent breakfastMenu = new Menu("早餐菜单","早餐");
MenuComponent lunchMenu = new Menu("午餐菜单","午餐");
MenuComponent dinnerMenu = new Menu("晚餐菜单", "晚餐");
MenuComponent addMenu = new Menu("加料","晚餐加料");
MenuComponent allMenus = new Menu("ALL MENUS","All menus combined");
allMenus.add(breakfastMenu);
allMenus.add(lunchMenu);
allMenus.add(dinnerMenu);
breakfastMenu.add(new MenuItem("豆浆","像水一样",false,0));
breakfastMenu.add(new MenuItem("馒头","像石头一样",false,0));
breakfastMenu.add(new MenuItem("玉米","还行",true,0));
lunchMenu.add(new MenuItem("烧腊","手撕鸡",false, 15));
lunchMenu.add(new MenuItem("荷叶饭","荷叶饭",false, 15));
dinnerMenu.add(new MenuItem("面", "食堂的面",false,10));
dinnerMenu.add(new MenuItem("粉", "食堂的粉",false,10));
addMenu.add(new MenuItem("萝卜粒","去晚了就没有了",true,0));
addMenu.add(new MenuItem("咸菜","不咸",true,0));
dinnerMenu.add(addMenu);
Waitress waitress = new Waitress(allMenus);
//打印完整菜单
// waitress.printMenu();
//只打印素食菜单
waitress.printVegetarianMenu();
}
}
总结,使用递归代码虽然少,但是会把代码运行过程搞得相当复杂。