美文网首页设计模式系列教程
设计模式系列教程—Iterator Pattern(迭代器模式)

设计模式系列教程—Iterator Pattern(迭代器模式)

作者: Vander1991 | 来源:发表于2019-05-14 23:17 被阅读0次

    10 Iterator Pattern(迭代器模式)

    前言:帮助客户用同样的方法遍历不同的集合。
    需求:
    由于海岸城的店租极具增长,Vander的Pizza店和烧烤店需要合并在一起了,合并在一起,这两家店的主厨A和主厨B,他们的菜单实现却不相同,Pizza店的菜单是使用List,而烧烤店的菜单用的却是数组,让我们先来看看他们菜单的实现。
    首先两个主厨都是使用了MenuItem来写自己的菜单的。
    MenuItem:

    public class MenuItem {
    
        private String name;
        
        private String desc;
        
        private double price;
    
        public MenuItem(String name, String desc, double price) {
            super();
            this.name = name;
            this.desc = desc;
            this.price = price;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getDesc() {
            return desc;
        }
    
        public void setDesc(String desc) {
            this.desc = desc;
        }
    
        public double getPrice() {
            return price;
        }
    
        public void setPrice(double price) {
            this.price = price;
        }
        
    }
    

    PizzaMenu:

    public class PizzaMenu {
    
        private ArrayList<MenuItem> menuItem;
        
        public PizzaMenu() {
            menuItem = new ArrayList<MenuItem>();
            menuItem.add(new MenuItem("FruitPizza", "Hawaii Style", 38.0));
            menuItem.add(new MenuItem("BuffPizza", "American Style", 28.0));
            menuItem.add(new MenuItem("TunaPizza", "Japan Style", 18.0));
        }
        
        public void addItem(MenuItem iterm) {
            menuItem.add(iterm);
        }
    
        public ArrayList<MenuItem> getMenuItem() {
            return menuItem;
        }
    
        public void setMenuItem(ArrayList<MenuItem> menuItem) {
            this.menuItem = menuItem;
        }
        
    }
    

    BarbecueMenu:

    public class BarbecueMenu {
    
        private static final int MAX_ITEMS = 5;
        
        private int numberOfIterms = 0;
        
        private MenuItem[] menuItems;
        
        public BarbecueMenu() {
            menuItems = new MenuItem[MAX_ITEMS];
            addItem(new MenuItem("chicken", "with pepper", 10));
            addItem(new MenuItem("tofu", "with pepper", 5));
            addItem(new MenuItem("fragrant-flowered garlic", "with pepper", 10));
        }
        
        public void addItem(MenuItem menuItem) {
            if(numberOfIterms >= MAX_ITEMS) {
                System.err.println("sorry, menu is full!");
            } else {
                menuItems[numberOfIterms] = menuItem;
                numberOfIterms = numberOfIterms + 1;
            }
        }
        
    }
    

    从代码可以看出,PizzaMenu是用List结构来存放菜单项目的,而BarbecueMenu则是用数组来存放菜单项目的。Vander作为老板又是他大显神威的时候了,他需要做一个菜单综合显示平台,显示出PizzaMenu和BarbecueMenu。我们来看看Vander的实现:

    public class MenuAdmin {
    
        private BarbecueMenu barbecueMenu;
    
        private PizzaMenu pizzaMenu;
    
        public MenuAdmin(BarbecueMenu barbecueMenu, PizzaMenu pizzaMenu) {
            super();
            this.barbecueMenu = barbecueMenu;
            this.pizzaMenu = pizzaMenu;
        }
    
        public void displayMenu() {
            MenuItem[] barbecueItems = barbecueMenu.getMenuItems();
            ArrayList<MenuItem> pizzaItems = pizzaMenu.getMenuItem();
            for (int i = 0; i < barbecueItems.length; i++) {//数组的大小为5
                if(barbecueItems[i] != null) {
                    System.out.println("name:" + barbecueItems[i].getName() + "-desc:" + barbecueItems[i].getDesc()
                            + "-price:" + barbecueItems[i].getPrice());
                }
            }
            
            for(MenuItem item : pizzaItems) {
                System.out.println("name:" + item.getName() + "-desc:" + item.getDesc()
                        + "-price:" + item.getPrice());
            }
    
        }
    
    }
    

    实现效果:

    image.png

    Vander这么设计真的好吗?
    1、MenuAdmin是不是直接针对PizzaMenu跟BarbecueMenu进行编码的,而不是针对接口编程。
    2、如果又加入了日式料理菜单,而菜单是用HashMap实现的,岂不是又得需要修改很多MenuAdmin的代码。
    3、MenuAdmin直接关心了PizzaMenu和BarbecueMenu的内部实现了,违反了封装原则。
    4、displayMenu部分有重复代码,有两个类似的循环,如果加入第三个菜单又需要多一个循环。
    Panda大师一看,又是糟糕的设计,Vander你就不能找一个迭代器来遍历这些数据吗,不同的数据结构实现不同的迭代器,MenuAdmin只需要用同样的循环就可以遍历这些菜单项了。接着Panda开始写迭代器(Iterator接口)。

    Iterator(迭代器)接口:

    public interface Iterator<T> {
    
        /**
         * 判断集合是否有下一项
         * @return
         */
        boolean hasNext();
        
        /**
         * 获取集合的下一项
         * @return
         */
        T next();
        
    }
    

    BarbecueMenuIterator:

    public class BarbecueMenuIterator implements Iterator<MenuItem> {
    
        private MenuItem[] menuItems;
        
        private int position = 0;
        
        public BarbecueMenuIterator(MenuItem[] menuItems) {
            super();
            this.menuItems = menuItems;
        }
    
        public boolean hasNext() {
            if(position >= 0 && menuItems[position] != null) {
                return true;
            }
            return false;
        }
    
        public MenuItem next() {
            if(position >= 0 && menuItems[position] != null) {
                MenuItem item = menuItems[position];  
                position++;
                return item;
            }
            return null;
        }
    
    }
    

    PizzaMenuIterator:

    public class PizzaMenuIterator implements Iterator<MenuItem> {
    
        private ArrayList<MenuItem> menuItems;
        
        private int position = 0;
        
        public PizzaMenuIterator(ArrayList<MenuItem> menuItems) {
            super();
            this.menuItems = menuItems;
        }
    
        public boolean hasNext() {
            if(position < menuItems.size() && menuItems.get(position) != null) {
                return true;
            }
            return false;
        }
    
        public MenuItem next() {
            if(position < menuItems.size() && menuItems.get(position) != null) {
                MenuItem item = menuItems.get(position);  
                position++;
                return item;
            }
            return null;
        }
    
    }
    

    BarbecueMenu:

    public class BarbecueMenu {
    
        private static final int MAX_ITEMS = 5;
        
        private int numberOfIterms = 0;
        
        private MenuItem[] menuItems;
        
        public BarbecueMenu() {
            menuItems = new MenuItem[MAX_ITEMS];
            addItem(new MenuItem("chicken", "with pepper", 10));
            addItem(new MenuItem("tofu", "with pepper", 5));
            addItem(new MenuItem("fragrant-flowered garlic", "with pepper", 10));
        }
        
        public void addItem(MenuItem menuItem) {
            if(numberOfIterms >= MAX_ITEMS) {
                System.err.println("sorry, menu is full!");
            } else {
                menuItems[numberOfIterms] = menuItem;
                numberOfIterms = numberOfIterms + 1;
            }
        }
    
        public Iterator<MenuItem> creatIterator() {
            BarbecueMenuIterator barbecueMenuIterator = new BarbecueMenuIterator(menuItems);
            return barbecueMenuIterator;
        }
    
        public void setMenuItems(MenuItem[] menuItems) {
            this.menuItems = menuItems;
        }
        
    }
    

    MenuAdmin:

    public class MenuAdmin {
    
        private BarbecueMenu barbecueMenu;
    
        private PizzaMenu pizzaMenu;
    
        public MenuAdmin(BarbecueMenu barbecueMenu, PizzaMenu pizzaMenu) {
            super();
            this.barbecueMenu = barbecueMenu;
            this.pizzaMenu = pizzaMenu;
        }
        
        public void displayMenus() {
            traverseMenus(barbecueMenu.creatIterator());
            traverseMenus(pizzaMenu.createIterator());
        }
        
        public void traverseMenus(Iterator<MenuItem> iterator) {
            while(iterator.hasNext()) {
                MenuItem menuItem = iterator.next();
                System.out.println("name:" + menuItem.getName() + "-desc:" + menuItem.getDesc()
                        + "-price:" + menuItem.getPrice());
            }
        }
        
    }
    

    下面是迭代器实现的基础类图:


    这里可能会有几个问题:
    1、能不能直接用BarbecueMenu直接实现迭代器接口?
    答案是可以的,但是代码侵入性太大,我们要保证原有类不进行太大的变动,再说了厨师们也不喜欢你乱搞。
    2、MenuAdmin中的两个属性能不能直接是两个菜单对应的迭代器?
    答案也是可以的,但是这样虽然实现上没有问题,但是迭代器作为较为低层的部分,这么做在逻辑上说不过去,本来菜单管理的类里面就应该是菜单管理的,突然属性变成了迭代器,这就有点奇怪了。

    分析:
    实际上当前的MenuAdmin依然是针对实现编程的,PizzaMenu和BarbecueMenu都有createIterator方法,所以可以定义一个Menu接口,java.util中的ArrayList实际上已经帮我们实现了Iterator了,我们直接用就可以了。接下来,进行下一步改造。
    我们首先删除我们自己定义的Iterator接口和PizzaMenuIterator(由于ArrayList本身就有迭代器实现),然后import进java.util.Iterator接口,然后将MenuAdmin中的实现改成接口,改写PizzaMenu类中的createIterator方法就完成了。
    Menu:

    public interface Menu<T> {
    
        /**
         * 创建迭代器
         * @return
         */
        Iterator<T> createIterator();
        
    }
    

    PizzaMenu:

    public class PizzaMenu implements Menu<MenuItem> {
    
        private ArrayList<MenuItem> menuItems;
        
        public PizzaMenu() {
            menuItems = new ArrayList<MenuItem>();
            menuItems.add(new MenuItem("FruitPizza", "Hawaii Style", 38.0));
            menuItems.add(new MenuItem("BuffPizza", "American Style", 28.0));
            menuItems.add(new MenuItem("TunaPizza", "Japan Style", 18.0));
        }
        
        public void addItem(MenuItem menuIterm) {
            menuItems.add(menuIterm);
        }
    
        public Iterator<MenuItem> createIterator() {
            Iterator<MenuItem> menuItemIterator =  menuItems.iterator();
            return menuItemIterator;
        }
    
        public void setMenuItem(ArrayList<MenuItem> menuItems) {
            this.menuItems = menuItems;
        }
        
    }
    

    MenuAdmin:

    public class MenuAdmin {
    
        private Menu<MenuItem> barbecueMenu;
    
        private Menu<MenuItem> pizzaMenu;
    
        public MenuAdmin(Menu<MenuItem> barbecueMenu, Menu<MenuItem> pizzaMenu) {
            super();
            this.barbecueMenu = barbecueMenu;
            this.pizzaMenu = pizzaMenu;
        }
        
        public void displayMenus() {
            traverseMenus(barbecueMenu.createIterator());
            traverseMenus(pizzaMenu.createIterator());
        }
        
        public void traverseMenus(Iterator<MenuItem> iterator) {
            while(iterator.hasNext()) {
                MenuItem menuItem = iterator.next();
                System.out.println("name:" + menuItem.getName() + "-desc:" + menuItem.getDesc()
                        + "-price:" + menuItem.getPrice());
            }
        }   
    
    }
    

    分析:
    这么写之后有什么好处呢:
    1、MenuAdmin不再针对于实现编程,而是针对接口(Menu)编程,减少了其对具体类的依赖。
    2、每个菜单都要求要实现Menu接口,所以每个菜单必须提供createIterator方法,让菜单管理类能够获得Iterator。MenuAdmin类也不需要关心每个菜单内部用了什么结构去存储数据,只需要用迭代器的方法就能完成遍历。

    下面我们隆重有请迭代器模式登场:

    迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的实现。迭代器模式把元素之间的游走的责任交给迭代器,而不是聚合对象,这不仅让聚合的接口和实现变得更简洁,也可以让聚合更专注在它所应该专注的事情(也就是管理对象集合),而不必去理会遍历的事情。

    迭代器的套路如下图所示,这里的Client对应MenuAdmin,ConcreteInterator对应PizzaMenuIterator和BarbecueMenuIterator,Aggregate对应Menu接口,ConcreteAggregate对应PizzaMenu和BarbecueMenu。

    迭代器一般类图

    这里简单说明一下聚合对象,首先当我们说集合的时候,是指一群对象,其存储方式可以是各种各样的数据结构,例如列表、数组、散列表,有时候集合也会叫做聚合。

    还记得我们刚刚讨论的吗,为什么不直接用PizzaMenu来实现Iterator接口然后实现遍历集合的这些方法?
    当你这么做之后,就会发现PizzaMenu不仅仅要维护MenuItems这个集合,还要承担遍历这个集合的责任,这样的话,这个集合改变,MenuItem就必须改变,若遍历的方式改变,这个集合又要改变,这样它就承担了两种变化的风险了。根据这个理念,我们引入了单一变化原则来说明这件事情。
    单一变化原则:\color{blue}{一个类应该只有一个引起变化的原因。}
    类的每一个责任都有改变的潜在区域。超过一个责任,意味着超过一个改变的区域。这个原则告诉我们尽量让每个类保持“单一责任”。
    \color{blue}{高内聚低耦合}是我们经常听到的,\color{blue}{内聚}是用来度量一个类或一个模块紧密地达到单一目的或责任。当一个类或模块被设计成只支持一组相关地功能时,我们说它具有高内聚;反之,当被设计成支持一组不相关地功能时,我们说它具有低内聚。内聚是一个比单一责任更加普遍的概念,但两者关系密切,遵守这原则的类容易具有很高的凝聚力,比低内聚的类容易维护得多。

    Vander在欢乐海岸的火锅店的店租也越来越贵了,近两个月甚至已经入不敷出了,所以它决定扩大pizza店的店面,加入烧烤之后再加入火锅,这样让客人有更多地选择,可惜地是厨师C的自助火锅店的菜单又是用另一种数据结构来完成的,不要紧,现在采用的系统已经具有一定的扩展性了。接下来添加火锅店的Menu的。
    HotPotMenu:

    public class HotPotMenu implements Menu<MenuItem>{
    
        private Map<String, MenuItem> itemMap; 
        
        public HotPotMenu(){
            itemMap = new HashMap<String, MenuItem>();
            itemMap.put("lotus root", new MenuItem("lotus root", "with salt", 10));
            itemMap.put("tofu", new MenuItem("tofu", "with salt", 5));
            itemMap.put("potatos", new MenuItem("potatos", "with salt", 10));
        }
        
        public Iterator<MenuItem> createIterator() {
            HotPotMenuIterator hotPotMenuIterator = new HotPotMenuIterator(itemMap);
            return hotPotMenuIterator;
        }
    
    }
    

    HotPotMenuIterator:

    public class HotPotMenuIterator implements Iterator<MenuItem>  {
    
        private Map<String, MenuItem> itemMap; 
        
        private Iterator<String> itemSetIterator;
        
        public HotPotMenuIterator(Map<String, MenuItem> itemMap) {
            this.itemMap = itemMap;
            itemSetIterator = itemMap.keySet().iterator();
        }
        
        public boolean hasNext() {
            if(itemSetIterator.hasNext()) {
                return true;
            }
            return false;
        }
    
        public MenuItem next() {
            MenuItem menuItem = itemMap.get(itemSetIterator.next());
            return menuItem;
        }
    
    }
    

    每次加入新的菜单,MenuAdmin都需要添加新的Menu,并且还要添加多一句遍历新菜单的语句。我们将其进一步进行改进。
    MenuAdmin:

    public class MenuAdmin {
    
        private List<Menu<MenuItem>> menuList;
    
        public MenuAdmin(List<Menu<MenuItem>> menuList) {
            this.menuList = menuList;
        }
        
        public void displayMenus() {
            for(Menu<MenuItem> menu : menuList) {
                traverseMenus(menu.createIterator());
            }
        }
        
        public void traverseMenus(Iterator<MenuItem> iterator) {
            while(iterator.hasNext()) {
                MenuItem menuItem = iterator.next();
                System.out.println("name:" + menuItem.getName() + "-desc:" + menuItem.getDesc()
                        + "-price:" + menuItem.getPrice());
            }
        }
        
    }
    

    最后又到了喜闻乐见的总结部分,我们又来总结我们现在现有的设计模式武器。

    面向对象基础

    抽象、封装、多态、继承

    九大设计原则

    设计原则一:封装变化
    设计原则二:针对接口编程,不针对实现编程
    设计原则三:多用组合,少用继承
    设计原则四:为交互对象之间的松耦合设计而努力
    设计原则五:对扩展开放,对修改关闭
    设计原则六:依赖抽象,不要依赖于具体的类
    设计原则七:只和你的密友谈话
    设计原则八:别找我,我有需要会找你
    设计原则九:类应该只有一个改变的理由

    模式

    迭代器模式:提供一个方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的实现。

    相关文章

      网友评论

        本文标题:设计模式系列教程—Iterator Pattern(迭代器模式)

        本文链接:https://www.haomeiwen.com/subject/lkvyaqtx.html