行为型-迭代器模式(五)

目录

1. 模式概述

1.1 定义

1.2 应用场景

1.3 优缺点

2 迭代器模式原理

3. 以Java为例剖析迭代器


1. 模式概述

1.1 定义

    迭代器模式(Iterator Design Pattern),也叫作游标模式(Cursor Design Pattern),提供一种方法顺序访问一个聚合中的各个元素,而不暴露其内部的表示。

    通俗的说就是通过对聚合提供额外的遍历类(迭代类),该类主要封装了对聚合的遍历,这么做的好处简化了聚合类并提供了对聚合类统一的遍历方式。

  • HeadFirst中形象的表示如下图:

1.2 应用场景

  • 1. 访问一个聚合对象的内容而无须暴露它的内部表示。
  • 2. 需要为聚合对象提供多种遍历方式。 对于复杂的树或图结构,例如针对图的遍历,可以定义 DFSIterator、BFSIterator 两个迭代器类,让它们分别来实现深度优先遍历和广度优先遍历。
  • 3. 为遍历不同的聚合结构提供一个统一的接口。

1.3 优缺点

  • 优点:
  1. 封装聚合内部的复杂数据结构,开发者不需要了解如何遍历,直接使用容器提供的迭代器即可;
  2. 将聚合对象的遍历操作从聚合类中拆分出来,放到迭代器类中,让两者的职责更加单一;
  3. 让添加新的遍历算法更加容易,更符合“开闭原则”。除此之外,因为迭代器都实现自相同的接口,在开发中,基于接口而非实现编程,替换迭代器也变得更加容易。
  • 缺点:由于将遍历功能与聚合对象进行抽离,因此对应的类数目会成对增加。

2 迭代器模式原理

   通过对聚合类的遍历进行封装成一个迭代类,然后提供统一的遍历方式。

   这么做的好处是将遍历与聚合实现了解耦,使得聚合与遍历的设计更加符合“单一职责”设计原则。

   当然这么也造成了类的成对增加,毕竟每添加一种聚合就得提供一个相对应的迭代类。

   根据迭代器的原理,以及“基于接口编程而非实现编程”,简易的类关系如下

 

  • 说明:迭代器类与具体的聚合实现类是通过组合的方式从而实现了迭代器遍历功能。

3. 以Java为例剖析迭代器

   通常对于迭代器模式大多语言将其做为基础类库提供了,比如Java就提供了直接可用的迭代器类。

   如下以ArrayList为例说明

   在ArrayList源码前,先思考下迭代器如何应对集合的增删问题?

   示例代码(以下代码全为与迭代器相关的核心代码,详细可参考JDK源码):

  • 1. ArrayList类
public class ArrayList<E> extends AbstractList<E> implements List<E> {
   //...  
   public Iterator<E> iterator() {
        return new Itr();
    }
}
  • 对于具体的聚合实现类主要是提供一个返回对应聚合迭代器类便可;
  • 注意ArrayList返回的Itr是ArrayList的内部类

 2. Iterator接口类

public interface Iterator<E> {
    boolean hasNext();   
    E next();   
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
}

3. ArrayList对应的具体迭代器类 

private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

解说:

  • 1. 上述代码可知道ArrayList维护了一个modCount变量,该变量为ArrayList内部维护的一个记录了集合改变的次数,如下代码删除或添加元素时,该变量会添加一;

ArrayList部分源码

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        ...
    }
    
    public E remove(int index) {
        ...
        modCount++;
    }
  • 2. 在创建迭代器的时候 int expectedModCount = modCount; 这行代码就将集合的修改次数赋值给expectedModCount 所期望的修改次数。
  • 3. 在迭代器调用next或remove方法时会先调用该方法checkForComodification();判断expectedModCount与modCount是否相等,不相等则抛出异常。
final void checkForComodification() {
       if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
 }
  • 4. 通过上面的分析可知,在应用迭代器遍历的时候是不允许对集合进行增删的,因为增删后集合所维护的成员变量modCount会与迭代器所维护的expectedModCount不一致,从而导致抛异常。为什么要维护这两个变量一致呢?

   关于这点稍微思考下其实很容易理解,例如原来获取下标为2的元素,结果在准备获取的时候集合将下标为1的元素删除,那么由于数组会进行移动,那么这时再获取下标为2的元素则不再是原先要获取的元素了。

  •  5. 如果非要在遍历的时候对元素进行删除可以直接通过调用迭代器内部的删除方法,源码如下

迭代器Itr类内部的删除方法

public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
 }
  • 观察上述代码可知,如果调用迭代器内部的删除方法,其内部是会对集合期望的修改值与集合修改值进行同步更新维护的。 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
使用 JavaScript 编写的记忆游戏(附源代码)   项目:JavaScript 记忆游戏(附源代码) 记忆检查游戏是一个使用 HTML5、CSS 和 JavaScript 开发的简单项目。这个游戏是关于测试你的短期 记忆技能。玩这个游戏 时,一系列图像会出现在一个盒子形状的区域中 。玩家必须找到两个相同的图像并单击它们以使它们消失。 如何运行游戏? 记忆游戏项目仅包含 HTML、CSS 和 JavaScript。谈到此游戏的功能,用户必须单击两个相同的图像才能使它们消失。 点击卡片或按下键盘键,通过 2 乘 2 旋转来重建鸟儿对,并发现隐藏在下面的图像! 如果翻开的牌面相同(一对),您就赢了,并且该对牌将从游戏中消失! 否则,卡片会自动翻面朝下,您需要重新尝试! 该游戏包含大量的 javascript 以确保游戏正常运行。 如何运行该项目? 要运行此游戏,您不需要任何类的本地服务器,但需要浏览器。我们建议您使用现代浏览器,如 Google Chrome 和 Mozilla Firefox, 以获得更好、更优化的游戏体验。要玩游戏,首先,通过单击 memorygame-index.html 文件在浏览器中打开游戏。 演示: 该项目为国外大神项目,可以作为毕业设计的项目,也可以作为大作业项目,不用担心代码重复,设计重复等,如果需要对项目进行修改,需要具备一定基础知识。 注意:如果装有360等杀毒软件,可能会出现误报的情况,源码本身并无病毒,使用源码时可以关闭360,或者添加信任。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值