上一节课,我们学习了状态模式,状态模式的组成由三部分,事件 状态 动作,将事件触发,并进行状态的转移和动作的执行,以此避免状态机中的分支判断逻辑,减少复杂度

今天,我们学习的是迭代器模式,很多编程语言都将迭代器作为一个基本的类库直接提供了,我们一般不会去主动实现一个出来,但是仍然有必要去了解如何实现的

大部分编程语言提供的遍历集合的方式,有for循环,foreach循环,迭代器等

而迭代器模式 Iterator Design Pattern 可以叫做游标模式,我们用其去遍历集合对象,这里说的集合对象,也叫做容器 聚合对象,就是包含了一组对象的对象,比如数组 链表 树 图 跳表等,迭代器模式让集合对象的遍历操作从集合类拆分出来,单独放在一个迭代器类中,符合了DN职责单一原则

迭代器是用来遍历容器的,一个完整的迭代器模式,由两个部分组成,容器中如何调用迭代器,迭代器如何实现的,大概的关系如下

图片

我们假设有一个新的编程语言,没有提供迭代器的类库,需要我们从零开发,我们看看如何去做

常见的线性数据结构有着 数组和链表,在数组ArrayList中书写迭代器,在链表LinkedList中书写迭代器

两者拥有共同的接口 list,让开发者可以方便切换和调用,同样,我们提供了一个共同的接口Itertor,并针对这两个容器进行实现不同的实现类,ArrayIterator和ListIterator

// 接口定义方式一

public interface Iterator<E> {

boolean hasNext();

void next();

E currentItem();

}

// 接口定义方式二

public interface Iterator<E> {

boolean hasNext();

E next();

}

这样的实现有两种

第二种是将第一种的前两个方法进行封装,调用一次调用两个,第一种方式,则是在其中将 next()函数只是往后进行移动一位,currentItem()函数则是返回当前游标的元素

第一种更加的灵活,方便我们多次查询一个元素,而不需要移动游标

下面就是简单的对于ArrayIterator的代码实现

public class ArrayIterator<E> implements Iterator<E> {

private int cursor;

private ArrayList<E> arrayList;

public ArrayIterator(ArrayList<E> arrayList) {

this.cursor = 0;

this.arrayList = arrayList;

}

@Override

public boolean hasNext() {

return cursor != arrayList.size(); //注意这里,cursor在指向最后一个元素的时候,hasNext()仍旧返回true。

}

@Override

public void next() {

cursor++;

}

@Override

public E currentItem() {

if (cursor >= arrayList.size()) {

throw new NoSuchElementException();

}

return arrayList.get(cursor);

}

}

public class Demo {

public static void main(String[] args) {

ArrayList<String> names = new ArrayList<>();

names.add(“xzg”);

names.add(“wang”);

names.add(“zheng”);

Iterator<String> iterator = new ArrayIterator(names);

while (iterator.hasNext()) {

System.out.println(iterator.currentItem());

iterator.next();

}

}

}

我们将需要遍历的容器对象,通过构造函数传给迭代器类,然后在容器中定义一个iterator()方法,去反向的创建一个迭代器,我们将这个方法定义在List接口中

同样,对于LinkedIterator,其代码结构和ArrayIterator一致,可以对比类似

其实现思路很简单了,需要定义hasNext(),currentItem(),next()三个最基本的方法,待遍历的容器对象调用iterator来进行依赖注入到代理类中,然后容器通过iterator()方法来创建迭代器

图片

迭代器的优势需要和另外的两种遍历方式的比较得到,for循环,foreach循环 iterator迭代器,我们拿Java来说

List<String> names = new ArrayList<>();

names.add(“xzg”);

names.add(“wang”);

names.add(“zheng”);

// 第一种遍历方式:for循环

for (int i = 0; i < names.size(); i++) {

System.out.print(names.get(i) + “,”);

}

// 第二种遍历方式:foreach循环

for (String name : names) {

System.out.print(name + “,”)

}

// 第三种遍历方式:迭代器遍历

Iterator<String> iterator = names.iterator();

while (iterator.hasNext()) {

System.out.print(iterator.next() + “,”);//Java中的迭代器接口是第二种定义方式,next()既移动游标又返回数据

}

foreach其实是一种语法糖,底层基于了迭代器实现的,所以直接pass

虽然看起来for循环看起来更加的简洁,但是对于更加复杂的数据结构,比如图结构 树形结构,就比较困难了,比如树有着前中后序,按层遍历, 图有着深度优先 广度优先遍历,如果都是客户端来实现,会比较苦难,所以可以单独拆分出来,拆分为迭代器类,减少了容器类代码的复杂性

而且,对于不同的遍历操作,比如针对于图的遍历,我们可以定义出DFSIterator,BFSIterator两个迭代器类,去分别实现广度头衔和深度优先的遍历类,然后其他的代码不需要修改,这样符合开闭原则

本章重点,

迭代器模式,又名游标模式,用来遍历对象,这里的对象,被称为集合对象,也可以叫做容器,聚合对象,实际上就是一组对象的对象,比如 数组 链表 树 图 跳表,一个完整的迭代器模式,一般会涉及容器或者容器迭代器两个部分,我们分别实现这两者,并通过依赖注入去将其出传递到迭代器类中

遍历结合的方式有三种,for循环,foreach循环,迭代循环,迭代循环的优势在于

迭代器模式封装结合内部的复杂数据结构,开发者不需要了解如何遍历,直接使用容器的迭代器即可

迭代器模式,让迭代的操作从容器类中拆分出来,职责更加单一

迭代器模式再添加新的遍历的时候更加容易,符合开闭原则

课后思考

1.在Java中,使用迭代器的同时删除元素,会导致报错,为什么呢?

2.编程语言中基础类库提供的针对集合的迭代器,在MySQL的ResultSet类中,提供了first last

previous()方法,可以看成一种迭代器,分析下源码

1.因为在一般的迭代器的内部维护了一个记录长度的属性,在进行删除之前,会进行对比,如果发现已经被修改了,那就意味着可能会造成删除了本不应该删得数值了,所以多线程下,考虑使用加锁吧,或者使用原子类

2.其实两者实现的功能相同,就是在ResultSet中,将原本的next和hashNext方法进行了集合,这可能是因为数据实际上还是存储在了MySQL中导致的,在遍历获取对应的数据的时候,直接while(rs.next())即可,不需要进行判断即可

发表评论

邮箱地址不会被公开。 必填项已用*标注