为了更好的浏览体验,请不要在本页面禁用 Javascript
2020-05-16
 Tags: 

Iterator模式

目的
提供一种对集合中的元素进行有序遍历的方式,而不关心不暴露集合的底层结构。
好处
1.
正如目的中所谈到,提供一种不关心不暴露底层数据结构的遍历方式
2.
将遍历机制和集合的结构分离,可以允许我们定义不同迭代策略的迭代器,而不是在内部的数据结构中进行遍历。
适用范围
在不暴露内部结构的情况下,对聚合对象进行迭代访问
对聚合对象的多次遍历有需求
提供用于遍历不同聚合结构的统一接口(即支持多态迭代)
模式结构
评价
对内部结构不同的聚合对象的遍历进行很好的支持
iterator简化了聚合对象遍历接口
对聚合对象的多次遍历进行很好的支持
联系
Composite模式:iterator模式常被用于composite模式的符合结构
Factory模式:多态迭代器依赖于工厂方法来实例化适当的Iterator子类。
实现
关注的问题
谁来控制集合的迭代?内部迭代器和外部迭代器
怎么定义迭代算法?
Aggregate本身定义算法——Cursor mode光标模式
iterator定义算法——iterator如何访问数据
迭代器有哪些常见操作?First,Next,hasNext,IsDone,CurrentItem
其他:实现多态迭代器,空迭代器
示例
场景
现在有两家餐厅:煎饼店和午餐饭店,它们想要合并。两家店的菜单需要统一起来,以便服务员给客户看。
但问题是:两家饭店菜单实现方式不一样,也即数据结构不一样,一家是通过数组实现的,另一家是动态数组实现的。
如果服务员为顾客提供新菜单是,得分别针对不同数据结构,在底层实现不同的迭代。
代码一
MenuItem.java
/** * 菜单项 */ public class MenuItem { String name; double price; public MenuItem() { } public MenuItem(String name, double price) { this.name = name; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } }
Java
PancakeMenu.java
import java.util.ArrayList; /** * 煎饼菜单 */ public class PancakeMenu { ArrayList<MenuItem> menuItems = new ArrayList<>(); public void addItem(String name, double price) { MenuItem menuItem = new MenuItem(name, price); menuItems.add(menuItem); } public PancakeMenu() { initItems(); } public void initItems() { addItem("葱花饼", 3.5); addItem("牛肉饼", 6); addItem("猪肉饼", 5); addItem("韭菜饼", 2.5); } }
Java
DinnerMenu.java
import java.util.Iterator; /** * 午餐菜单 */ public class DinnerMenu { MenuItem[] menuItems = new MenuItem[20]; int numberOfItems = 0; public DinnerMenu() { initItems(); } public void addItem(String name, double price) { MenuItem menuItem = new MenuItem(name, price); if (numberOfItems >= menuItems.length) { System.out.println("菜单已满,不能再添加!"); return; } menuItems[numberOfItems] = menuItem; numberOfItems++; } public void initItems() { addItem("青椒肉丝炒饭", 12); addItem("番茄鸡蛋炒饭", 10); addItem("鱼香茄子炒饭", 11); addItem("土豆烧鸡炒饭", 12.5); } }
Java
Waitress.java
import java.util.ArrayList; public class Waitress { PancakeMenu pancakeMenu = new PancakeMenu(); DinnerMenu dinnerMenu = new DinnerMenu(); /** * 打印出菜单 */ public void printMenu() { ArrayList<MenuItem> pMenuItems = pancakeMenu.menuItems; for (int i = 0; i < pMenuItems.size(); i++) { MenuItem menuItem = pMenuItems.get(i); System.out.println(menuItem.getName() + ":" + menuItem.getPrice()); } MenuItem[] dMenuItems = dinnerMenu.menuItems; for (int i = 0, n = dMenuItems.length; i < n; i++) { MenuItem menuItem = dMenuItems[i]; if (menuItem == null) break; System.out.println(menuItem.getName() + ":" + menuItem.getPrice()); } } }
Java
Main.java
public class Main { public static void main(String[] args) { var waitress=new Waitress(); waitress.printMenu(); } }
Java
分析一
代码一的问题在于:
Waitress类暴露了菜单的底层结构
扩展性差,如果新加盟了一家咖啡馆,菜单实现为HashMap,那么又得新增迭代代码
迭代部分大同小异,代码冗余。
改进
采用iterator设计模式,下图为该模式的结构:
迭代动作被封装成了一个接口Iterator,每一种聚合(也就是不同底层实现的集合),都对应了一种具体的迭代器ConcreteIterator
代码二
创建基本的菜单项类MenuItem,包含了各种菜单的共有属性和方法
package ItrUtils; /** * 菜单项 */ public class MenuItem { String name; double price; public MenuItem() { } public MenuItem(String name, double price) { this.name = name; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } }
Java
自定义迭代器接口MyIterator.java,有hasNextnext两种方法
package ItrUtils; /** * 自定义迭代器接口 * @param <E> */ public interface MyIterator<E> { public boolean hasNext(); public E next(); }
Java
实现具体的迭代器接口PancakeMenuIterator.java以及DinnerMenuIterator.java,其中实现的方法依赖于聚合对象的数据结构
package ItrUtils; import java.util.ArrayList; /** * 煎饼菜单迭代器 */ public class PancakeMenuIterator implements MyIterator<MenuItem> { ArrayList<MenuItem> menuItems; int position = 0; public PancakeMenuIterator(ArrayList<MenuItem> menuItems) { this.menuItems = menuItems; } @Override public boolean hasNext() { if (position >= menuItems.size() || menuItems.get(position) == null) return false; return true; } @Override public MenuItem next() { MenuItem item = menuItems.get(position); position += 1; return item; } }
Java
package ItrUtils; /** * 饭店菜单迭代器 */ public class DinnerMenuIterator implements MyIterator<MenuItem> { 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; return true; } @Override public MenuItem next() { MenuItem item = menuItems[position]; position += 1; return item; } }
Java
实现具体聚合(某一对象集合)类并在其中实现创建迭代器的方法,即分别创建DinnerMenuPancakeMenu
package ItrUtils; /** * 午餐菜单 */ public class DinnerMenu { MenuItem[] menuItems = new MenuItem[20]; int numberOfItems = 0; public DinnerMenu() { initItems(); } public void addItem(String name, double price) { MenuItem menuItem = new MenuItem(name, price); if (numberOfItems >= menuItems.length) { System.out.println("菜单已满,不能再添加!"); return; } menuItems[numberOfItems] = menuItem; numberOfItems++; } public MyIterator<MenuItem> createIterator() { return new DinnerMenuIterator(menuItems); } public void initItems() { addItem("青椒肉丝炒饭", 12); addItem("番茄鸡蛋炒饭", 10); addItem("鱼香茄子炒饭", 11); addItem("土豆烧鸡炒饭", 12.5); } }
Java
package ItrUtils; import java.util.ArrayList; /** * 煎饼菜单 */ public class PancakeMenu { ArrayList<MenuItem> menuItems = new ArrayList<>(); public void addItem(String name, double price) { MenuItem menuItem = new MenuItem(name, price); menuItems.add(menuItem); } public PancakeMenu() { initItems(); } public void initItems() { addItem("葱花饼", 3.5); addItem("牛肉饼", 6); addItem("猪肉饼", 5); addItem("韭菜饼", 2.5); } /** * 创建自己的迭代器 */ public MyIterator<MenuItem> createIterator() { return new PancakeMenuIterator(menuItems); } }
Java
创建服务员类Waitress,现在的服务员,不需要清楚菜单的具体设计,打印总菜单时,只需要调用各自菜单的迭代器即可。而且即使扩展增加新的菜单时,也更加容易
package ItrUtils; /** * 服务员类 */ public class Waitress { PancakeMenu pancakeMenu = new PancakeMenu(); DinnerMenu dinnerMenu = new DinnerMenu(); /** * 打印出菜单 */ public void printMenu() { MyIterator<MenuItem> myIterator = pancakeMenu.createIterator(); printMenu(myIterator); myIterator = dinnerMenu.createIterator(); printMenu(myIterator); } private void printMenu(MyIterator<MenuItem> myIterator) { while (myIterator.hasNext()) { MenuItem menuItem = myIterator.next(); System.out.println(menuItem.getName() + ":" + menuItem.getPrice()); } } }
Java
main函数中运行
import ItrUtils.Waitress; public class Main2TestItreator { public static void main(String []args) { Waitress waitress=new Waitress(); System.out.println("\niterator模式:\n"); waitress.printMenu(); } }
Java
结果:
iterator模式: 葱花饼:3.5 牛肉饼:6.0 猪肉饼:5.0 韭菜饼:2.5 青椒肉丝炒饭:12.0 番茄鸡蛋炒饭:10.0 鱼香茄子炒饭:11.0 土豆烧鸡炒饭:12.5
Java
分析二
通过上面的代码,可看到,迭代器模式能够不暴露聚合内部的具体实现,而且也让聚合任务减轻,把“游走”的任务放在了迭代器上,简化了聚合的接口和实现,也让任务各得其所
加粗部分就是单一责任原则(一个类应该只有一个引起变化的原因)的体现。
之所以我们要让一个类只有一个改变的原因,在于:我们需要避免类的改变,而类的责任越多时,它改变的机率就越大。而且,当类真的改变时,两个责任的代码都可能受到影响。
参考文章:
设计模式01--Iterator模式_Java_脚踏实地,慢慢来-CSDN博客
定义 迭代器( Iterator )模式,从其英文单词可以看出,有反复做某件事的意思。迭代器模式常用于数据集合中,对数据集合中的数据按照顺序进行遍历。它能提供一种方法访问一个容器对象中各个元素,而又不暴露该对象的内部细节。 问题引入 假设我们有一个书架,假设书架可以按照顺序放置无数本书,现在有一个需求,那就是遍历书架里面的所有的书籍,将书籍的名称打印出来。 常规的解决办法 一般情况下,我们在用代码来解决该问题的时候,都是想到使用 for循环,对书架中的每一本书进行循环遍历,然后输出名称。这是一种比较常规的做法,考虑的问题就是为了遍历书架中的书籍,并使用变量 i 来记录当前遍历到哪一本书了,以及将指针指向下一本书,伪代码如下: 这里的 for语句中的 i++的作用是将 i的值在每一次循环之后就自增 1,使得在遍历的过程中就可以访问集合中的下一本书籍,下下一本书籍,再下下一本书籍,直到遍历完所有的书籍。如果我们考虑将 i 的作用进行抽象化,使其通用化,那么就可以形成迭代器模式。 从List中获取启发 说到迭代器设计模式,大家肯定会想到 java.util包中的集合,是的, Java中集合类中运用到了迭代器设计模式,那么,我们在学习迭代器设计模式的时候,完全可以通过去学习集合的源码来学习迭代器设计模式,下面,我将从 ArrayList出发,探究一下在 ArrayList中如何运用的迭代器设计模式。 作为集合类的顶级接口, Collection接口继承了 Iterable接口, Iterable接口的子接口或者实现类都具备迭代和遍历的功能,那么List接口继承自 Collection,自然也是具备基本的迭代功能的,那么我们从 List出发,来探究迭代器模式的运用。 List接口中有一个重写自 Collection的 iterator()方法,它的返回值是一个 Iterator接口的实现类对象。 那么我们接着看 ArrayList类,它实现了 List接口,也实现了 List接口中的 iterator()方法。 那么,针对实例化 ArrayList的时候传入的具体的泛型,可以生成其对应的迭代器,也就是说,上图中 new语句后面的 Itr()创造出来的迭代器对象肯定是针对传入的泛型的迭代器对象。我们继续阅读代码,发现 Itr是 ArrayList的私有内部类,它还实现了 Iterator接口,也实现了基本的 hasNext()方法和 next()方法。 因为 Itr是 ArrayList的内部类,那么 Itr可以操作 ArrayList的成员变量,而 ArrayList作为集合,它内部的元素是存储在变量名为 elementData的 Object数组中的,整个遍历的过程就是针对这个数组进行遍历的。 分析完源码,我们也许还是有些迷糊,那么我们需要借助UML类图来描述这些接口或者类之间的关系了。 从上图可以看出, List接口中有创建 Iterator接口的实现类对象的抽象方法,而 ArrayList实现了 List接口,而 Itr类实现了 Iterator接口,且其拥有 ArrayList的元素数组,可以针对该数组进行一系列的操作,比如遍历。类图可以很清晰表表现出类与类之间的关系,不太熟悉的可以去学习一下 UML 类图。 手动实现迭代器设计模式 从 List中获取到迭代器设计模式实现的灵感,那么我们需要自己手动实现自己的迭代器模式,来解决文章开始引入的遍历书架的问题。首先,我们根据 List中采用的迭代器设计模式,我们需要一个集合接口 Aggregate(有"使聚集"、"聚合"的意思),该集合接口中有创建迭代器接口 Iterator实现类对象的抽象方法 iterator,这个迭代器接口有 hasNext、 next方法,用来判断集合是否还有元素以及获取集合元素,我们还需要具体的集合实现类,也就是具体的书架,用来承载书籍,该实现类实现了 Aggregate集合的 iterator方法,最后,我们还需要实现迭代器接口的具体类,它持有集合的具体的实现类,还实现了 hasNext和 next方法。我们将类图设计如下所示: 根据类图,我们可以很轻松地设计出基本的迭代器设计模式及的代码,主要代码如下所示: 该接口遵循类图的设计,接口中包含创建 Iterator 接口实现类对象的方法。 该迭代器接口有两个方法,分别是判断被遍历的集合是否还有元素以及获取集合中元素并将指针指向下一个元素。 getBookAt方法可以获取指定索引位置的书籍, appendBook方法是向书架中最后一个位置添加书籍的方法, getLength方法可以得到书籍的数量, iterator ...
https://blog.csdn.net/Lammonpeter/article/details/82142020
Modified based on gine-blog