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


    组合模式

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

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

    image-20200620230101658

    这时候就需要组合模式

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

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

    image-20200620230124627

    例子

    image-20200620230141642

    实现

    以下是简化版实现

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

    测试

    image-20200620232331158

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

    image-20200620230302426

    组合迭代器1.0

    实现这样一个树形结构

    image-20200620232334417

    还记得迭代器模式吗?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());
            }
        }
    }
    

    image-20200620231556377

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

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

    组合迭代器2.0

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

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

    image-20200621004529408

    所以问题所在就是应该是同样的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;
        }
    }
    

    修改后

    image-20200621005123560

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

    测试

    image-20200621004918859

    完美解决。

    组合迭代器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元的食物等..)

  • 相关阅读:
    顺序前缀改为随机性前缀 反转时间戳 反转年月日
    后台组件的治理思路
    干货 | 高耦合场景下,Trip.com如何做支付设计与落地
    每天响应数亿次请求,腾讯云如何提供高可用API服务?
    系统管理及操作命令
    远程连接及系统管理
    linux系统部署安装过程
    day 1 硬件组成概念及介绍笔记
    day 4
    day 3
  • 原文地址:https://www.cnblogs.com/cpaulyz/p/13171142.html
Copyright © 2020-2023  润新知