《Head First 设计模式》中组合迭代器重复打印的bug的两种解决方案

Posted cpaulyz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Head First 设计模式》中组合迭代器重复打印的bug的两种解决方案相关的知识,希望对你有一定的参考价值。

组合模式

面对这样一种问题,可能List里面套List,是一种如下的树形数据结构

简单来说,叶子节点才是实际对象,其他都是一个集合

技术图片

这时候就需要组合模式

组合模式将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

  • 想表示对象的部分-整体层次结构(树形结构)。
  • 用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
  • 树枝和叶子实现统一接口,树枝内部组合该接口。

技术图片

例子

技术图片

实现

以下是简化版实现

abstract class MenuComponent {
    public void add(MenuComponent menuComponent){
        throw new UnsupportedOperationException();
    }
    public MenuComponent getChild(int index){
        throw new UnsupportedOperationException();
    }
    public String getName(){
        throw new UnsupportedOperationException();
    }
    public void print(){
        throw new UnsupportedOperationException();
    }
}
public class MenuItem extends MenuComponent {
    String name;
    public MenuItem(String name){
        this.name = name;
    }
    @Override
    public String getName() {
        return name;
    }
    @Override
    public void print() {
        System.out.println(getName());
    }
}
public class Menu extends MenuComponent {
    ArrayList<MenuComponent> menuComponents = new ArrayList<>();
    String name;

    public Menu(String name){
        this.name = name;
    }

    @Override
    public void add(MenuComponent menuComponent) {
        menuComponents.add(menuComponent);
    }

    @Override
    public MenuComponent getChild(int index) {
        return (MenuComponent)menuComponents.get(index);
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void print() {
        System.out.println();
        System.out.println(getName());
        System.out.println("---------------------");
        Iterator<MenuComponent> iterator = menuComponents.iterator();
        while (iterator.hasNext()){
            MenuComponent menuComponent = iterator.next();
            menuComponent.print();
        }
    }
}

测试

技术图片

public class Test {
    public static void main(String[] args) {
        MenuComponent pancakeMenu = new Menu("PANCAKE MENU");
        MenuComponent dinerMenu = new Menu("DINER MENU");
        MenuComponent cafeMenu = new Menu("CAFE MENU");
        MenuComponent allMenus = new Menu("ALL MENU");
        allMenus.add(pancakeMenu);
        allMenus.add(dinerMenu);


        pancakeMenu.add(new MenuItem("Taco"));
        pancakeMenu.add(new MenuItem("Noodles"));
        dinerMenu.add(new MenuItem("PIE"));
        dinerMenu.add(cafeMenu);
        cafeMenu.add(new MenuItem("Black Tea"));
        cafeMenu.add(new MenuItem("Green Tea"));

        allMenus.print();
    }
}

技术图片

组合迭代器1.0

实现这样一个树形结构

技术图片

还记得迭代器模式吗?CompositeIterator能够遍历组合中的组件的迭代器

实现

public class CompositeIterator implements Iterator {
    Stack stack = new Stack();
    public CompositeIterator(Iterator iterator){
        stack.push(iterator);
    }
    @Override
    public boolean hasNext() {
        if(stack.isEmpty()){
            return false;
        }else {
            Iterator iterator = (Iterator) stack.peek();
            if(!iterator.hasNext()){
                stack.pop(); // 如果栈顶的迭代器已经结束了,把他出栈,然后重新调用hasNext
                return hasNext();
            }else {
                return true;
            }
        }
    }
    @Override
    public Object next() {
        if(hasNext()){
            Iterator iterator = (Iterator) stack.peek();
            MenuComponent menuComponent = (MenuComponent) iterator.next();
            if(menuComponent instanceof Menu){
                stack.push(menuComponent.createIterator());
            }
            return menuComponent;
        }else {
            return null;
        }
    }
}

在抽象类中添加方法

    public Iterator createIterator(){
        throw new UnsupportedOperationException();
    }

在MenuItem中添加方法及空对象

    @Override
    public Iterator createIterator() {
        return new NullIterator();
    }
    private class NullIterator implements Iterator{

        @Override
        public boolean hasNext() {
            return false;
        }

        @Override
        public Object next() {
            return null;
        }
    }

在Menu中添加方法

    @Override
    public Iterator createIterator() {
        return new CompositeIterator(menuComponents.iterator());
    }

测试

public class Test {
    public static void main(String[] args) {
        MenuComponent pancakeMenu = new Menu("PANCAKE MENU");
        MenuComponent dinerMenu = new Menu("DINER MENU");
        MenuComponent cafeMenu = new Menu("CAFE MENU");
        MenuComponent allMenus = new Menu("ALL MENU");
        allMenus.add(pancakeMenu);
        allMenus.add(dinerMenu);


        pancakeMenu.add(new MenuItem("Taco"));
        pancakeMenu.add(new MenuItem("Noodles"));
        dinerMenu.add(new MenuItem("PIE"));
        dinerMenu.add(cafeMenu);
        cafeMenu.add(new MenuItem("Black Tea"));
        cafeMenu.add(new MenuItem("Green Tea"));

//        allMenus.print();
        Iterator<MenuComponent> iterator = allMenus.createIterator();
        while (iterator.hasNext()){
            MenuComponent component = iterator.next();
            System.out.println(component.getName());
        }
    }
}

技术图片

可以发现,是深度优先遍历

但是存在重复打印的bug,Black Tea和Green Tea打印了两遍。发现《Head First Design Pattern》书上也是一样的问题

组合迭代器2.0

研究一下问题出现在哪里,发现所有非第一层的叶节点都会出现重复打印

打断点,发现在打印完第一遍Black Tea和Green Tea后,CompositeIterator@986要被删掉了,但是一样的CompositeIterator@991还在,而且cursor=0,说明还会重新遍历一遍。

技术图片

所以问题所在就是应该是同样的CompositeIterator对象,由于在Menu类中的实现是return new CompositeIterator(menuComponents.iterator());,所以会创建多个对象。

修改

public class Menu extends MenuComponent {
// 新加这个成员遍历Iterator,来保证每次createIterator获取到的都是同一个对象
    Iterator iterator;
// ...
    @Override
    public Iterator createIterator() {
        if(iterator==null){
            iterator = new CompositeIterator(menuComponents.iterator());
        }
        return iterator;
    }
}

修改后

技术图片

由于是同一个对象,cursor已经=2了,所以哪怕再次用到这个迭代器的应用,也因为cursor=size而不会重复打印。

测试

技术图片

完美解决。

组合迭代器3.0

在这里介绍另外一种修改方法

不做2.0的修改,将CompositeIterator中的代码改为如下

    @Override
    public Object next() {
        if(hasNext()){
            Iterator iterator = (Iterator) stack.peek();
            MenuComponent menuComponent = (MenuComponent) iterator.next();
            if(menuComponent instanceof Menu){
                stack.push(((CompositeIterator)menuComponent.createIterator()).stack.peek());
//                stack.push(menuComponent.createIterator());
            }
            return menuComponent;
        }else {
            return null;
        }
    }

也就是把stack中的Iterator类型都统一为ArrayListItr的应用,问题也可以解决,在此不多赘述。

其他

通过组合迭代器的方法,可以在MenuComponent中添加一些flag,实现遍历下的筛选(比如筛选出所有饮料,所有价格低于100元的食物等..)

以上是关于《Head First 设计模式》中组合迭代器重复打印的bug的两种解决方案的主要内容,如果未能解决你的问题,请参考以下文章

Head First设计模式——组合模式

《Head first设计模式》学习笔记 – 迭代器模式

Head First设计模式之组合模式

Head First 设计模式-- 总结

迭代器和组合模式

(38)23种设计模式研究之九迭代器模式和组合模式