關於《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();
}
}
總結,使用遞歸代碼雖然少,但是會把代碼運行過程搞得相當複雜。