Iterator

什么是迭代器模式

迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示。

HeadFirst 上实例练习

MenuItem

新的餐厅想用煎饼屋菜单当作早餐的菜单,使用餐厅的菜单当做午餐的菜单,大家都同意了这样实现菜单项。但是大家无法同意菜单的实现。煎饼屋使用ArrayList记录他的菜单项,而餐厅使用的是数组。他们两个都不愿意改变他们的实现,毕竟有太多代码依赖于它们了。

package iterator.ver1;

/**
 * Created by liqiushi on 2017/12/5.
 */
public class MenuItem {
    String name;
    //描述
    String description;
    // 是否为素食
    boolean vegetarian;
        // 价格

    double price;

    public MenuItem(String name,
                    String description,
                    boolean vegetarian,
                    double price) {
        this.name = name;
        this.description = description;
        this.vegetarian = vegetarian;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }


    public double getPrice() {
        return price;
    }
    
    public boolean isVegetarian() {
        return vegetarian;
    }
}

package iterator.ver1;

import java.util.ArrayList;

/**
 * Created by liqiushi on 2017/12/5.
 */
public  class  PancakeHouseMenu {

    // 煎饼屋使用一个ArrayList存储他的菜单项

    ArrayList menuItems;

    public PancakeHouseMenu() {

        menuItems = new ArrayList();

        // 在菜单的构造器中,每一个菜单项都会被加入到ArrayList中

        // 每个菜单项都有一个名称、一个描述、是否为素食、还有价格

        addItem("K&B's Pancake Breakfast",
                "Pancakes with scrambled eggs, and toast",
                true,
                2.99);
        addItem("Regular Pancake Breakfast",
                "Pancakes with fried eggs, sausage",
                false,
                2.99);
        addItem("Blueberry Pancakes",
                "Pancakes made with fresh blueberries",
                true,
                3.49);
        addItem("Waffles",
                "Waffles, with your choice of blueberries or strawberries",
                true,
                3.59);
    }

    
    // 要加入一个菜单项,煎饼屋的做法是创建一个新的菜单项对象,
    // 传入每一个变量,然后将它加入ArrayList中
    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        menuItems.add(menuItem);
    }
    

    // 这个方法返回菜单项列表
    public ArrayList getMenuItems() {

        return menuItems;

    }
    
    // 这里还有菜单的其他方法,这些方法都依赖于这个ArrayList,所以煎饼屋不希望重写全部的代码!

    // ...

}

最初版本的实现

// getMenuItems()方法看起来是一样的,但是调用所返回的结果却是不一样的类型。
// 早餐项是在一个ArrayList中,午餐项则是在一个数组中
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
ArrayList breakfastItems = pancakeHouseMenu.getMenuItems();
 
DinnerMenu dinnerMenu = new DinnerMenu();
MenuItem[] lunchItems = dinnerMenu.getMenuItems();

// 现在,我们必须实现两个不同的循环,个别处理这两个不同的菜单
for (int i = 0; i < breakfastItems.size(); i++) {
    MenuItem menuItem = (MenuItem) breakfastItems.get(i);
    System.out.print(menuItem.getName() + " ");
    System.out.print(menuItem.getPrice() + " ");
    System.out.println(menuItem.getDescription() + " ");
}
 
for (int i = 0; i < lunchItems.length; i++) {
    MenuItem menuItem = lunchItems[i];
    System.out.print(menuItem.getName() + " ");
    System.out.print(menuItem.getPrice() + " ");
    System.out.println(menuItem.getDescription() + " ");
}

因为返回的类型不同,所以我们需要两个循环来分别处理。如果我们现在想通过一种方式就可以遍历,而且并不需要知道其中的实现,让他们的两个菜单实现同一个接口,封装变化的部分

迭代器

package iterator.ver1;

/**
 * Created by liqiushi on 2017/12/6.
 */
public interface Iterator {
    Boolean hasNext();
    Object next();
}

迭代器实现

package iterator.ver1;

/**
 * Created by liqiushi on 2017/12/6.
 */
public class DinnerMenuIterator implements Iterator {
    MenuItem[] items;
    // position记录当前数组遍历的位置
    int position = 0;

    // 构造器需要被传入一个菜单项的数组当做参数
    public DinnerMenuIterator(MenuItem[] items) {
        this.items = items;
    }

    // next()方法返回数组内的下一项,并递增其位置
    public Object next() {
        MenuItem menuItem = items[position];
        position = position + 1;
        return menuItem;
    }

    // hasNext()方法会检查我们是否已经取得数组内所有的元素。
    // 如果还有元素待遍历,则返回true
    public boolean hasNext() {
        if (position >= items.length || items[position] == null) {
            return false;
        } else {
            return true;
        }
    }
}

修改之前的菜单

  • DinnerMenu
public class DinnerMenu {
    static final int MAX_ITEMS = 6;
    int numberOfItems = 0;
    MenuItem[] menuItems;
 
    // ...
 
    // 我们不再需要getMenuItems()方法,事实上,我们根本不想要这个方法,
    // 因为它会暴露我们内部的实现。
    // 这是createIterator()方法,用来从菜单项数组创建一个DinnerMenuIterator,
    // 并将它返回给客户
    public Iterator createIterator() {
        return new DinnerMenuIterator(menuItems);
    }

    // ...
}

加入迭代器后的女服务生

public class Waitress {
    PancakeHouseMenu pancakeHouseMenu;
    DinnerMenu dinnerMenu;
 
    // 在构造器中,女招待照顾两个菜单
    public Waitress(PancakeHouseMenu pancakeHouseMenu, DinnerMenu dinnerMenu) {
        this.pancakeHouseMenu = pancakeHouseMenu;
        this.dinnerMenu = dinnerMenu;
    }
 
    public void printMenu() {
        // 这个printMenu()方法为每一个菜单各自创建一个迭代器
        Iterator pancakeIterator = pancakeHouseMenu.createIterator();
        Iterator dinnerIterator = dinnerMenu.createIterator();
        // 然后调用重载的printMenu(),将迭代器传入
        printMenu(pancakeIterator);
        printMenu(dinnerIterator);
    }
 
    // 这个重载的printMenu()方法,使用迭代器来遍历菜单项并打印出来
    private void printMenu(Iterator iterator) {
        while (iterator.hasNext()) {
            MenuItem menuItem = (MenuItem) iterator.next();
            System.out.println(menuItem.getName() + " " + 
                    menuItem.getPrice() + " " + menuItem.getDescription());
        }
    }
}

继续优化

Menu接口,使女招待员解耦

package iterator.ver2;
import java.util.Iterator;
/**
 * Created by liqiushi on 2017/12/7.
 */
public interface Menu {
    Iterator createIterator();
}

女招待员

//...
    // 在构造器中,女招待照顾两个菜单
    public Waitress(Menu pancakeHouseMenu, Menu dinnerMenu) {
        this.pancakeHouseMenu = pancakeHouseMenu;
        this.dinnerMenu = dinnerMenu;
    }
//...

其他的优化

将菜单放在一个ArraList里,遍历它的所有项
或者后面的组合模式,用一个迭代器

迭代器模式的优缺点

定义迭代器模式

现在我们来看看这个模式的正式定义:

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
迭代器模式让我们能游走于聚合内的每一个元素,而又不暴露内部的表示。把游走的任务放在迭代器上,而不是聚合上,这样简化了聚合的接口和实现,也让责任各得其所。
这很有意义:这个模式给你提供了一种方法,可以顺序访问一个聚集对象中的元素,而又不用知道内部是如何表示的。你已经在前面的两个菜单实现中看到了这一点。在设计中使用迭代器的影响是明显的:如果你有一个统一的方法访问聚合中的每一个对象,你就可以编写多态的代码和这些聚合搭配使用,如同前面的printMenu()方法一样,只要有了迭代器这个方法,根本不管菜单项究竟是由数组还是由ArrayList(或者其他能创建迭代器的东西)来保存的。
另一个对你的设计造成重要影响的,是迭代器模式把这些元素之间游走的责任交给迭代器,而不是聚合对象。这不仅让聚合的接口和实现变得更简洁,也可以让聚合更专注在它所应该专注的事情上面(也就是管理对象组合),而不必去理会遍历的事情。

单一责任

如果我们允许我们的聚合实现它们内部的集合,以及相关的操作和遍历的方法,又会如何?我们已经知道这会增加聚合中的方法个数,但又怎样呢?为什么这么做不好?
想知道为什么,首先你需要认清楚,当我们允许一个类不但要完成自己的事情(管理某种聚合),还同时要担负更多的责任(例如遍历)时,我们就给了这个类两个变化的原因。两个?没错,就是两个!如果这个集合改变的话,这个类也必须改变,如果我们遍历的方式改变的话,这个类也必须跟着改变。所以,再一次地,我们的老朋友“改变”又成了我们设计原则的中心:

设计原则:一个类应该只有一个引起变化的原因

我们知道要避免类内的改变,因为修改代码很容易造成许多潜在的错误。如果有一个类具有两个改变的原因,那么这会使得将来该类的变化几率上升,而当它真的改变时,你的设计中同时有两个方面将会受到影响。
没错,这听起来很容易,但其实做起来并不简单:区分设计中的责任,是最困难的事情之一。我们的大脑很习惯看着一大群的行为,然后将它们集中在一起,尽管他们可能属于两个或者多个不同的责任。想要成功的唯一方法,就是努力不懈地检查你的设计,随着系统的成长,随时观察有没有迹象显示某个类改变的原因超出一个。

你可能感兴趣的:(Iterator)