bboyjing's blog

HeadFirst设计模式十【迭代器模式】

这个模式从字面上就大概能知道是什么意思,我们将要学习如何能让用户遍历你的对象而又无法窥视你存储对象的方式。

场景描述

假设有两个餐厅,一个是做早餐的,另外一个是做午餐的。其中一个菜单是使用ArrayList来记录菜单项,而另一家用的是数组。现在两家餐厅合并了,在不大改的情况下,两种餐单如何做兼容。先看下两家菜单当前的实现:

  1. 两家的菜单项是一样的,有名称、描述、是否是素食、价格

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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 boolean isVegetarian() {
    return vegetarian;
    }
    public double getPrice() {
    return price;
    }
    }
  2. 煎饼屋的菜单使用ArrayList实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public class PancakeHouseMenu {
    ArrayList menuItems;
    public PancakeHouseMenu() {
    menuItems = new 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, and blueberry syrup",
    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;
    }
    }
  3. 餐厅的菜单使用数组实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    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);
    addItem("Steamed Veggies and Brown Rice",
    "Steamed vegetables over brown rice", true, 3.99);
    addItem("Pasta",
    "Spaghetti with Marinara Sauce, and a slice of sourdough bread",
    true, 3.89);
    }
    public void addItem(String name, String description, boolean vegetarian, double price) {
    MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
    if (numberOfItems >= MAX_ITEMS) {
    System.out.println("Sorry, menu is full! Can't add item to menu");
    } else {
    menuItems[numberOfItems] = menuItem;
    numberOfItems = numberOfItems + 1;
    }
    }
    public MenuItem[] getMenuItems() {
    return menuItems;
    }
    }
  4. 测试打印菜单,从代码可以看出,必须处理两个不一样的菜单。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class Test {
    public static void main(String[] args) {
    PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
    ArrayList breakfastItems = pancakeHouseMenu.getMenuItems();
    DinerMenu dinerMenu = new DinerMenu();
    MenuItem[] lunchItems = dinerMenu.getMenuItems();
    for (int i = 0; i < breakfastItems.size(); i++) {
    MenuItem menuItem = (MenuItem) breakfastItems.get(i);
    System.out.print(menuItem.getName() + " ");
    System.out.println(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.println(menuItem.getPrice() + " ");
    System.out.println(menuItem.getDescription());
    }
    }
    }

下面就来看下迭代器模式如何来处理这个场景。

代码示例

  1. 定义迭代器接口

    1
    2
    3
    4
    5
    public interface Iterator {
    boolean hasNext();
    Object next();
    }
  2. 菜单项目跟上面一样,不贴出来了

  3. 创建餐厅迭代器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class DinerMenuIterator implements Iterator {
    MenuItem[] items;
    int position = 0;
    public DinerMenuIterator(MenuItem[] items) {
    this.items = items;
    }
    @Override
    public boolean hasNext() {
    if (position >= items.length || items[position] == null) {
    return false;
    } else {
    return true;
    }
    }
    @Override
    public Object next() {
    MenuItem menuItem = items[position];
    position = position + 1;
    return menuItem;
    }
    }
  4. 改写餐厅菜单,将getMenuItems()替换成createIterator(),其它代码保持不变

    1
    2
    3
    4
    5
    6
    public class DinerMenu {
    ......
    public Iterator createIterator() {
    return new DinerMenuIterator(menuItems);
    }
    }
  5. 创煎饼屋菜单迭代器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class PancakeHouseMenuIterator implements Iterator {
    ArrayList items;
    int position = 0;
    public PancakeHouseMenuIterator(ArrayList items) {
    this.items = items;
    }
    @Override
    public boolean hasNext() {
    return position < items.size();
    }
    @Override
    public Object next() {
    MenuItem menuItem = (MenuItem) items.get(position);
    position++;
    return menuItem;
    }
    }
  6. 改写煎饼屋菜单,将getMenuItems()替换成createIterator(),其它代码保持不变

    1
    2
    3
    4
    5
    6
    public class PancakeHouseMenu {
    ......
    public Iterator createIterator() {
    return new PancakeHouseMenuIterator(menuItems);
    }
    }
  7. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class Test {
    public static void main(String[] args) {
    DinerMenu dinerMenu = new DinerMenu();
    printMenu(dinerMenu.createIterator());
    PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
    printMenu(pancakeHouseMenu.createIterator());
    }
    private static void printMenu(Iterator iterator) {
    while (iterator.hasNext()) {
    MenuItem menuItem = (MenuItem) iterator.next();
    System.out.print(menuItem.getName() + " ");
    System.out.println(menuItem.getPrice() + " ");
    System.out.println(menuItem.getDescription());
    }
    }
    }

这个模式的好处也是显而易见的,利用了迭代器屏蔽了遍历各种集合的实现,这样使用的时候就统一了。迭代器模式就到这里了,这里我们再引出更复杂的一个场景。假如现在需要在餐厅菜单中添加甜点子菜单,目前的数据结构式不支持子菜单功能的,需要重构看来支持这种树形结构。下一章节将学习一种新的模式:组合模式,来解决这个问题。