今天了解一下迭代器模式。
说到迭代器, 有点编程经验的应该都知道 Iterator..不错,这个就是迭代器。
有时候在走循环流程,我们通常会拿到容器中的迭代器,通过迭代器进行循环。
什么叫迭代器模式呢:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
这边举个小例子解释一下。
有两家餐厅要合并了,一个是中式餐馆的, 一个是港式茶点的,因为合并了,两种东西都卖,点餐的人希望同时可以点两种不同的东西。
但是有个问题,在他们的菜单中,尽管菜单的明细情况都一样,其中一个是用数组实现的,一个是却用链表实现的(这里暂且不讨论数组和链表的优劣势)。
这个是菜单的明细,包含了菜品的名字,描述和价格。
package com.chris.iterator;
public class MenuItem {
String name;
String description;
double price;
public MenuItem(String name, String description, double price) {
this.name = name;
this.description = description;
this.price = price;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public double getPrice() {
return price;
}
}
中式餐厅的菜单是用数组去保存菜单项的。
package com.chris.iterator;
public class ChineseMenu {
static final int MAX_ITEMS = 10;
int numberOfItems = 0;
MenuItem[] menuItems;
public ChineseMenu(){
menuItems = new MenuItem[MAX_ITEMS];
addItem("剁椒鱼头", "湘菜,非常美味的剁椒鱼头", 50);
addItem("回锅肉", "特别好吃", 30);
addItem("干锅花菜", "用干锅弄的花菜,十分美味", 24);
addItem("东坡肉", "苏东坡的肉?", 42);
}
public void addItem(String name, String description, double price){
MenuItem menuItem = new MenuItem(name, description, price);
if(numberOfItems >= MAX_ITEMS){
System.err.println("sorry, menu is full! Can't add item to menu");
}else{
menuItems[numberOfItems] = menuItem;
numberOfItems++;
}
}
public MenuItem[] getMenuItems(){
return menuItems;
}
}
而港式茶餐厅的菜单时用链表去保存菜单的。
package com.chris.iterator;
import java.util.ArrayList;
public class HongKongMenu {
ArrayList menuItems;
public HongKongMenu(){
menuItems = new ArrayList();
addItem("水晶虾饺", "里面有虾,很好吃", 18);
addItem("豉汁蒸排骨", "要等一段时间,但是很美味", 20);
addItem("流沙包", "甜甜的,超好吃", 10);
addItem("叉烧包", "广东特色,不错", 12);
}
public void addItem(String name, String description, double price){
MenuItem menuItem = new MenuItem(name, description, price);
menuItems.add(menuItem);
}
public ArrayList getMenuItems(){
return menuItems;
}
}
看起来好像没有什么问题,但是每次服务员在展示菜单时,需要先创建两个菜单,再获取里面的菜单,
由于菜单的容器不同,需要 再分别通过两个循环进行展示,打印出菜单的情况(如果再合并一个餐厅,就要三个循环。。)
基于两边菜单都不愿意改变自身的实现,可能由于其他关联太多(实际开发中这种情况也是会有的)。
于是,我们开始考虑其他的解决方案,封装遍历!!
我们只需要封装变化的部分,而这里面的变化,就是两种容器遍历方法和获取元素的方法不同。
链表是通过.size() 和get(index), 数组是通过.length 和 [index],这导致他们的循环不能统一。
我们开始引入迭代器模式,创建迭代器的接口,简单的不能再简单了,只有两个方法,获取下一个元素,判断是否有下一个元素。
package com.chris.iterator;
public interface Iterator {
boolean hasNext();
Object next();
}
我们为中式餐厅做一个迭代器,为他的菜单服务,实现了上面接口的两个方法。
package com.chris.iterator;
public class ChineseMenuIterator implements Iterator{
MenuItem[] items;
int position = 0;
public ChineseMenuIterator(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 ++;
return menuItem;
}
}
同样的, 也为港式茶餐厅做一个迭代器,为他们的菜单服务。
package com.chris.iterator;
import java.util.ArrayList;
public class HongKongMenuIterator implements Iterator {
ArrayList menuItems;
int position = 0;
public HongKongMenuIterator(ArrayList menuItems) {
this.menuItems = menuItems;
}
@Override
public boolean hasNext() {
if (position >= menuItems.size() || menuItems.get(position) == null) {
return false;
} else {
return true;
}
}
@Override
public Object next() {
MenuItem menuItem = (MenuItem) menuItems.get(position);
position++;
return menuItem;
}
}
然后在各自的菜单中,把获取菜单的方法删除, 分别增加一个方法,返回各自的迭代器,这样就OK了。
然后,服务员报菜单就方便多啦,不需要几次循环了,只需要通过统一的遍历将元素循环打印出来,代码瞬间变得优雅起来。
package com.chris.iterator;
public class Waitress {
ChineseMenu chineseMenu;
HongKongMenu hongKongMenu;
public Waitress(ChineseMenu chineseMenu, HongKongMenu hongKongMenu){
this.chineseMenu = chineseMenu;
this.hongKongMenu = hongKongMenu;
}
public void printMenu(){
Iterator chineseMenuIterator = chineseMenu.createIterator();
Iterator hongKongMenuIterator = hongKongMenu.createIterator();
System.out.println("Chinese food:");
printMenu(chineseMenuIterator);
System.out.println("HongKong food:");
printMenu(hongKongMenuIterator);
}
private void printMenu(Iterator iterator){
while(iterator.hasNext()){
MenuItem menuItem = (MenuItem) iterator.next();
System.out.print(menuItem.getName() + "-");
System.out.print(menuItem.getDescription() + "-");
System.out.println(menuItem.getPrice());
}
}
}
测试代码这里就不贴了,应该结果一目了然。
我们只是增加一个接口, 然后通过各自实现接口,将自己的逻辑封装起来,就可以把流程统一起来。
这里就用到面向对象的一个很重要的思想:要针对接口编程,而不要针对实现编程。
这也是大家今后在编程的时候要多考虑的地方,这样才能有足够的扩展性,而不是把代码写死。
其实,在java的基本库中就有Iterator接口,而且他相较我们自己写的接口,还多了一个remove方法。
这就是最开始提到的默认的迭代器,有很多容器是在其实现类内部维护一个自身的迭代器, 我们可以通过直接获取这个迭代器进行使用。
而没有的,如果我们需要有类似上述需求的话,也可以自己实现。
迭代器模式就到这了,如果文中有什么不妥甚至错误的地方,还望纠正,和大家共勉!!