2

1 前言

有许多种方法可以将对象堆起来成为一个集合,比如数组,ArrayList,Hashtable。总会存在客户需要遍历这些集合的情况,所以需要知道存储对象的方式,而通过迭代器模式,可以实现让客户遍历你的对象但又无法窥视你存储对象的方式。

2 问题


现在有 煎饼屋 和 餐厅两家店做餐饮服务,他们打算强强联手,进行合并。
但是此时出现了一点问题,如果有顾客来吃饭,需要提供给顾客两家店所有的菜单,但是煎饼屋使用的存储方式是数组,而餐厅使用的是ArrayList,但是由于两家的其他代码分别依赖于各自的存储方式,如果修改存储方式,其他的地方也需要很多的修改,所以两家都不想要修改自己的存储方式。因此服务员需要来根据两家不同的存储方式获取到所有的菜单来显示给顾客。

PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
DinnerMenu dinnerMenu = new DinnerMenu();

Waitress waitress = new Waitress(pancakeHouseMenu, dinnerMenu);
waitress.printMenu();
public void printMenu() {
  ArrayList<MenuItem> pancakeItems = pancakeHouseMenu.getMenuItems();
  MenuItem[] dinnerItems = dinnerMenu.getMenuItems()
  for (int i = 0; i < pancakeItems.size(); i++) {
    MenuItem menuItem = pancakeItems.get(i);
    printItem(menuItem);
  }
  for (int i = 0; i < dinnerItems.length; i++) {
    MenuItem menuItem = dinnerItems[i];
    printItem(menuItem);
  }
}

可以看到,服务员需要分别对这两家的菜单进行处理然后给出全部的菜单,而且服务员如果要实现其他方法也都需要分别处理两个菜单,并且用两个循环遍历,还有可能他们进一步扩大来了第三家店那就需要分别对三家店的菜单进行处理了,总之,这种方式并不理想,我们需要一种更简单的形式,这时候迭代器模式就可以发挥的作用了。

3 迭代器模式

3.1 介绍

3.1.1 迭代器模式是什么

迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
迭代器模式让我门能够游走于聚合内的每一个元素,而又不暴露其内部的表示。把游走的任务房子迭代器上,而不是聚合上。这样简化了聚合的接口和实现,也让责任各得其所。也就是对遍历进行封装

3.1.2 实现

首先需要定义一个迭代器接口,为服务员提供一个统一的操作方式,这样服务员就无需知道每家店具体的存储方式。

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

然后需要让两家店分别对他们的菜单实现迭代器接口

public class PancakeHouseMenuIterator implements Iterator {
  ArrayList<MenuItem> menuItems;
  int position = 0;

  public PancakeHouseMenuIterator(ArrayList<MenuItem> menuItems) {
    this.menuItems = menuItems;
  }

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

  @Override
  public Object next() {
    MenuItem menuItem = menuItems.get(position);
    position = position + 1;
    return menuItem;
  }
}


public class DinnerMenuIterator implements Iterator {
  MenuItem[] menuItems;
  int position = 0;
  public DinnerMenuIterator(MenuItem[] menuItems) {
    this.menuItems = menuItems;
  }

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

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

然后为两家店添加返回对应的迭代器的方法

// PancakeHouseMenu
public Iterator getMenuItemsIterator() {
  return new PancakeHouseMenuIterator(menuItems);
}

// DinnerMenu
public Iterator getMenuItemsIterator() {
  return new DinnerMenuIterator(menuItems);
}

最后服务员就可以使用一个统一的方式来获取菜单项了

public class Waitress {

  PancakeHouseMenu pancakeHouseMenu;
  DinnerMenu dinnerMenu;

  public void printMenu() {
    Iterator pancakeItemsIterator = pancakeHouseMenu.getMenuItemsIterator();
    Iterator dinnerItemsIterator = dinnerMenu.getMenuItemsIterator();
    while (pancakeItemsIterator.hasNext()) {
      MenuItem menuItem = (MenuItem) pancakeItemsIterator.next();
      printItem(menuItem);
    }

    while (dinnerItemsIterator.hasNext()) {
      MenuItem menuItem = (MenuItem) pancakeItemsIterator.next();
      printItem(menuItem);
    }
  }
}

3.2 使用迭代器模式的前后对比

  1. 目前的类结构

    难以维护的服务员实现由迭代器支持的新服务员
    菜单封装的不好;煎饼屋使用的是数组ArrayList,而餐厅使用的是数组。菜单的实现已经被封装起来了。服务员不知道菜单是如何存储菜单项集合的
    需要两个循环来遍历菜单项只要实现迭代器,我们只需要一个循环,就可以多态的处理任何项的集合
    服务员捆绑于具体类(MenuItem[]和ArrayList)服务员现在只使用一个接口(迭代器)
    服务员捆绑于两个不同的具体菜单类,尽管这两个类的接口大致上是一样的使用的菜单接口完全一样,但是仍然捆绑于两个不同的具体菜单类。
  2. 优化
    由于使用迭代器模式之后,使用的两个菜单类的接口完全相同。所以我们将服务员与具体类与具体类解绑,用接口声明,实现面向接口编程。

    • 定义Menu接口
    public interface Menu {
      Iterator getMenuItemsIterator();
    }
    • 在菜单类中实现
    public class PancakeHouseMenu {
      ...
      @Override
      public Iterator getMenuItemsIterator() implements Menu {
        return new PancakeHouseMenuIterator(menuItems);
      }
    }
    
    public class DinnerMenu implements Menu {
      ... 
      @Override
      public Iterator getMenuItemsIterator() {
        return new DinnerMenuIterator(menuItems);
      }
    }
    • 服务员使用接口声明
    public class Waitress {
      Menu pancakeHouseMenu;
      Menu dinnerMenu;
    
      public Waitress(Menu pancakeHouseMenu, Menu dinnerMenu) {
         this.pancakeHouseMenu = pancakeHouseMenu;
         this.dinnerMenu = dinnerMenu;
      }
      ...
    }
    • 继续优化服务员
    public class Waitress {
     Menu[] menus;
    
     public Waitress(Menu[] menus) {
         this.menus = menus;
     }
    
     public void printMenu() {
       for (Menu m : menus) {
         Iterator iterator = m.getMenuItemsIterator();
         while (iterator.hasNext()) {
           MenuItem menuItem = (MenuItem) iterator.next();
           printItem(menuItem);
         }
       }
     }
    }

    最后实现:

    3.2 第三家餐厅入驻

    这时只需要分别实现MenuIterator两个接口,从而拥有XXXMenuXXXIterator两个新的类,在实例化服务员的时候将XXXMenu放入到构造函数中即可。

4 参考

《Head First 设计模式》


chshihang
116 声望13 粉丝

引用和评论

0 条评论