设计模式之迭代器模式

披萨店和咖啡店合并了

主管告诉小明,披萨店和咖啡店合并了,小明说你怎么知道啊?因为他们在合并是遇到了问题,由于他们的菜单不一样,所以在服务员在使用时很麻烦,这不新店找到了小明他们帮助解决这个问题。

菜单上的单项(庆幸的是他们菜单上菜品是用的相同的类):

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 PizzaMenu implements Menu {
    ArrayList menuItems;

    public PizzaMenu() {
        menuItems = new ArrayList();
        addItem("芝士披萨", "芝士加番茄", true, 2.99);
        addItem("榴莲披萨", "榴莲味", true, 3.99);
    }

    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;
    }
    // other menu methods here
}

咖啡店菜单:

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

    public CoffeeMenu() {
        menuItems = new MenuItem[MAX_ITEMS];
        addItem("焦糖玛奇朵", "", true, 12);
        addItem("拿铁", "", true, 10);
    }

    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;
    }
    // other menu methods here
}

合并以后,服务员在使用两个菜单必须这样

public class Waitress {

    PizzaMenu pizzaMenu;
    CoffeeMenu coffeeMenu;

    public Waitress(PizzaMenu pizzaMenu, Menu coffeeMenu) {
        this.pizzaMenu = pizzaMenu;
        this.coffeeMenu = coffeeMenu;
    }

    public void printMenu() {
        ArrayList pizzaMenuItems = pizzaMenu. getMenuItems();
        // 打印披萨菜单
        MenuItem[] cafeMenuItems = coffeeMenu. getMenuItems();
        // 打印咖啡菜单
    }
}

对于两个菜单,他们用不同的集合实现了内部的菜品管理,一个是ArrayList,一个是数组,所以服务员在操作时,就必须按照它们数据结构不同的操作方式去操作这些数据,最严重的问题时,如果再合并一家其他的餐厅,而该餐厅用的又是Map来管理的,那么不但要修改服务员的代码,还需要用map的遍历方式去阅读菜单。这样服务员和菜单的耦合就太严重了,所以必须要有一种方式防止这个的问题,小明问主管,有什么方式呢?主管说,迭代器模式可以很好的解决这个问题,让我们开始动手优化吧!

首先,创造一个迭代器:

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

分别针对两个菜单都实现它:

披萨菜单迭代器:

public class PizzaMenuIterator implements Iterator {
    ArrayList items;
    int position = 0;

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

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

    public boolean hasNext() {
        if (position >= items.size()) {
            return false;
        } else {
            return true;
        }
    }
}

咖啡菜单迭代器:

public class CoffeeMenuIterator implements Iterator {
    MenuItem[] items;
    int position = 0;

    public CoffeeMenuIterator(MenuItem[] items) {
        this.items = items;
    }

    public Object next() {
        MenuItem menuItem = items[position];
        position = position + 1;
        return menuItem;
    }

    public boolean hasNext() {
        if (position >= items.length || items[position] == null) {
            return false;
        } else {
            return true;
        }
    }
}

新的披萨店菜单:

public class PizzaMenu implements Menu {
    ArrayList menuItems;

    public PizzaMenu() {
        menuItems = new ArrayList();
        addItem("芝士披萨", "芝士加番茄", true, 2.99);
        addItem("榴莲披萨", "榴莲味", true, 3.99);
    }

    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        menuItems.add(menuItem);
    }

    public Iterator createIterator() {
        return new PizzaMenuIterator(menuItems);
    }
    // other menu methods here
}

新的咖啡店菜单:

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

    public CoffeeMenu() {
        menuItems = new MenuItem[MAX_ITEMS];
        addItem("焦糖玛奇朵", "", true, 12);
        addItem("拿铁", "", true, 10);
    }

    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 Iterator createIterator() {
        return new CoffeeMenuIterator(menuItems);
    }
    // other menu methods here
}

两个新的菜单将之前获取具体数据集的方式修改成了createIterator()方法,我们看看新的方式下,服务员再使用他们时的用法:

public class Waitress {

    PizzaMenu pizzaMenu;
    CoffeeMenu coffeeMenu;

    public Waitress(PizzaMenu pizzaMenu, Menu coffeeMenu) {
        this.pizzaMenu = pizzaMenu;
        this.coffeeMenu = coffeeMenu;
    }

    public void printMenu() {
        Iterator pizzaIterator = pizzaMenu.createIterator();
        printMenu(pizzaIterator);
        Iterator coffeeIterator = coffeeMenu.createIterator();
        printMenu(coffeeIterator);
    }

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

和之前的方式相比,服务员浏览(打印所有菜品)菜单只用了一个方法printMenu(Iterator iterator),如果以后新增了用其他集合管理的菜单,只需要用一个新的迭代器去实现相应数据集合的迭代即可,服务员只需要拿到这个新的菜单就可以浏览了,而不用去修改服务员内部代码。这不是适配器吗?稍等,先看看定义,然后我们再总结他们的区别。

定义

提供一种方法顺序访问集合对象中的各个元素,而又不暴露其内部的表示。

Iterator

角色

  • 迭代器
  • 迭代器实现
  • 聚合 主要是定义createIterator方法
  • 聚合的实现

迭代器和集合同存亡

深入认识一下迭代器

首先回答上面那个问题,它和适配器有什么区别,其实从定义可以看出来,迭代是为了访问集合中的元素,而适配器将不满足要求的接口适配为满足要求的接口。而且有很重要的一点,适配器不仅仅是为了统一接口,它的作用还体现了一个重要的原则单一职责原则,像上面我们的菜单,它主要的职责是菜单本身,如果将访问菜单也加入菜单类的功能,那么就违背了单一职责原则,所以将这个功能用单独的一个迭代器去实现,将更好的体现菜单本身的职责。

实际的运用

我们刚才实现了两个迭代器,分别针对ArrayList和数组,其实JDK本身已经给我们在ArrayList提供了这个功能:

@Override public Iterator<E> iterator() {
    return new ArrayListIterator();
}

private class ArrayListIterator implements Iterator<E> {
    /** Number of elements remaining in this iteration */
    private int remaining = size;

    /** Index of element that remove() would remove, or -1 if no such elt */
    private int removalIndex = -1;

    /** The expected modCount value */
    private int expectedModCount = modCount;

    public boolean hasNext() {
        return remaining != 0;
    }

    @SuppressWarnings("unchecked") public E next() {
        ArrayList<E> ourList = ArrayList.this;
        int rem = remaining;
        if (ourList.modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        if (rem == 0) {
            throw new NoSuchElementException();
        }
        remaining = rem - 1;
        return (E) ourList.array[removalIndex = ourList.size - rem];
    }

    public void remove() {
        Object[] a = array;
        int removalIdx = removalIndex;
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        if (removalIdx < 0) {
            throw new IllegalStateException();
        }
        System.arraycopy(a, removalIdx + 1, a, removalIdx, remaining);
        a[--size] = null;  // Prevent memory leak
        removalIndex = -1;
        expectedModCount = ++modCount;
    }
}

如上面的PizzaMenu菜单中的createIterator()方法,其实直接可以这样使用

public class PizzaMenu implements Menu {
    ArrayList menuItems;

    public PizzaMenu() {
        menuItems = new ArrayList();
        addItem("芝士披萨", "芝士加番茄", true, 2.99);
        addItem("榴莲披萨", "榴莲味", true, 3.99);
    }

    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        menuItems.add(menuItem);
    }

    public Iterator createIterator() {
        return menuItems.iterator();
    }
    // other menu methods here
}

当然我们之前的Iterator是我们自己定义的,其实JDK也有现成的迭代器接口,我们可以直接使用,它提供了三个方法供我们实现:

public interface Iterator<E> {
    public boolean hasNext();
    public E next();
    public void remove();
}

而且在JDK1.5之后,我们可以用for/in语法遍历迭代器,而不用再像上面那种方式去遍历数据了:

之前:

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

现在:

ArrayList<MenuItem> menuItems = new ArrayList();
menuItems.add(new MenuItem());
// ……
for(MenuItem menuItem : menuItems){
    System.out.print(menuItem.getName() + ", ");
    System.out.print(menuItem.getPrice() + " -- ");
    System.out.println(menuItem.getDescription());
}

JDK中的迭代器:

ArrayList --> ArrayListIterator
HahsMap --> KeyIterator、ValueIterator、EntryIterator
LinkedList --> LinkIterator
Vector --> SimpleListIterator
……

内部迭代器

刚才我们实现的是一个外部迭代器,什么是外部迭代器呢,其实很简单,就是客户端通过调用next()方法取得下一个元素,而内部迭代器是由迭代器内部控制一切,我们只需要告诉它要做的操作,其他的事就由它自己完成了。

坚持原创分享,您的支持将鼓励我不断前行!