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

总结,使用递归代码虽然少,但是会把代码运行过程搞得相当复杂。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值