迭代子(Iterator)模式
迭代子模式又叫做游标模式。迭代子模式可以顺序地访问一个聚集中的元素而必暴露聚集的内部表象。
1. 聚集和Java聚集
多个对象在一起形成的总体形成聚集(Aggregate),聚集对象是能够包容一组对象的容器对象。数组就是最基本的聚集,也是其他Java聚集对象的设计基础。
Java聚集(Collection)对象是实现了共同的接口(java.util.Collection)的对象。从1.2版本开始提供了很多聚集,包括Vector、ArrayList、Queue、Stack、LinkedList、HashSet、TreeSet、HashTable、Hashmap以及TreeMap等。
2. 为什么需要迭代子
迭代子对象是对遍历的抽象化,不同的聚集对象可以提供相同的迭代子对象,从而使客户端无需知道聚集的低层结构。它将迭代逻辑封装到一个独立的迭代子对象中,从而与聚集本身分隔开。一个聚集可以提供多个不同的迭代子对象,从而使得遍历逻辑的变化不会影响到聚集对象本身。
从代码重构的角度看,迭代子在客户端和聚集层之间增加了一层,从而使得客户端与聚集之间的通信从直接变为间接。这么做的好处是缓存了客户端的变化对聚集的影响,以及聚集的变化对客户端的影响。
3. 迭代子模式的结构
一般性结构如下:
涉及的角色如下:
抽象迭代子(Iterator)角色:此抽象角色定义出遍历元素所需的接口
具体迭代子(ConcreteIterator)角色:实现了Iterator接口,并保持迭代过程中的游标位置。
聚集(Aggregate)角色:此抽象角色给出创建迭代子对象的接口。
具体聚集(ConcreteAggregate)角色:实现了创建迭代子对象的接口,返回一个合适的具体迭代子实例。
客户端角色:持有对聚集及其迭代子对象的引用,调用迭代子对象的迭代接口,也有可能通过迭代子操作聚集元素的增加和删除。
宽接口和窄接口:
如果一个具体的接口提供了可以用来修改聚集元素的方法,这个接口就是宽接口;反之则为窄接口。
提供宽接口的聚集又被称为白箱聚集。聚集对象向外界提供相同的宽接口,如下:
由于迭代子是在聚集的结构之外的,因此这样的迭代子又被称为外禀迭代子模式。
在改良的设计中,聚集对象为迭代子对象提供宽接口,对其他对象提供窄接口。换言之,聚集对象的内部结构对迭代子应当适当的公开。
由于迭代子是在聚集的结构内定义的,因此这样的迭代子又称为内禀迭代子。
3.1 白箱聚集与外禀迭代子
白箱聚集向外界提供访问自己内部元素的接口,从而使外禀迭代子可以通过聚集的遍历方法实现迭代功能。
结构图如下:
源代码如下:
抽象聚集角色类,这个角色规定出所有的具体聚集必须实现的接口。迭代子模式要求聚集对象必须有一个工厂方法,也就是createIterator()方法,以向外界提供迭代子对象的实例。
public abstract class Aggregate { /**
* 工厂方法,创建相应迭代子对象的接口
*/
public abstract Iterator createIterator();
}
抽象迭代子角色声明具体迭代子需要实现的接口
public interface Iterator { /**
* 迭代方法:移动到第一个元素
*/
void first(); /**
* 迭代方法:移动到下一个元素
*/
void next(); /**
* 迭代方法:是否为最后一个元素
*/
boolean isDone(); /**
* 迭代方法:返还当前元素
*/
Object currentItem();
}
具体聚集角色实现抽象聚集角色类所要求的接口,也就是createIterator()方法。此外,还有方法getElement()向外界提供聚集元素,而方法size()向外界提供聚集的大小等。
public class ConcreteAggregate extends Aggregate { private Object[] objArray = null; /**
* 构造方法,传入聚合对象的具体内容
*/
public ConcreteAggregate(Object[] objArray) {
this.objArray = objArray;
} @Override
public Iterator createIterator() {
return new ConcreteIterator(this);
} /**
* 取值方法:向外界提供聚集元素
*/
public Object getElement(int index) { if (index < objArray.length) {
return objArray[index];
} else {
return null;
}
} /**
* 取值方法:向外界提供聚集的大小
*/
public int size() {
return objArray.length;
}
}
具体迭代子角色
public class ConcreteIterator implements Iterator { // 持有被迭代的具体的聚合对象
private ConcreteAggregate concreteAggregate; // 内部索引,记录当前迭代到的索引位置
private int index = 0; // 记录当前聚集对象的大小
private int size = 0; public ConcreteIterator(ConcreteAggregate agg) {
this.concreteAggregate = agg;
this.size = agg.size();
index = 0;
} /**
* 迭代方法:返还当前元素
*/
@Override
public Object currentItem() {
return concreteAggregate.getElement(index);
} /**
* 迭代方法:移动到第一个元素
*/
@Override
public void first() { index = 0;
} /**
* 迭代方法:是否为最后一个元素
*/
@Override
public boolean isDone() {
return (index >= size);
} /**
* 迭代方法:移动到下一个元素
*/
@Override
public void next() {
if (index < size) {
index++;
}
} }
客户端类:
public class Client { public void operation() {
Object[] objArray = { "1", "2", "3", "4", "5" };
Aggregate agg = new ConcreteAggregate(objArray); Iterator it = agg.createIterator();
while (!it.isDone()) {
System.out.println(it.currentItem());
it.next();
}
} public static void main(String[] args) { Client client = new Client();
client.operation();
} }
结果:
1
2
3
4
5
外禀迭代子的意义:
一个常常会问的问题是:既然白箱聚集已经向外界提供了遍历方法,客户端已经可以自行进行迭代了,为什么还要应用迭代子模式,并创建一个迭代子对象进行迭代呢?
客户端当然可以自行进行迭代,不一定非得需要一个迭代子对象。但是,迭代子对象和迭代模式会将迭代过程抽象化,将作为迭代消费者的客户端与迭代负责人的迭代子责任分隔开,使得两者可以独立的演化。在聚集对象的种类发生变化,或者迭代的方法发生改变时,迭代子作为一个中介层可以吸收变化的因素,而避免修改客户端或者聚集本身。
此外,如果系统需要同时针对几个不同的聚集对象进行迭代,而这些聚集对象所提供的遍历方法有所不同时,使用迭代子模式和一个外界的迭代子对象是有意义的。具有同一迭代接口的不同迭代子对象处理具有不同遍历接口的聚集对象,使得系统可以使用一个统一的迭代接口进行所有的迭代
3.2黑箱聚集与内禀迭代器 (只是把具体迭代子角色设计为具体聚集角色的私有内部成员内)
一个黑箱聚集不向外部提供遍历自己元素的接口,因此这些元素对象只可以被聚集内部成员访问。由于内禀迭代子恰好是聚集内部的成员子类,因此内禀迭代子对象是可以访问聚集的元素的。
结构图如下:
源代码如下:(只是具体迭代子角色和具体聚集角色代码与上面不同)
public abstract class Aggregate { /**
* 工厂方法,创建相应迭代子对象的接口
*/
public abstract Iterator createIterator();
}
public interface Iterator { /**
* 迭代方法:移动到第一个元素
*/
void first(); /**
* 迭代方法:移动到下一个元素
*/
void next(); /**
* 迭代方法:是否为最后一个元素
*/
boolean isDone(); /**
* 迭代方法:返还当前元素
*/
Object currentItem();
}
具体聚集角色将具体迭代子封装为私有内部成员类。
public class ConcreteAggregate extends Aggregate { private Object[] objArray = null; /**
* 构造方法,传入聚合对象的具体内容
*/
public ConcreteAggregate(Object[] objArray) {
this.objArray = objArray;
} @Override
public Iterator createIterator() {
return new ConcreteIterator();
} /**
* 内部成员类,具体迭代子类
*/
private class ConcreteIterator implements Iterator {
// 内部索引,记录当前迭代到的索引位置
private int index = 0;
// 记录当前聚集对象的大小
private int size = 0; /**
* 构造函数
*/
public ConcreteIterator() {
this.size = objArray.length;
index = 0;
} /**
* 迭代方法:返还当前元素
*/
@Override
public Object currentItem() {
return objArray[index];
} /**
* 迭代方法:移动到第一个元素
*/
@Override
public void first() {
index = 0;
} /**
* 迭代方法:是否为最后一个元素
*/
@Override
public boolean isDone() {
return (index >= size);
} /**
* 迭代方法:移动到下一个元素
*/
@Override
public void next() {
if (index < size) {
index++;
}
}
}
}
客户端代码:
public class Client { public void operation() {
Object[] objArray = { "1", "2", "3", "4", "5" };
Aggregate agg = new ConcreteAggregate(objArray); Iterator it = agg.createIterator();
while (!it.isDone()) {
System.out.println(it.currentItem());
it.next();
}
} public static void main(String[] args) { Client client = new Client();
client.operation();
} }
结果:
1
2
3
4
5
4. 迭代子模式的实现
4.1 主动迭代子和被动迭代子
迭代子是主动的还是被动的是相对于客户端而言的。如果客户端控制迭代的进程,那么这样的迭代子就是主动迭代子,相反就是被动迭代子。
使用主动迭代子的客户端会明显调用迭代子的next()等方法,在遍历过程中向前行进;而客户端在调用被动迭代子模式时,客户端不需要明显的调用迭代方法,迭代子自行推进遍历过程。
4.2 静态迭代子和动态迭代子
静态迭代子由聚集对象创建,并持有聚集对象的一个快照(snapshot),在产生后这个快照的内容不再变化。客户端可以继续修改原聚集的内容,但是迭代子不会反映出来。其好是安全性和简易性。但是由于静态迭代子将原聚集内容复制了一份,其缺点是对时间和内存资源的浪费。
动态迭代子与静态迭代子相反,在迭代子被产生之后,迭代子保持对聚集元素的引用,因此任何对原聚集内容的改变都会在迭代子对象上反映出来。完整的动态迭代子不容易实现,但是简易的动态迭代子易于实现。大多数java设计师遇到的迭代子都是简化的动态迭代子。
为了说明什么是动态迭代子,需要先说明一个新概念: Fail Fast
Fail Fast:
如果一个算法开始之后,它的运算环境发生变化,使得算法无法进行必需的调整时,这个算法就应当立即发出故障信号。这就是Fail Fast的含义。
如果聚集对象的元素在一个动态迭代子的迭代过程中发生变化时,迭代过程会受到影响而变得不能自恰。这时候,迭代子就应当立即抛出一个异常。这种迭代子就是实现了Fail Fast功能的迭代子。
Fail Fast 是一种出现错误时的早期报警功能。如果迭代子遍历的对象被外界直接修改会造成迭代崩溃,这时候迭代子应当在崩溃可能发生之前尽早抛出一个异常。
Abstract.Itr迭代子是一个FailFast的迭代子,方法 checkForComodification 会检查聚集的内容是否刚刚被外界直接修改过(不是通过迭代子的 remove() 方法修改的)。如果在迭代后,聚集的内容被外界直接修改的话会立即抛出ConcurrentModificationException异常。(Java中LIst的迭代模式也是基于黑箱子的迭代子模式,将具体迭代设为内部类)
源码如下:
private class Itr implements Iterator<E> {
/**
* Index of element to be returned by subsequent call to next.
*/
int cursor = 0; /**
* Index of element returned by most recent call to next or
* previous. Reset to -1 if this element is deleted by a call
* to remove.
*/
int lastRet = -1; /**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int expectedModCount = modCount; public boolean hasNext() {
return cursor != size();
} public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
} public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification(); try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
} final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
4.3 迭代子模式的优缺点
优点:
(1)迭代子模式简化了聚集的接口。迭代子具备了一个遍历接口,这样聚集的接口就不必具备遍历接口。
(2)每一个聚集对象都可以有一个或多个迭代子对象,每一个迭代子的迭代状态可以是彼此独立的。因此,一个聚集对象可以同时有几个迭代在进行之中。
(3)由于遍历算法被封装在迭代子角色里面,因此迭代的算法可以独立于聚集角色变化。
缺点:
(1)迭代子模式给客户端一个聚集被顺序化的错觉,因为大多数的情况下聚集的元素并没有确定的顺序,但是迭代必须以一定的顺序进行。
(2)迭代子给出的聚集元素没有类型特征。一般而言,迭代子给出的元素都是Object类型。 (这个在目前的聚集中已通过泛型解决)
总结:
意图:提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。
主要解决:不同的方式来遍历整个整合对象。
何时使用:遍历一个聚合对象。
如何解决:把在元素之间游走的责任交给迭代器,而不是聚合对象。
关键代码:定义接口:hasNext, next。
应用实例:JAVA 中的 iterator。
优点: 1、它支持以不同的方式遍历一个聚合对象。 2、迭代器简化了聚合类。 3、在同一个聚合上可以有多个遍历。 4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
使用场景: 1、访问一个聚合对象的内容而无须暴露它的内部表示。 2、需要为聚合对象提供多种遍历方式。 3、为遍历不同的聚合结构提供一个统一的接口。
注意事项:迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。
迭代子(Iterator)模式的更多相关文章
- Java设计模式(12)迭代模式(Iterator模式)
上了这么多年学,我发现一个问题,好象老师都很喜欢点名,甚至点名都成了某些老师的嗜好,一日不点名,就饭吃不香,觉睡不好似的,我就觉得很奇怪,你的课要是讲的好,同学又怎么会不来听课呢,殊不知:“误人子弟, ...
- 设计模式之Iterator模式
STL里的iterator就是应用了iterator模式. 一.什么是迭代模式 Iterator模式也叫迭代模式,是行为模式之一,它把对容器中包含的内部对象的访问委让给外部类,使用Iterator按顺 ...
- [工作中的设计模式]迭代子模式Iterator
一.模式解析 迭代子模式又叫游标(Cursor)模式,是对象的行为模式.迭代子模式可以顺序地访问一个聚集中的元素而不必暴露聚集的内部表象 1.迭代子模式一般用于对集合框架的访问,常用的集合框架为lis ...
- stl 迭代子的失效
迭代子是STL中很重要的特性,但是其很脆弱(我个人认为),因为使用它的条件很苛刻,一不小心就失效了.其在两中情况下可能会失效. 1.当容器有插入操作时 在初始化了迭代子后,如果容器有插入操作时,迭代子 ...
- [设计模式]迭代子模式 Iterator
迭代子模式又叫做游标cursor模式,是对象的行为模式.迭代子模式可以顺序的访问一个聚集中的元素而不必暴露聚集的内部表象. 迭代子模式被广泛的应用在Java语言的API中的几个设计模式之一.在Java ...
- 第3月第13天 cpp模版 Iterator模式 proactor
1.模版除了传参,还可以自动创建.而传指针只是传参而已. template <class TYPE, class FUNCTOR, class ACE_LOCK, typename TIME_P ...
- Java 实现迭代器(Iterator)模式
类图 /** * 自己定义集合接口, 相似java.util.Collection * 用于数据存储 * @author stone * */ public interface ICollection ...
- snmpd 子代理模式编译测试
1.参考链接 1)Net-snmp添加子代理示例 https://blog.csdn.net/eyf0917/article/details/39546651 2.操作步骤 1)网络拷贝下面的文件 ...
- 设计模式—迭代器Iterator模式
什么是迭代器模式? 让用户通过特定的接口访问容器的数据,不需要了解容器内部的数据结构. 首先我们先模仿集合中ArrayList和LinkedList的实现.一个是基于数组的实现.一个是基于链表的实现, ...
随机推荐
- 【开发笔记】- Idea启动Gradle报错:Warning:Unable to make the module: reading, related gradle configuration was not found. Please, re-import the Gradle project and try again
报错信息: Warning:Unable to make the module: reading, related gradle configuration was not found. Please ...
- ES6环境搭配(一)
一.Node(NodeJS.Node.js)的安装:1.下载官网下载地址:https://nodejs.org/en/ 2.安装a.Linux先将安装包解压,然后进行环境变量的配置即可b.window ...
- Qt 字符串截取 获取指定字符位置
获取字符在字符串中的位置 QString str = "AT+LOC+LOCATION: 115.850441,33.004833"; QString s = "LOC& ...
- MyDAL - .OpenDebug() 与 Visual Studio 输出窗口 使用
索引: 目录索引 SQL Debug 信息说明 一. 对 XConnection 对象 未开启 OpenDebug, 在 VS 状态下,将默认在 VS 窗口 打印出 参数化的 SQL 执行语句: 新 ...
- python循环删除列表元素留一个问题
https://www.cnblogs.com/baihualin/p/10698651.html 引用up
- 0x04 Python logger 支持多进程日志按大小分割
目录 支持多进程日志按大小分割 多进程日志大小分割handler配置实例 支持多进程日志按大小分割 由于python内置模块logging.handlers.RotatingFileHandler是不 ...
- 随笔记录--RegExp类型
阅读Javascript高级程序设计第五章 -- RegExp类型总结 对于基础教程部分, 有小伙伴不熟悉的,可以参考 正则表达式 - 教程 1. 基础部分回顾: ECMASript通过RegExp类 ...
- 初学者git的用法
初学者github的用法 1.在github上创建一个自己的工程 2.按着上面的要求执行你的命令行 3.将你的代码放到这个已经创建了.git的文件夹中,执行git add . 系统出现如下错误:war ...
- Windbg Register(寄存器)窗口的使用
寄存器是位于在 CPU 的小易失性内存单位. 许多寄存器专用于特定用途,并可用于用户模式应用程序使用的其他寄存器. 基于 x86 和基于 x64 的处理器在有可用的寄存器的不同集合. 如何打开寄存器窗 ...
- 掌握Visual Studio断点
我知道你现在在想什么.断点?真的吗?关于断点有什么需要掌握的?你按F9然后停在代码行.如果你是这么想的,这篇文章是给你的:继续读 Visual Studio提供了一组相当丰富的断点类型和操作,可以使您 ...