迭代器模式 Iterator 行为型 设计模式(二十)

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

意图

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

集合与遍历

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

结构

image_5c11cddc_779f
抽象迭代器角色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());
}
}
}
image_5c11cddc_5524
示例代码中ConcreateAggregate自己提供了获取指定下标元素的方法,能够直接调用获取元素
借助于Iterator,将迭代逻辑从Aggregate中剥离出来,独立封装实现
在客户端与容器之间,增长了一层Iterator,实现了客户端程序与容器的解耦
image_5c11cddc_440b
说白了,增长了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类提供了对元素的遍历方法
image_5c11cddc_295f
接下来看下ArrayList的实现
ArrayList中iterator()返回了一个Itr对象,而这个对象是ArrayList的内部类,实现了Iterator接口
image_5c11cddc_26d
看得出来,java给集合框架内置了迭代器模式
在ArrayList中使用就是内部类的形式,也就是内部迭代器
boolean hasNext()
是否拥有更多元素,换句话说,若是next()方法不会抛出异常,就会返回true
next();
返回下一个元素
remove()
删除元素
 
有几点须要注意
1.)初始时,能够认为“当前位置”为第一个元素前面
因此next()获取第一个元素
image_5c11cddc_3d19
2.)根据第一点,初始的当前位置”为第一个元素前面,因此若是想要删除第一个元素的话,必须先next,而后remove
Iterator iterator = list.iterator();

iterator.next();

iterator.remove();

 

不然,会抛出异常
image_5c11cddc_6e70
3.)不只仅是删除第一个元素须要先next,而后才能remove,每个remove,前面必须有一个next,成对出现
因此remove是删除当前元素
若是下面这样,会抛出异常
iterator.next();

iterator.remove();

iterator.remove();

 

image_5c11cddc_424a
4.)迭代器只能遍历一次,若是须要从新遍历,能够从新获取迭代器对象
若是已经遍历到尾部以后仍旧继续使用,将会抛出异常
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
iterator.next();
}
iterator.next();
image_5c11cddc_7a48

总结

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