迭代器模式
顾名思义,就是将遍历等操作封装起来,对外统一成一个接口,获取遍历器,方便用户代码使用。
概念定义:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
小例子
例子摘自head first,帮助理解。
餐厅一
餐厅一的现有程序,使用ArrayList作为菜单的数据结构,如下:
public class PancakeHouseMenu {
private ArrayList<MenuItem> menuItems;
public PancakeHouseMenu() {
menuItems = new ArrayList<>();
addItem("pancake1", "good1", true, 10.5);
addItem("pancake2", "good2", false, 11.5);
addItem("pancake3", "good3", true, 12.5);
addItem("pancake4", "good4", false, 13.5);
}
public void addItem(String name, String description, boolean vegetarian, double prive) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, prive);
menuItems.add(menuItem);
}
public ArrayList<MenuItem> getMenuItems() {
return menuItems;
}
//..其他方法
}
餐厅二
餐厅二的现有程序,使用数组作为菜单的数据结构,如下:
public class DinnerMenu {
private final static int MAX_NUMBER_OF_ITEMS = 4;
private MenuItem[] menuItems;
private int numberOfItems = 0;
public DinnerMenu() {
menuItems = new MenuItem[MAX_NUMBER_OF_ITEMS];
addItem("pancake1", "good1", true, 10.5);
addItem("pancake2", "good2", false, 11.5);
addItem("pancake3", "good3", true, 12.5);
addItem("pancake4", "good4", false, 13.5);
}
public void addItem(String name, String description, boolean vegetarian, double prive) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, prive);
if (numberOfItems >= MAX_NUMBER_OF_ITEMS) {
throw new RuntimeException("超过最大数量");
} else {
menuItems[numberOfItems] = menuItem;
numberOfItems++;
}
}
public MenuItem[] getMenuItems() {
return menuItems;
}
//..其他方法
}
餐厅合并,苦了服务员
餐厅合并,服务员的代码程序需要给客户展示菜单,及print所有的菜单项,这时候麻烦来了。
public class oldWaiter {
private ArrayList<MenuItem> menuItemArrayList;
private MenuItem[] menuItems;
public oldWaiter() {
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
menuItemArrayList = pancakeHouseMenu.getMenuItems();
DinnerMenu dinnerMenu = new DinnerMenu();
menuItems = dinnerMenu.getMenuItems();
}
public void print() {
for (int i = 0; i < menuItemArrayList.size(); i++) {
System.out.println(menuItemArrayList.get(i).toString());
}
for (int i = 0; i < menuItems.length; i++) {
System.out.println(menuItems[i].toString());
}
}
}
代码好像看似还比较简单,当然我们只是举例子,但如果有3个、4个、5个餐厅呢?代码是不是变得重复和冗余,功能都是遍历和打印。
思考一下
代码变化的部分是不同的数据结构,我们是不是可以封装遍历
呢,使用统一接口的方法就可以遍历任意的数据结构?
利用迭代器
我们创建一个迭代器接口:
public interface Iterator {
boolean hasNext();
Object next();
}
迭代器接口定义了一个hasNext()和next()方法,其中hasNext()方法判断数据结构中是否还有数据,也就是游标是否到了尽头。next()方法负责返回游标所处的位置。
实现两个餐厅菜单的迭代器:
public class PancakeHouseMenuIterator implements Iterator {
private ArrayList<MenuItem> menuItemArrayList;
private int iteratorIndex = 0;
public PancakeHouseMenuIterator(ArrayList<MenuItem> menuItemArrayList) {
this.menuItemArrayList = menuItemArrayList;
}
@Override
public boolean hasNext() {
if (iteratorIndex >= menuItemArrayList.size()) {
return false;
} else {
return true;
}
}
@Override
public Object next() {
MenuItem menuItem = menuItemArrayList.get(iteratorIndex);
iteratorIndex++;
return menuItem;
}
}
public class DinnerMenuIterator implements Iterator {
private MenuItem[] menuItems;
private int iteratorIndex = 0;
public DinnerMenuIterator(MenuItem[] menuItems) {
this.menuItems = menuItems;
}
@Override
public boolean hasNext() {
if (iteratorIndex >= menuItems.length || menuItems[iteratorIndex] == null) {
return false;
} else {
return true;
}
}
@Override
public Object next() {
MenuItem menuItem = menuItems[iteratorIndex];
iteratorIndex++;
return menuItem;
}
}
两个迭代器实现了Iterator接口,负责遍历。
改造餐厅
public class PancakeHouseMenu {
private ArrayList<MenuItem> menuItems;
public PancakeHouseMenu() {
menuItems = new ArrayList<>();
addItem("pancake1", "good1", true, 10.5);
addItem("pancake2", "good2", false, 11.5);
addItem("pancake3", "good3", true, 12.5);
addItem("pancake4", "good4", false, 13.5);
}
public void addItem(String name, String description, boolean vegetarian, double prive) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, prive);
menuItems.add(menuItem);
}
// public ArrayList<MenuItem> getMenuItems() {
// return menuItems;
// }
public PancakeHouseMenuIterator getIterator() {
return new PancakeHouseMenuIterator(this.menuItems);
}
//..其他方法
}
餐厅一主体代码未进行改动,只是注释了获取菜单的方法(该方法或暴露我们内部的结构
,如List),改成了获取菜单的迭代器。
public class DinnerMenu {
private final static int MAX_NUMBER_OF_ITEMS = 4;
private MenuItem[] menuItems;
private int numberOfItems = 0;
public DinnerMenu() {
menuItems = new MenuItem[MAX_NUMBER_OF_ITEMS];
addItem("pancake1", "good1", true, 10.5);
addItem("pancake2", "good2", false, 11.5);
addItem("pancake3", "good3", true, 12.5);
addItem("pancake4", "good4", false, 13.5);
}
public void addItem(String name, String description, boolean vegetarian, double prive) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, prive);
if (numberOfItems >= MAX_NUMBER_OF_ITEMS) {
throw new RuntimeException("超过最大数量");
} else {
menuItems[numberOfItems] = menuItem;
numberOfItems++;
}
}
// public MenuItem[] getMenuItems() {
// return menuItems;
// }
public DinnerMenuIterator getIterator() {
return new DinnerMenuIterator(this.menuItems);
}
//..其他方法
}
同样,餐厅二主体代码未进行改动,注释获取菜单的方法(该方法或暴露我们内部的结构
,如数组),改成了获取菜单的迭代器。
改造完成,服务员使用新接口
由于两个餐厅使用了同一个接口,因此我们可以针对接口编程,而不是针对实现编程。
public class NewWaiter {
private Iterator pancakeHouseMenuIterator;
private Iterator dinnerMenuIterator;
public NewWaiter() {
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
this.pancakeHouseMenuIterator = pancakeHouseMenu.getIterator();
DinnerMenu dinnerMenu = new DinnerMenu();
this.dinnerMenuIterator = dinnerMenu.getIterator();
}
public void printMenu() {
printItems(pancakeHouseMenuIterator);
printItems(dinnerMenuIterator);
}
public void printItems(Iterator iterator) {
while (iterator.hasNext()) {
System.out.println(iterator.next().toString());
}
}
}
使用了迭代器模式,我们的女招待是不是轻松了点呢,至少服务员不负责遍历,遍历交给了迭代器完成。
目前迭代器的类图
让服务员更简洁
两个菜单都是用了相同的接口,我们需要为菜单实现一个接口,针对接口编程。
编写菜单获取迭代器的接口:
public interface Menu {
Iterator getIterator();
}
让两个餐厅实现接口:
//实现获取迭代器的接口
public class DinnerMenu implements Menu{
private final static int MAX_NUMBER_OF_ITEMS = 4;
private MenuItem[] menuItems;
private int numberOfItems = 0;
public DinnerMenu() {
menuItems = new MenuItem[MAX_NUMBER_OF_ITEMS];
addItem("pancake1", "good1", true, 10.5);
addItem("pancake2", "good2", false, 11.5);
addItem("pancake3", "good3", true, 12.5);
addItem("pancake4", "good4", false, 13.5);
}
public void addItem(String name, String description, boolean vegetarian, double prive) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, prive);
if (numberOfItems >= MAX_NUMBER_OF_ITEMS) {
throw new RuntimeException("超过最大数量");
} else {
menuItems[numberOfItems] = menuItem;
numberOfItems++;
}
}
// public MenuItem[] getMenuItems() {
// return menuItems;
// }
@Override
public DinnerMenuIterator getIterator() {
return new DinnerMenuIterator(this.menuItems);
}
//..其他方法
}
另一个餐厅类似,接下在改造服务员,使之针对接口编程。
public class NewWaiter {
private Menu pancakeHouseMenu;
private Menu dinnerMenu;
public NewWaiter(Menu pancakeHouseMenu, Menu dinnerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinnerMenu = dinnerMenu;
}
public void printMenu() {
printItems(pancakeHouseMenu.getIterator());
printItems(dinnerMenu.getIterator());
}
public void printItems(Iterator iterator) {
while (iterator.hasNext()) {
System.out.println(iterator.next().toString());
}
}
}
这样编程的好处,减少服务员与具体菜单的依赖。
现在的类图
迭代器模式把在元素之间游走的责任交给迭代器,而不是聚合对象,这不仅让聚合的接口和实现变得简洁,也可以让聚合对象更专注在它所应该专注的事情上面(管理对象集合)。
设计原则
一个类应该只有一个引起变化的原因
。
个人理解:类应该有单一责任,负责一个单一的功能,过多的功能会引起过多的关联,超过一个责任,意味着超过一个改变的区域。