《HeadFirst設計模式》迭代器和組合模式的錯誤原因分析和解決辦法

關於《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();
    }
}

總結,使用遞歸代碼雖然少,但是會把代碼運行過程搞得相當複雜。

發佈了22 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章