迭代器模式(Iterator)
 
走遍天下,世界那么大,我想去看看
 
在计算机中,Iterator意为迭代器,迭代有重复的含义,在程序中,更有“遍历”的含义
如果给定一个数组,我们可以通过for循环来遍历这个数组,这种遍历就叫做迭代
对于数组这种数据结构,我们称为是可迭代的
所以
迭代器就是可以用来对于一个数据集合进行遍历的对象

意图

提供一种方法,顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
别名:游标 Cursor

集合与遍历

由一个或多个确定的元素所构成的整体叫做集合
多个对象聚集在一起形成的总体称之为聚集aggregate。
集合和聚集有相似的含义。
容器是指盛物品的器具,Java的Collection框架,就是设计用来保存对象的容器
容器可以认为是集合、聚集的具体体现形式,三个元素在一起叫做集合(聚集),怎么在一起?数组,列表?这具体的体现形式就是容器
 
容器必须提供内部对象的访问方式,如果不能获取对象,容器也失去了存在的意义,一个只能进不能出的储蓄罐你要它何用?
因为容器的存在就是为了更方便的使用、管理对象。
而且通常需要容器提供对于内部所有元素的遍历方法
 
然而容器其内部有不同的摆放形式,可顺序,可无序堆集
简言之,就是不同类型的容器必然有不同的内部数据结构
那么,一种解决办法就是不同的容器各自提供自己的遍历方法
这样的话,对于使用容器管理对象的客户端程序来说:
如果迭代的逻辑,也就是业务逻辑没有变化
当需要更换为另外的集合时,就需要同时更换这个迭代方法
考虑这样一个场景
有一个方法,方法的参数类型为 Collection
他的迭代逻辑,也就是业务逻辑为遍历所有元素,读取每个元素的信息,并且进行打印...
如果不同的容器有不同的遍历方法,也就是一种实现类有一种不同的遍历方法
一旦更换实现类,那么就需要同步更换掉这个迭代方法,否则方法将无法通过编译
 
如果集合的实现不变,需要改变业务逻辑,也就是迭代的逻辑
那么就需要修改容器类的迭代方法,也就是修改原来的遍历方法
 
还是上面的场景
有一个方法,方法的参数类型为 Collection
他的迭代逻辑,也就是业务逻辑为遍历所有元素,读取每个元素的信息,并且进行打印...
现在他的实现类无需变化
但是业务逻辑需要变动,比如希望从后往前的方式进行遍历,而不再是从前往后
就需要修改原来的方法或者重新写一个方法
 
 
出现上述问题的根本原因就在于元素的迭代逻辑与容器本身耦合在一起
当迭代逻辑或者集合实现发生变更时,需要进行修改,不符合开闭原则
容器自身不仅仅需要存储管理对象,还要负责对象的遍历访问,不符合单一职责原则
 
存储管理对象是容器的核心职责,虽然经常需要提供遍历方法,但是他并不是核心职责
但是为了提供遍历元素的方法,可能不得不在容器类内提供各种全局变量,比如保存当前的位置等,这无疑也会导致容器聚集类设计的复杂度

结构

抽象迭代器角色Iterator
定义遍历元素所需要的接口
具体的迭代器ConcreteIterator
实现了Iterator接口,并且跟踪当前位置
抽象集合容器角色Aggregate
定义创建相应迭代器的接口(方法)
就是一个容器类,并且定义了一个返回迭代器的方法
具体的容器角色ConcreteAggregate
Aggregate的子类,并且实现了创建Iterator对象的接口,也就是返回一个ConcreteIterator实例
客户端角色Client
持有容器对象以及迭代器对象的引用,调用迭代对象的迭代方法遍历元素
 
迭代器模式中,通过一个外部的迭代器来对容器集合对象进行遍历。
迭代器定义了遍历访问元素的协议方式。
容器集合对象提供创建迭代器的方法。

示例代码

Aggregate角色
提供了iterator()获取Iterator
package iterator;
public abstract class Aggregate {
abstract Iterator iterator();
abstract Object get(int index);
abstract int size();
}
ConcreateAggregate角色
内部使用一个Object数组,数组直接通过构造方法传递进去(只是为了演示学习模式,不要纠结这算不上一个容器)
提供了大小的获取方法以及获取指定下标元素的方法
尤其是实现了iterator()方法,创建一个ConcreteIterator实例,将当前ConcreteAggregate作为参数传递给他的构造方法
package iterator;
public class ConcreateAggregate extends Aggregate { private Object[] objects; ConcreateAggregate(Object[] objects) {
this.objects = objects;
} @Override
Iterator iterator() {
return new ConcreateIterator(this);
} @Override
Object get(int index) {
return objects[index];
} @Override
int size() {
return objects.length;
}
}
迭代器接口
一个是否还有元素的方法,一个获取下一个元素的方法
package iterator;
public interface Iterator {
boolean hasNext();
Object next();
}
具体的迭代器
内部维护了数据的大小和当前位置
如果下标未到最后,那么就是还有元素
next()方法用于获取当前元素,获取后当前位置往后移动一下
package iterator;
public class ConcreateIterator implements Iterator {
private Aggregate aggregate;
private int index = 0;
private int size = 0; ConcreateIterator(Aggregate aggregate) {
this.aggregate = aggregate;
size = aggregate.size();
} @Override
public boolean hasNext() {
return index < size ? true : false;
} @Override
public Object next() {
Object value = aggregate.get(index);
index++;
return value;
}
}
测试类
package iterator;
public class Client {
public static void main(String[] args) {
Object[] objects = {"1", 2, 3, 4, 5};
Aggregate aggregate = new ConcreateAggregate(objects);
Iterator iterator = aggregate.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
示例代码中ConcreateAggregate本身提供了获取指定下标元素的方法,可以直接调用获取元素
借助于Iterator,将迭代逻辑从Aggregate中剥离出来,独立封装实现
在客户端与容器之间,增加了一层Iterator,实现了客户端程序与容器的解耦
说白了,增加了Iterator,相当于通过Iterator封装了真实容器对象的获取元素的方法
不直接调用方法,经过Iterator转换一层
 
而且仔细品味下,这有点“适配”的韵味,适配的目标就是统一的元素访问协议,通过Iterator约定
而被适配的角色,则是真实容器对象元素的操作方法
总之“间接”“委托”“代理”的感觉,对吧,好处自己品味

外部迭代与内部迭代

在上面的示例程序中,通过引入Iterator,实现了迭代逻辑的封装抽象 
但是容器聚集对象本身有获取元素的方法,所以客户端仍旧可以自行遍历
Iterator也只不过是容器聚集对象的一个客户而已
这种迭代器也叫做外部迭代器
对于外部迭代器有一个问题,对于不同的ConcreteAggregate,可能都需要一个不同的ConcreteIterator
也就是很可能会不得不创建了一个与Aggregate等级结构平行的Iterator结构,出现了很多的ConcreteIterator类
这势必会增加维护成本
而且,虽然迭代器将客户端的访问与容器进行解耦,但是迭代器却是必须依赖容器对象
也就是迭代器类ConcreteIterator与ConcreteAggregate必须进行通信,会增加设计的复杂度,而且这也会增加类之间的耦合性
 
另外的一种方法是使用内部类的形式,也就是将ConcreteIterator的实现,移入到ConcreteAggregate的内部
借助于内部类的优势:对外部类有充足的访问权限,也就是无需担心为了通信要增加复杂度的问题
准确的说,你没有任何的通信成本,内部类可以直接读取外部类的属性数据信息
而且,使用内部类的方式不会导致类的爆炸(尽管仍旧是会有另一个class文件,但是从代码维护的角度看算是一个类)
这种形式可以叫做内部迭代器
 
不过无论哪种方式,你可以看得出来,使用迭代器的客户端代码,都是一样的
借助于工厂方法iterator()获得一个迭代器实例(简单工厂模式)
然后借助于迭代器进行元素遍历

JDK中的迭代

我们看下JDK中的Collection提供给我们的迭代方式
Collection是所有集合的父类,Collection实现了Iterable接口
Iterable接口提供了iterator()方法用于返回一个Iterator类的一个实例对象
Iterator类提供了对元素的遍历方法
接下来看下ArrayList的实现
ArrayList中iterator()返回了一个Itr对象,而这个对象是ArrayList的内部类,实现了Iterator接口
看得出来,java给集合框架内置了迭代器模式
在ArrayList中使用就是内部类的形式,也就是内部迭代器
boolean hasNext()
是否拥有更多元素,换句话说,如果next()方法不会抛出异常,就会返回true
next();
返回下一个元素
remove()
删除元素
 
有几点需要注意
1.)初始时,可以认为“当前位置”为第一个元素前面
所以next()获取第一个元素
2.)根据第一点,初始的当前位置”为第一个元素前面,所以如果想要删除第一个元素的话,必须先next,然后remove
Iterator iterator = list.iterator();

iterator.next();

iterator.remove();
否则,会抛出异常
3.)不仅仅是删除第一个元素需要先next,然后才能remove,每一个remove,前面必须有一个next,成对出现
所以remove是删除当前元素
如果下面这样,会抛出异常
iterator.next();

iterator.remove();

iterator.remove();
4.)迭代器只能遍历一次,如果需要重新遍历,可以重新获取迭代器对象
如果已经遍历到尾部之后仍旧继续使用,将会抛出异常
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
iterator.next();
}
iterator.next();

总结

在java中万事万物都是对象
前面的命令模式将请求转换为命令对象
解释器模式中,将语法规则转换为终结符表达式和非终结符表达式
迭代器模式中,将“遍历元素”转换为对象
 
通过迭代器模式引入迭代器,将遍历逻辑功能从容器聚集对象中分离出来
聚合对象本身只负责数据存储,遍历的职责交给了迭代器
 
对于同一个容器对象,可以定义多种迭代器,也就是可以定义多种遍历方式
如果需要使用另外的迭代方式,仅仅需要更改迭代器对象即可
这样你甚至可以把ConcreteIterator使用配置文件进行注入,灵活设置 
 
将迭代遍历的逻辑从容器对象中分离,必然会减少容器类的复杂程度
 
当增加新的容器类或者迭代器类时,不需要修改原有的代码,符合开闭原则
 
如果你想要将容器聚集对象的遍历逻辑从容器对象中的分离
或者想要提供多种不同形式的遍历方式时,或者你想为不同的容器对象提供一致性的遍历接口逻辑
你就应该考虑迭代器模式了
迭代器模式的应用是如此广泛,以至于java已经将他内置到集合框架中了
所以对于我们自己来说,多数时候可以认为迭代器模式几乎用不到了
因为绝大多数时候,使用框架提供的应该就足够了
在java实现中,迭代器模式的比较好的做法就是Java集合框架使用的这种形式---内部类形式的内部迭代器,如果真的需要自己搞一个迭代器,建议仿照集合框架搞吧
 
借助于迭代器模式,如果迭代的逻辑不变,更换另外的集合实现,因为实现了共同的迭代器接口,所以不需要对迭代这块,无需做任何变动
如果需要改变迭代逻辑,必须增加新的迭代形式,只需要增加一个新的内部类实现迭代器接口即可,其他使用的地方只需要做很小的调整
ArrayList中的ListIterator<E> listIterator() 方法就是如此
有人觉得增加一个类和一个方法这不也是修改么?个人认为:开闭原则尽管最高境界是完全的对扩展开放对修改关闭,但是也不能死抠字眼
增加了一个新的获取迭代对象的方法以及一个新的类,总比将原有的源代码中添加新的方法那种修改要强得多,所有的遍历逻辑都封装在新的迭代器实现类中,某种程度上可以认为并没有“修改源代码”
使用内部类的形式,有人觉得不还是在一个文件中么?但是内部类会有单独的class文件,而且,内部类就像一道墙,分割了内外,所有的逻辑被封装在迭代器实现类中
不需要影响容器自身的设计实现,所以也是符合单一职责原则的。

迭代器模式 Iterator 行为型 设计模式(二十)的更多相关文章

  1. 解释器模式 Interpreter 行为型 设计模式(十九)

      解释器模式(Interpreter)   考虑上图中计算器的例子 设计可以用于计算加减运算(简单起见,省略乘除),你会怎么做?    你可能会定义一个工具类,工具类中有N多静态方法 比如定义了两个 ...

  2. 命令模式 Command 行为型 设计模式(十八)

    命令模式(Command) 请分析上图中这条命令的涉及到的角色以及执行过程,一种可能的理解方式是这样子的: 涉及角色为:大狗子和大狗子他妈 过程为:大狗子他妈角色 调用 大狗子的“回家吃饭”方法 引子 ...

  3. 设计模式 ( 十四 ) 迭代器模式Iterator(对象行为型)

      设计模式 ( 十四 ) 迭代器模式Iterator(对象行为型) 1.概述 类中的面向对象编程封装应用逻辑.类,就是实例化的对象,每个单独的对象都有一个特定的身份和状态.单独的对象是一种组织代码的 ...

  4. 二十四种设计模式:迭代器模式(Iterator Pattern)

    迭代器模式(Iterator Pattern) 介绍提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示. 示例有一个Message实体类,某聚合对象内的各个元素均为该实体对象,现 ...

  5. 设计模式 ( 二十 ) 访问者模式Visitor(对象行为型)

    设计模式 ( 二十 ) 访问者模式Visitor(对象行为型) 1.概述 在软件开发过程中,对于系统中的某些对象,它们存储在同一个集合collection中,且具有不同的类型,而且对于该集合中的对象, ...

  6. 设计模式(十):从电影院中认识"迭代器模式"(Iterator Pattern)

    上篇博客我们从醋溜土豆丝与清炒苦瓜中认识了“模板方法模式”,那么在今天这篇博客中我们要从电影院中来认识"迭代器模式"(Iterator Pattern).“迭代器模式”顾名思义就是 ...

  7. Java 设计模式系列(十五)迭代器模式(Iterator)

    Java 设计模式系列(十五)迭代器模式(Iterator) 迭代器模式又叫游标(Cursor)模式,是对象的行为模式.迭代子模式可以顺序地访问一个聚集中的元素而不必暴露聚集的内部表象(interna ...

  8. 设计模式17:Iterator 迭代器模式(行为型模式)

    Iterator 迭代器模式(行为型模式) 动机(Motivation) 在软件构建过程中,集合对象内部结构常常变化各异.但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码可以透 ...

  9. 访问者模式 Visitor 行为型 设计模式(二十七)

    访问者模式 Visitor    <侠客行>是当代作家金庸创作的长篇武侠小说,新版电视剧<侠客行>中,开篇有一段独白:  “茫茫海外,传说有座侠客岛,岛上赏善罚恶二使,每隔十年 ...

随机推荐

  1. MySQL · 引擎特性 · 临时表那些事儿

    前言 相比于普通的用户数据表,MySQL/InnoDB中的临时表,大家应该会陌生很多.再加上不同的临时表创建的时机和创建的位置都不固定,这也进一步加大神秘感.最让人捉摸不透的是,临时表很多时候会先创建 ...

  2. 《k8s 源码分析》- Custom Controller 之 Informer

    Custom Controller 之 Informer 概述 架构概览 reflector - List & Watch API Server Reflector 对象 ListAndWat ...

  3. AI - TensorFlow - 示例03:基本回归

    基本回归 回归(Regression):https://www.tensorflow.org/tutorials/keras/basic_regression 主要步骤:数据部分 获取数据(Get t ...

  4. ES踩坑笔记

    现在开始在业务上使用ES,记录一些踩坑经历,做点笔记. 2018-11-13 source不返回问题 使用了角色校验,客户端插入成功之后获取数据没有source,和查询参数无关. 检查mapping, ...

  5. Group Convolution分组卷积,以及Depthwise Convolution和Global Depthwise Convolution

    目录 写在前面 Convolution VS Group Convolution Group Convolution的用途 参考 博客:blog.shinelee.me | 博客园 | CSDN 写在 ...

  6. 【3y】从零单排学Redis【青铜】

    前言 只有光头才能变强 最近在学Redis,我相信只要是接触过Java开发的都会听过Redis这么一个技术.面试也是非常高频的一个知识点,之前一直都是处于了解阶段.秋招过后这段时间是没有什么压力的,所 ...

  7. 程序员如何巧用Excel提高工作效率 第二篇

    之前写了一篇博客程序员如何巧用Excel提高工作效率,讲解了程序员在日常工作中如何利用Excel来提高工作效率,没想到收到很好的反馈,点赞量,评论量以及阅读量一度飙升为我的博客中Top 1,看来大家平 ...

  8. 企业微信快捷接入Odoo的模块——WeOdoo

    WeOdoo Odoo 快速接入企业微信,快捷使用,基于Oauth2.0安全认证协议,免对接开发配置,支持局域网等内网环境的 Odoo 服务 详见: http://oejia.net/blog/201 ...

  9. SQL Server中是否可以准确获取最后一次索引重建的时间?

    在SQL Server中,我们能否找到索引的创建时间?最后一次索引重建(Index Rebuild)的时间? 最后一次索引重组(INDEX REORGANIZE)的时间呢?  答案是我们无法准确的找到 ...

  10. SpringCloud的分布式配置及消息总线

    1.在搭建分布式配置时,我们大概看下分布式配置的流程 如图所示: 当一个系统中的配置文件发生改变的时候,我们需要重新启动该服务,才能使得新的配置文件生效,spring cloud config可以实现 ...