迭代器模式 Iterator 行为型 设计模式(二十)
迭代器模式(Iterator)!(data/attachment/forum/202208/02/152904ag6nwbmz6qgmqw7g.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
**走遍**天下,世界那么大,我想去看看
!(data/attachment/forum/202208/02/152918l7q7uuuqkhsi6783.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
在计算机中,Iterator意为迭代器,迭代有重复的含义,在程序中,更有“遍历”的含义
如果给定一个数组,我们可以通过for循环来遍历这个数组,这种遍历就叫做迭代
对于数组这种数据结构,我们称为是可迭代的
所以
**迭代器就是可以用来对于一个数据集合进行遍历的对象**
### 意图
提供一种方法,顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
**别名:游标 Cursor**
### 集合与遍历
由一个或多个确定的元素所构成的整体叫做集合**。**
多个对象聚集在一起形成的总体称之为聚集aggregate。
集合和聚集有相似的含义。
**容器是指盛物品的器具,Java的Collection框架,就是设计用来保存对象的容器**
容器可以认为是集合、聚集的具体体现形式,三个元素在一起叫做集合(聚集),怎么在一起?数组,列表?这具体的体现形式就是容器
容器必须提供内部对象的访问方式,如果不能获取对象,容器也失去了存在的意义,一个只能进不能出的储蓄罐你要它何用?
因为容器的存在就是为了更方便的使用、管理对象。
而且**通常需要容器提供对于内部所有元素的遍历方法**。
然而容器其内部有不同的摆放形式,可顺序,可无序堆集
简言之,就是不同类型的容器必然有不同的内部数据结构
那么,**一种解决办法就是不同的容器各自提供自己的遍历方法**。
这样的话,对于使用容器管理对象的客户端程序来说:
**如果迭代的逻辑,也就是业务逻辑没有变化**
**当需要更换为另外的集合时,就需要同时更换这个迭代方法**
考虑这样一个场景
有一个方法,方法的参数类型为 Collection
他的迭代逻辑,也就是业务逻辑为遍历所有元素,读取每个元素的信息,并且进行打印...
如果不同的容器有不同的遍历方法,也就是一种实现类有一种不同的遍历方法
一旦更换实现类,那么就需要同步更换掉这个迭代方法,否则方法将无法通过编译
**如果集合的实现不变,需要改变业务逻辑,也就是迭代的逻辑**
**那么就需要修改容器类的迭代方法,也就是修改原来的遍历方法**
还是上面的场景
有一个方法,方法的参数类型为 Collection
他的迭代逻辑,也就是业务逻辑为遍历所有元素,读取每个元素的信息,并且进行打印...
现在他的实现类无需变化
但是业务逻辑需要变动,比如希望从后往前的方式进行遍历,而不再是从前往后
就需要修改原来的方法或者重新写一个方法
出现上述问题的根本原因就在于**元素的迭代逻辑与容器本身耦合在一起**
当迭代逻辑或者集合实现发生变更时,需要进行修改,**不符合开闭原则**
容器自身不仅仅需要存储管理对象,还要负责对象的遍历访问,**不符合单一职责原则**
**存储管理对象是容器的核心职责,虽然经常需要提供遍历方法,但是他并不是核心职责**
**但是为了提供遍历元素的方法,可能不得不在容器类内提供各种全局变量,比如保存当前的位置等,这无疑也会导致容器聚集类设计的复杂度**
### 结构
!(data/attachment/forum/202208/02/152946ruziwma9g9i297zu.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
**抽象迭代器角色Iterator**
定义遍历元素所需要的接口
**具体的迭代器ConcreteIterator**
实现了Iterator接口,并且跟踪当前位置
**抽象集合容器角色Aggregate**
定义创建相应迭代器的接口(方法)
就是一个容器类,并且定义了一个返回迭代器的方法
**具体的容器角色ConcreteAggregate**
Aggregate的子类,并且实现了创建Iterator对象的接口,也就是返回一个ConcreteIterator实例
**客户端角色Client**
持有容器对象以及迭代器对象的引用,调用迭代对象的迭代方法遍历元素
**迭代器模式中,通过一个外部的迭代器来对容器集合对象进行遍历。**
**迭代器定义了遍历访问元素的协议方式。**
**容器集合对象提供创建迭代器的方法。**
### 示例代码
**Aggregate角色**
提供了iterator()获取Iterator
```java
package iterator;
public abstract class Aggregate {
abstract Iterator iterator();
abstract Object get(int index);
abstract int size();
}
```
ConcreateAggregate角色
内部使用一个Object数组,数组直接通过构造方法传递进去(只是为了演示学习模式,不要纠结这算不上一个容器)
提供了大小的获取方法以及获取指定下标元素的方法
尤其是实现了iterator()方法,创建一个ConcreteIterator实例,将当前ConcreteAggregate作为参数传递给他的构造方法
```java
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;
}
@Override
int size() {
return objects.length;
}
}
```
**迭代器接口**
一个是否还有元素的方法,一个获取下一个元素的方法
```java
package iterator;
public interface Iterator {
boolean hasNext();
Object next();
}
```
**具体的迭代器**
内部维护了数据的大小和当前位置
如果下标未到最后,那么就是还有元素
next()方法用于获取当前元素,获取后当前位置往后移动一下
```java
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;
}
}
```
**测试类**
```java
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());
}
}
}
```
!(data/attachment/forum/202208/02/153135t5nrl0s3wl0hkdfh.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
示例代码中ConcreateAggregate本身提供了获取指定下标元素的方法,可以直接调用获取元素
**借助于Iterator,将迭代逻辑从Aggregate中剥离出来,独立封装实现**
**在客户端与容器之间,增加了一层Iterator,实现了客户端程序与容器的解耦**
!(data/attachment/forum/202208/02/153148iz1ht1ttpzcrdcbx.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
说白了,增加了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类提供了对元素的遍历方法
!(data/attachment/forum/202208/02/153215hg1tuj74117gjhmf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
接下来看下ArrayList的实现
ArrayList中iterator()返回了一个Itr对象,而这个对象是ArrayList的内部类,实现了Iterator接口
!(data/attachment/forum/202208/02/153233ltqjqgistggqqh8i.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
看得出来,java给集合框架内置了迭代器模式
在ArrayList中使用就是内部类的形式,也就是内部迭代器
**boolean hasNext()**
是否拥有更多元素,换句话说,如果next()方法不会抛出异常,就会返回true
**next();**
返回下一个元素
**remove()**
删除元素
**有几点需要注意**
1.)初始时,可以认为**“当前位置”为第一个元素前面**
所以next()获取第一个元素
!(data/attachment/forum/202208/02/153248v0e474q9eq8sfq4z.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
2.)根据第一点,初始的当前位置”为第一个元素前面,所以如果想要**删除第一个元素**的话,**必须先next**,然后**remove**
```java
Iterator iterator = list.iterator();
iterator.next();
iterator.remove();
```
否则,会抛出异常
!(data/attachment/forum/202208/02/153312fv93m4itv9wby4i4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
3.)不仅仅是删除第一个元素需要先next,然后才能remove,**每一个remove,前面必须有一个next,成对出现**
所以remove是删除当前元素
如果下面这样,会抛出异常
```java
iterator.next();
iterator.remove();
iterator.remove();
```
!(data/attachment/forum/202208/02/153339vh2gqsqtwj87tiig.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
4.)**迭代器只能遍历一次**,如果需要重新遍历,可以重新获取迭代器对象
如果已经遍历到尾部之后仍旧继续使用,将会抛出异常
```java
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
iterator.next();
}
iterator.next();
```
!(data/attachment/forum/202208/02/153401s0bvawojajfajzy3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")
### 总结
在java中万事万物都是对象
前面的命令模式将请求转换为命令对象
解释器模式中,将语法规则转换为终结符表达式和非终结符表达式
在**迭代器模式中,将“遍历元素”转换为对象**
通过迭代器模式引入迭代器,将**遍历逻辑功能从容器聚集对象中分离**出来
聚合对象本身只负责数据存储,遍历的职责交给了迭代器
对于同一个容器对象,可以定义多种迭代器,也就是可以定义多种遍历方式
如果需要使用另外的迭代方式,仅仅需要更改迭代器对象即可
这样你甚至可以把ConcreteIterator使用配置文件进行注入,灵活设置
将迭代遍历的逻辑从容器对象中分离,必然**会减少容器类的复杂程度**
当增加新的容器类或者迭代器类时,不需要修改原有的代码,**符合开闭原则**
**如果你想要将容器聚集对象的遍历逻辑从容器对象中的分离**
**或者想要提供多种不同形式的遍历方式时,或者你想为不同的容器对象提供一致性的遍历接口逻辑**
**你就应该考虑迭代器模式了**
迭代器模式的应用是如此广泛,以至于java已经将他内置到集合框架中了
所以对于我们自己来说,多数时候可以认为迭代器模式几乎用不到了
因为绝大多数时候,使用框架提供的应该就足够了
**在java实现中,迭代器模式的比较好的做法就是Java集合框架使用的这种形式---内部类形式的内部迭代器,如果真的需要自己搞一个迭代器,建议仿照集合框架搞吧**
借助于迭代器模式,如果迭代的逻辑不变,更换另外的集合实现,因为实现了共同的迭代器接口,所以不需要对迭代这块,无需做任何变动
如果需要改变迭代逻辑,必须增加新的迭代形式,只需要增加一个新的内部类实现迭代器接口即可,其他使用的地方只需要做很小的调整
ArrayList中的ListIterator<E> listIterator() 方法就是如此
有人觉得增加一个类和一个方法这不也是修改么?个人认为:开闭原则尽管最高境界是完全的对扩展开放对修改关闭,但是也不能死抠字眼
增加了一个新的获取迭代对象的方法以及一个新的类,总比将原有的源代码中添加新的方法那种修改要强得多,所有的遍历逻辑都封装在新的迭代器实现类中,某种程度上可以认为并没有“修改源代码”
使用内部类的形式,有人觉得不还是在一个文件中么?但是内部类会有单独的class文件,而且,内部类就像一道墙,分割了内外,所有的逻辑被封装在迭代器实现类中
不需要影响容器自身的设计实现,所以也是符合单一职责原则的。
!(data/attachment/forum/202206/16/141330jha7st9soow8772i.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "common_log.png")
`转载务必注明出处:程序员潇然,疯狂的字节X,https://crazybytex.com/thread-119-1-1.html `
页:
[1]