【设计形式】之迭代器(Iterator)

【设计模式】之迭代器(Iterator)

迭代器的定义为:提供一个无需暴露对象的底层实现而连续访问聚合对象元素的方法。

Provide a way to access the elements of an aggregate objectsequentially withoutexposing its underlying representation。

由定义可以看出,Iterator有三个关键词,首先是聚合对象(aggregate object),《Head First》中给出了定义:

When we say COLLECTION we just mean a group of objects. They might be stored in very different data structures lik
e lists, arrays, hashtables, but they're still collections. We also sometimes call these AGGREGATE.

简单来说一个容器就是一个聚合对象。

第二个关键词是连续地(sequentially),迭代器要能够追踪当前要访问的元素。

第三个关键词是暴露(exposing),迭代器隐藏了对象的聚合方式,也就是说使用迭代器的客户是无法知道对象的聚合方式是数组还是列表。


迭代器模式的关键是遵循了单一责任(Single Responsibility)的设计原则:

The key idea in this pattern is to take the responsibility for access and traversal out of the list object and put it into aniterator object. --<Design Patterns>

Assign each responsibility to one class and only one class. --<Head First Design Patterns>

单一责任可以让iterator更专注地去完成访问元素的任务,因此它可以在不该变客户端代码的前提下,实现各种各样的访问方式,并且提供了一个统一的访问方式。


迭代器可以看作是聚合对象的扩展。

An iterator can be viewed as an extension of the aggregate that created it.

迭代器的内容太多了,还分为external与internal。external就是需要客户端去迭代,而internal则只需要调用一个方法即可,迭代过程都是对客户端是透明的。

举例说明

首先定义一个迭代器的接口 Interator

public interface Iterator {
    boolean hasNext();
    Object next();
}

需要迭代遍历的类型,菜单项。

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;
    }
}
定义一个菜单,里面存储的是菜单项。

public class DinerMenu {
    static final int MAX_ITEMS = 6;
    int numberOfItems = 0;
    MenuItem[] menuItems;

    public DinerMenu() {
        menuItems = new MenuItem[MAX_ITEMS];

        addItem("Vegetarian BLT",
            "(Fakin') Bacon with lettuce & tomato on whole wheat", true, 2.99);
        addItem("BLT",
            "Bacon with lettuce & tomato on whole wheat", false, 2.99);

        addItem("Soup of the day",
            "Soup of the day, with a side of potato salad", false, 3.29);

        addItem("Hotdog",
            "A hot dog, with saurkraut, relish, onions, topped with cheese",
            false, 3.05);
        //a couple of other Diner Menu items added here

    }

    public void addItem(String name, String description,
                        boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        if (numberOfItems >= MAX_ITEMS) {
            System.err.println("Sorry, menu is full! Can't add item to menu");
        } else {
            menuItems[numberOfItems] = menuItem;
            numberOfItems = numberOfItems + 1;
        }
    }

//      public MenuItem[] getMenuItems() {
//          return menuItems;
//      }

    public Iterator createIterator() {
        return new DinerMenuIterator(menuItems);
    }
    // other menu methods here
}
定义个另一个菜单

import java.util.ArrayList;
public class PancakeHouseMenu {
    ArrayList menuItems;

    public PancakeHouseMenu() {
        menuItems = new ArrayList();

        addItem("K&B's Pancake Breakfast",
            "Pancakes with scrambled eggs, ant toast",
            true,
            2.99);
        
        addItem("Regular Pancake Breakfast",
            "Pancakes with fired eggs, sausage",
            false,
            2.99);

        addItem("Blueberry Pancakes",
            "Pancakes made with fresh bluebarries",
            true,
            3.49);

        addItem("Waffles",
            "Waffles, with your choice of blueberries or strawberries",
            true,
            3.59);
    }

    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;
//    }

    public Iterator createIterator() {
        return new PancakeMenuIterator(menuItems);
    }
    // other menu methods here
}
菜单迭代器

import java.util.ArrayList;
public class PancakeMenuIterator implements Iterator {
    ArrayList items;
    int position = 0;

    public PancakeMenuIterator(ArrayList items) {
        this.items = items;
    }

    public Object next() {
        return items.get(position++);
    }

    public boolean hasNext() {
        return position < items.size();
    }
}

public class Waitress {
    PancakeHouseMenu pancakeHouseMenu;
    DinerMenu dinerMenu;

    public Waitress(PancakeHouseMenu pancakeHouseMenu, DinerMenu dinerMenu) {
        this.pancakeHouseMenu = pancakeHouseMenu;
        this.dinerMenu = dinerMenu;
    }

    public void printMenu() {
        Iterator pancakeIterator = pancakeHouseMenu.createIterator();
        Iterator dinerIterator = dinerMenu.createIterator();
        System.out.println("MENU\n---\nBREAKFAST");
        printMenu(pancakeIterator);
        System.out.println("\nLUNCH");
        printMenu(dinerIterator);
    }

    private void printMenu(Iterator iterator) {
        while (iterator.hasNext()) {
            MenuItem menuItem = (MenuItem)iterator.next();
            System.out.println(menuItem.getName() + ", ");
            System.out.println(menuItem.getPrice() + " -- ");
            System.out.println(menuItem.getDescription());
        }
    }

    // other methods here
}

测试代码

public class MenuTestDrive {
    public static void main(String[] args) {
        PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
        DinerMenu dinerMenu = new DinerMenu();

        Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu);

        waitress.printMenu();
    }
}

通过代码可以看出,客户端代码Waitress的代码,不许要关心每个菜单对象内部的聚合情况,只需要调用相应的遍历器便可。

下一节组合模式(Composite)里面会继续用到迭代器。