程序员潇然 发表于 2022-8-2 15:34:29

迭代器模式 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]
查看完整版本: 迭代器模式 Iterator 行为型 设计模式(二十)