1 概念
定义
类型
迭代器模式(Iterator Pattern)
目前已经是一个没落的模式, 基本上没人会单独写一个迭代器, 除非是产品性质的开发, 其定义如下:
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.(它提供一种方法访问一个容器对象中各个元素, 而又不需暴露该对象的内部细节.)
迭代器是为容器服务的, 那什么是容器呢? 能容纳对象的所有类型都可以称之为容器, 例如 Collection 集合类型, Set 类型等, 迭代器模式就是为解决遍历这些容器中的元素而诞生的
迭代器模式的通用类图
迭代器模式提供了遍历容器的方便性, 容器只要管理增减元素就可以了, 需要遍历时交由迭代器进行
看看迭代器模式中的各个角色:
Iterator 抽象迭代器
抽象迭代器负责定义访问和遍历元素的接口, 而且基本上是有固定的 3 个方法:
first()获得第一个元素
next()访问下一个元素
hasNext()是否已经访问到底部
ConcreteIterator 具体迭代器
具体迭代器角色要实现迭代器接口, 完成容器元素的遍历.
Aggregate 抽象容器
容器角色负责提供创建具体迭代器角色的接口, 必然提供一个类似 createIterator()这样的方法, 在 Java 中一般是 iterator()方法.
Concrete Aggregate 具体容器
具体容器实现容器接口定义的方法, 创建出容纳迭代器的对象.
我们来看迭代器模式的通用源代码
抽象迭代器
- public interface Iterator<E> {
- boolean hasNext();
- E next();
- default void remove() {
- throw new UnsupportedOperationException("remove");
- }
- /**
- * Performs the given action for each remaining element until all elements
- * have been processed or the action throws an exception. Actions are
- * performed in the order of iteration, if that order is specified.
- * Exceptions thrown by the action are relayed to the caller.
- *
- * @implSpec
- * <p>The default implementation behaves as if:
- * <pre>{@code
- * while (hasNext())
- * action.accept(next());
- * }</pre>
- *
- * @param action The action to be performed for each element
- * @throws NullPointerException if the specified action is null
- * @since 1.8
- */
- default void forEachRemaining(Consumer<? super E> action) {
- Objects.requireNonNull(action);
- while (hasNext())
- action.accept(next());
- }
- }
具体迭代器
- public class ConcreteIterator implements Iterator {
- private Vector vector = new Vector();
- // 定义当前游标
- public int cursor = 0;
- @SuppressWarnings("unchecked")
- public ConcreteIterator(Vector _vector){
- this.vector = _vector;
- }
- // 判断是否到达尾部
- public boolean hasNext() {
- if(this.cursor == this.vector.size()){
- return false;
- }else{
- return true;
- }
- }
- // 返回下一个元素
- public Object next() {
- Object result = null;
- if(this.hasNext()){
- result = this.vector.get(this.cursor++);
- }else{
- result = null;
- }
- return result;
- }
- // 删除当前元素
- public boolean remove() {
- this.vector.remove(this.cursor);
- return true;
- }
- }
开发系统时, 迭代器的删除方法应该完成两个逻辑
删除当前元素
当前游标指向下一个元素
抽象容器
- public interface Aggregate {
- // 是容器必然有元素的增加
- public void add(Object object);
- // 减少元素
- public void remove(Object object);
- // 由迭代器来遍历所有的元素
- public Iterator iterator();
- }
具体容器
- public class ConcreteAggregate implements Aggregate {
- // 容纳对象的容器
- private Vector vector = new Vector();
- // 增加一个元素
- public void add(Object object) {
- this.vector.add(object);
- }
- // 返回迭代器对象
- public Iterator iterator() {
- return new ConcreteIterator(this.vector);
- }
- // 删除一个元素
- public void remove(Object object) {
- this.remove(object);
- }
- }
场景类
- public class Client {
- public static void main(String[] args) {
- // 声明出容器
- Aggregate agg = new ConcreteAggregate();
- // 产生对象数据放进去
- agg.add("abc");
- agg.add("aaa");
- agg.add("1234");
- // 遍历一下
- Iterator iterator = agg.iterator();
- while(iterator.hasNext()){
- System.out.println(iterator.next());
- }
- }
- }
简单地说, 迭代器就类似于一个数据库中的游标, 可以在一个容器内上下翻滚, 遍历所有它需要查看的元素.
2 适用场景
我们在例子中使用了迭代器模式后为什么使原本简单的应用变得复杂起来了呢? 那是因为我们在简单的应用中使用了迭代器, 在哪?
注意这段话
for(IProject project:projectList)
它为什么能够运行起来? 还不是因为 ArrayList 已经实现了 iterator()方法, 我们才能如此简单地应用.
从 JDK 1.2 版本开始增加 java.util.Iterator 这个接口, 并逐步把 Iterator 应用到各个聚集类 (Collection) 中, 我们来看 JDK 1.5 的 API 帮助文件, 你会看到有一个叫 java.util.Iterable 的接口
看看有多少个接口继承了它:
BeanContext,BeanContextServices,BlockingQueue,Collection,List,Queue,Set,SortedSet
再看看有它多少个实现类: AbstractCollection,AbstractList,AbstractQueue,AbstractSequentialList,AbstractSet,ArrayBlockingQueue,ArrayList,AttributeList,BeanContextServicesSupport,BeanContextSupport,ConcurrentLinkedQueue,CopyOnWriteArrayList,CopyOnWriteArraySet,DelayQueue,EnumSet,HashSet,JobStateReasons,LinkedBlockingQueue,LinkedHashSet,LinkedList,PriorityBlockingQueue,PriorityQueue,RoleList,RoleUnresolvedList,Stack,SynchronousQueue,TreeSet,Vector
基本上我们经常使用的类都在这个表中了, 也正是因为 Java 把迭代器模式已经融入到基本 API 中了, 我们才能如此轻松, 便捷.
我们再来看看 Iterable 接口. java.util.Iterable 接口只有一个方法: iterator(), 也就说, 通过 iterator()这个方法去遍历聚集类中的所有方法或属性, 基本上现在所有的高级语言都有 Iterator 这个接口或者实现, Java 已经把迭代器给我们准备好了, 我们再去写迭代器, 就有点多余了. 所以呀, 这个迭代器模式也有点没落了, 基本上很少有项目再独立写迭代器了, 直接使用 Collection 下的实现类就可以完美地解决问题.
迭代器现在应用得越来越广泛了, 甚至已经成为一个最基础的工具. 一些大师级人物甚至建议把迭代器模式从 23 个模式中删除, 为什么呢? 就是因为现在它太普通了, 已经融入到各个语言和工具中了, 比如 PHP 中你能找到它的身影, Perl 也有它的存在, 甚至是前台的页面技术 Ajax 也可以有它的出现 (如在 Struts2 中就可以直接使用 iterator). 基本上, 只要你不是在使用那些古董级(指版本号) 的编程语言的话, 都不用自己动手写迭代器.
3 优点
4 缺点
相关模式
实例
现在还在开发或者维护的 103 个项目, 项目信息很乱, 很多是两年前的信息, 你能不能先把这些项目最新情况重新打印一份给我, 咱们好查查到底有什么问题
项目信息类图
简单得不能再简单的类图, 是个程序员都能实现. 我们来看看这个简单的东西
项目信息接口
- public interface IProject {
- // 从老板这里看到的就是项目信息
- public String getProjectInfo();
- }
项目信息的实现
- public class Project implements IProject {
- // 项目名称
- private String name = "";
- // 项目成员数量
- private int num = 0;
- // 项目费用
- private int cost = 0;
- // 定义一个构造函数, 把所有老板需要看到的信息存储起来
- public Project(String name,int num,int cost){
- // 赋值到类的成员变量中
- this.name = name;
- this.num = num;
- this.cost=cost;
- }
- // 得到项目的信息
- public String getProjectInfo() {
- String info = "";
- // 获得项目的名称
- info = info+ "项目名称是:" + [this.name](http://this.name/);
- // 获得项目人数
- info = info + "\t 项目人数:"+ this.num;
- // 项目费用
- info = info+ "\t 项目费用:"+ this.cost;
- return info;
- }
- }
通过构造函数把要显示的数据传递过来, 然后放到 getProjectInfo 中显示
报表的场景
- public class Boss {
- public static void main(String[] args) {
- // 定义一个 List, 存放所有的项目对象
- ArrayList projectList = new ArrayList();
- // 增加星球大战项目
- projectList.add(new Project("星球大战项目",10,100000));
- // 增加扭转时空项目
- projectList.add(new Project("扭转时空项目",100,10000000));
- // 增加超人改造项目
- projectList.add(new Project("超人改造项目",10000,1000000000));
- // 这边 100 个项目
- for(int i=4;i<104;i++){
- projectList.add(new Project("第"+i+"个项目",i*5,i*1000000));
- }
- // 遍历一下 ArrayList, 把所有的数据都取出
- for(IProject project:projectList){
- System.out.println(project.getProjectInfo());
- }
- }
- }
然后看一下我们的运行结果, 如下所示:
又看了一遍程序, 应该还有另外一种实现方式, 因为是遍历嘛, 让我想到的就是 Java 的迭代器接口 java.util.iterator, 它的作用就是遍历 Collection 集合下的元素, 那我们的程序还可以有另外一种实现, 通过实现 iterator 接口来实现遍历
增加迭代接口的类图
看着是不是复杂了很多? 是的, 是有点复杂了, 是不是我们把简单的事情复杂化了?
我们先分析一下我们的类图 java.util.Iterator 接口中声明了三个方法, 这是 JDK 定义的, ProjectIterator 实现该接口, 并且聚合了 Project 对象, 也就是把 Project 对象作为本对象的成员变量使用. 看类图还不是很清晰, 我们一起看一下代码, 先看 IProject 接口的改变
项目信息接口
- public interface IProject {
- // 增加项目
- public void add(String name,int num,int cost);
- // 从老板这里看到的就是项目信息
- public String getProjectInfo();
- // 获得一个可以被遍历的对象
- public IProjectIterator iterator();
- }
这里多了两个方法, 一个是 add 方法, 这个方法是增加项目, 也就是说产生了一个对象后, 直接使用 add 方法增加项目信息. 我们再来看其实现类
项目信息
- public class Project implements IProject {
- // 定义一个项目列表, 说有的项目都放在这里
- private ArrayList projectList = new ArrayList();
- // 项目名称
- private String name = "";
- // 项目成员数量
- private int num = 0;
- // 项目费用
- private int cost = 0;
- public Project(){
- }
- // 定义一个构造函数, 把所有老板需要看到的信息存储起来
- private Project(String name,int num,int cost){
- // 赋值到类的成员变量中
- [this.name](http://this.name/) = name;
- this.num = num;
- this.cost=cost;
- }
- // 增加项目
- public void add(String name,int num,int cost){
- this.projectList.add(new Project(name,num,cost));
- }
- // 得到项目的信息
- public String getProjectInfo() {
- String info = "";
- // 获得项目的名称
- info = info+ "项目名称是:" + [this.name](http://this.name/);
- // 获得项目人数
- info = info + "\t 项目人数:"+ this.num;
- // 项目费用
- info = info+ "\t 项目费用:"+ this.cost;
- return info;
- }
- // 产生一个遍历对象
- public IProjectIterator iterator(){
- return new ProjectIterator(this.projectList);
- }
- }
通过构造函数, 传递了一个项目所必需的信息, 然后通过 iterator()方法, 把所有项目都返回到一个迭代器中. Iterator()方法看不懂不要紧, 继续向下阅读. 再看 IProjectIterator 接口
项目迭代器接口
- public interface IProjectIterator extends Iterator {
- }
大家可能对该接口感觉很奇怪, 你定义的这个接口方法, 变量都没有, 有什么意义呢? 有意义, 所有的 Java 书上都会说要面向接口编程, 你的接口是对一个事物的描述, 也就是说我通过接口就知道这个事物有哪些方法, 哪些属性, 我们这里的 IProjectIterator 是要建立一个指向 Project 类的迭代器, 目前暂时定义的就是一个通用的迭代器, 可能以后会增加 IProjectIterator 的一些属性或者方法. 当然了, 你也可以在实现类上实现两个接口, 一个是 Iterator, 一个是 IProjectIterator(这时候, 这个接口就不用继承 Iterator), 杀猪杀尾巴, 各有各的杀法.
如果我要实现一个容器或者其他 API 提供接口时, 我一般都自己先写一个接口继承, 然后再继承自己写的接口, 保证自己的实现类只用实现自己写的接口(接口传递, 当然也要实现顶层的接口)
我们继续看迭代器的实现类
项目迭代器
- public class ProjectIterator implements IProjectIterator {
- // 所有的项目都放在 ArrayList 中
- private ArrayList projectList = new ArrayList();
- private int currentItem = 0;
- // 构造函数传入 projectList
- public ProjectIterator(ArrayList projectList){
- this.projectList = projectList;
- }
- // 判断是否还有元素, 必须实现
- public boolean hasNext() {
- // 定义一个返回值
- boolean b = true;
- if(this.currentItem>=projectList.size()||this.projectList.get(this.currentItem)==null){
- b =false;
- }
- return b;
- }
- // 取得下一个值
- public IProject next() {
- return (IProject)this.projectList.get(this.currentItem++);
- }
- // 删除一个对象
- public void remove() {
- // 暂时没有使用到
- }
- }
细心的读者可能会从代码中发现一个问题, java.util.iterator 接口中定义 next()方法的返回值类型是 E, 而你在 ProjectIterator 中返回值却是 IProject,E 和 IProject 有什么关系?
E 是 JDK 1.5 中定义的新类型: 元素(Element), 是一个泛型符号, 表示一个类型, 具体什么类型是在实现或运行时决定, 总之它代表的是一种类型, 你在这个实现类中把它定义为 ProjectIterator, 在另外一个实现类可以把它定义为 String, 都没有问题. 它与 Object 这个类可是不同的, Object 是所有类的父类, 随便一个类你都可以把它向上转型到 Object 类, 也只是因为它是所有类的父类, 它才是一个通用类, 而 E 是一个符号, 代表所有的类, 当然也代表 Object 了.
都写完毕了, 看看我们的 Boss 类有多少改动
老板看报表
- public class Boss {
- public static void main(String[] args) {
- // 定义一个 List, 存放所有的项目对象
- IProject project = new Project();
- // 增加星球大战项目
- project.add("星球大战项目 ddddd",10,100000);
- // 增加扭转时空项目
- project.add("扭转时空项目",100,10000000);
- // 增加超人改造项目
- project.add("超人改造项目",10000,1000000000);
- // 这边 100 个项目
- for(int i=4;i<104;i++){
- project.add("第"+i+"个项目",i*5,i*1000000);
- }
- // 遍历一下 ArrayList, 把所有的数据都取出
- IProjectIterator projectIterator = project.iterator();
- while(projectIterator.hasNext()){
- IProject p = (IProject)projectIterator.next();
- System.out.println(p.getProjectInfo());
- }
- }
- }
运行结果如下所示
最佳实践
如果你是做 Java 开发, 尽量不要自己写迭代器模式! 省省吧, 使用 Java 提供的 Iterator 一般就能满足你的要求了
来源: http://www.jianshu.com/p/d2472c78748d