设计模式——迭代器模式

在阎宏博士的《JAVA与模式》一书中开头是这样描述迭代子(Iterator)模式的:迭代子模式又叫游标(Cursor)模式,是对象的行为模式。迭代子模式可以顺序地访问一个聚集中的元素而不必暴露聚集的内部表象(internal representation)。

迭代器模式的定义是通过提供一种方法顺序访问一个聚合对象中的各个元素,而又不必暴露该聚合对象中的内部表示。迭代器模式是针对聚合对象(数组、集合、链表)的“访问”而来。通过定义不同的遍历策略来遍历聚合对象。

应用场景

  1. 如果希望提供访问一个聚合对象的内容,但又不想暴露它的内部表示的时候可使用迭代器模式
  2. 如果希望有多种遍历方式可以访问聚合对象。
  3. 如果希望为遍历不同的聚合对象提供一个统一的接口,可以使用迭代器模式(多态迭代)。
IteratorPattern.png

迭代器模式中涉及的角色:

  • 抽象迭代器角色(Iterator):此抽象角色定义出遍历元素的接口方法;
  • 具体迭代器角色(ConcreteIterator):实现具体的迭代方法,继承或实现Iterator。
  • 聚集角色(Aggregate):定义聚集对象的抽象,并定义出具体的遍历接口返回Iterator类型
  • 具体聚集角色(ConcreteAggregate):实现了创建迭代子(Iterator)对象的接口,返回一个合适的具体迭代子实例。

案例演示

首先创建迭代器Iterator接口

/**
 * 定义迭代器角色
 * @author Iflytek_dsw
 *
 */
interface Iterator {
    /**
     * 获取第一个元素
     * @return
     */
    String first();
    /**
     * 获取最后一个元素
     * @return
     */
    String last();
    /**
     * 判断是否有下一个元素
     * @return
     */
    boolean hasNext();
    /**
     * 下一个元素
     * @return
     */
    String next();
}

在Iterator接口中定义了聚集对象需要的遍历操作。

定义抽象聚集对象

/**
 * 聚集角色,定义聚集角色具备的接口
 * @author Iflytek_dsw
 *
 */
abstract class Aggregate {

    abstract Iterator iterator();
}

定义具体聚集对象

class ConcreteAggregate extends Aggregate{
    
    private List<String> names;

    public ConcreteAggregate(List<String> names) {
        super();
        this.names = names;
    }

    @Override
    Iterator iterator() {
        return new AggregateIterator(this);
    }
    
    public String first(){
        return names == null ? null : names.get(0);
    }
    
    public String last(){
        return names == null ? null : names.get(names.size() -1);
    }
    
    public String next(int index){
        return names == null ? null : names.get(index);
    }
    
    /**
     * 聚集中的元素个数
     * @return
     */
    public int size(){
        return names.size();
    }
}

在上面的实例中,我们可以看到在具体聚集中,我们定义了与Iterator对象的方法,用来封装具体的操作。以便在Iterator的具体实例中进行调用,同时我们也封装了一个生成Iterator的方法。

具体迭代器

class AggregateIterator implements Iterator{
    private ConcreteAggregate concreteAggregate;
    private int index;
    public AggregateIterator(ConcreteAggregate concreteAggregate) {
        super();
        this.concreteAggregate = concreteAggregate;
        this.index = 0;
    }

    @Override
    public String first() {
        return concreteAggregate.first();
    }

    @Override
    public String last() {
        return concreteAggregate.last();
    }

    @Override
    public String next() {
        return concreteAggregate.next(index -1);
    }

    @Override
    public boolean hasNext() {
        if(index < concreteAggregate.size()){
            index++;
            return true;
        }
        return false;
    }
}

在具体迭代器中定义包含一个聚集对象的实例,即对这个聚集对象进行相应操作的访问。

客户端

public class Client {

    /**
     * @param args
     */
    public static void main(String[] args) {
        List<String> names = new ArrayList<>(Arrays.asList(new String[]{"James","Lucy","Jack"}));
        Aggregate aggregate = new ConcreteAggregate(names);
        Iterator iterator = aggregate.iterator();
        System.out.println("第一个元素是:" + iterator.first());
        System.out.println("最后一个元素是:" + iterator.last());
        
        while(iterator.hasNext()){
            System.out.println("遍历元素:" + iterator.next());
        }
    }
}

在上面的客户端中,首先创建一个聚集类实例,然后调用iterator方法得到一个迭代器角色,得到迭代器角色后,我们就可以进行相关的遍历操作。

执行结果

第一个元素是:James
最后一个元素是:Jack
遍历元素:James
遍历元素:Lucy
遍历元素:Jack

通过上面的实例演示,我们可以得到以下几点列:

  1. 迭代器的本质:控制访问聚合对象中的元素。迭代器能实现“无须暴露聚合对象的内部实现,就能够访问到聚合对象的各个元素的功能”,做到“透明”的访问聚合对象中的元素。注意迭代器能够即“透明”访问,又可以“控制访问”聚合对象。
  2. 迭代器的关键思想:把对聚合对象的遍历访问从聚合对象中分离出来,放入单独的迭代器中,这样聚合对象会变得简单,而且迭代器和聚合对象可以独立地变化和发展,提高系统的灵活性。
  3. 迭代器的动机:在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素;同时这种“透明遍历”也为“同一种算法在多种集合对象上进行操作”提供了可能。将遍历机制抽象为“迭代器对象”为“应对变化中的集合对象”提供了一种优雅的方法

在上面的例子中,既然聚集内部已经提供了相应的操作方法,那么我们还要使用迭代器模式呢?确实,客户端可以根据聚集提供的接口来进行遍历操作,但是,迭代器模式将迭代进行了抽象好,具有更好的扩展性,因为集合对象内部结构多变,使用迭代器模式可将客户端和聚集的责任分割开,使得两者可独立演变,迭代器模式作为中间层,可以吸收变化因素,避免客户端的修改。

内部迭代VS外部迭代

外部迭代器
指的是由客户端来控制迭代下一个元素的步骤,客户端会显式调用迭代器的next()等迭代方法,在遍历过程中向前进行。

内部迭代器
指的是由迭代器自己来控制迭代下一个元素的步骤。因此,如果想要在迭代的过程中完成工作的话,客户端就需要把操作传递给迭代器,迭代器在迭代的时候会在每个元素上执行这个操作,类似于JAVA的回调机制。

总体来说外部迭代器比内部迭代器要灵活一些,因此我们常见的实现多属于主动迭代子。

如果一个聚集的接口没有提供修改聚集元素的方法,这样的接口就是所谓的窄接口。

聚集对象为迭代器提供了一个宽接口,为其它对象提供窄接口来访问。即聚集对象对迭代器是适当公开的,以便迭代器对聚集对象有足够的了解,从而可以进行迭代操作。但是聚集对象应该避免向其它对象提供这些方法,其它对象应该通过迭代器完成这些操作,不能直接操作聚集对象。

在Java中,实现这种双重限制接口的办法就是通过内部类来实现,即将迭代器对象设置为聚集类的内部类,这样迭代器对象可以对聚集类有很好的访问,同时又限制了对外的操作。这种同时保证聚集对象的封装和迭代子功能的实现的方案叫做黑箱实现方案。而这种形式定义的迭代器,又被称为内部迭代器

如下面的例子所示:

public class NameRepository implements Container {
   public String names[] = {"Robert" , "John" ,"Julie" , "Lora"};

   @Override
   public Iterator getIterator() {
      return new NameIterator();
   }

   private class NameIterator implements Iterator {

      int index;

      @Override
      public boolean hasNext() {
         if(index < names.length){
            return true;
         }
         return false;
      }

      @Override
      public Object next() {
         if(this.hasNext()){
            return names[index++];
         }
         return null;
      }        
   }
}

补充知识——静态迭代器和动态迭代器

静态迭代器
静态迭代子由聚集对象创建,并持有聚集对象的一份快照(snapshot),在产生后这个快照的内容就不再变化。客户端可以继续修改原聚集的内容,但是迭代子对象不会反映出聚集的新变化。

静态迭代子的好处是它的安全性和简易性,换言之,静态迭代子易于实现,不容易出现错误。但是由于静态迭代子将原聚集复制了一份,因此它的短处是对时间和内存资源的消耗。

动态迭代器
动态迭代子则与静态迭代子完全相反,在迭代子被产生之后,迭代子保持着对聚集元素的引用,因此,任何对原聚集内容的修改都会在迭代子对象上反映出来。

完整的动态迭代子不容易实现,但是简化的动态迭代子并不难实现。大多数JAVA设计师遇到的迭代子都是这种简化的动态迭代子。为了说明什么是简化的动态迭代子,首先需要介绍一个新的概念:Fail Fast

Fail Fast
如果一个算法开始之后,它的运算环境发生变化,使得算法无法进行必需的调整时,这个算法就应当立即发出故障信号。这就是Fail Fast的含义。

如果聚集对象的元素在一个动态迭代子的迭代过程中发生变化时,迭代过程会受到影响而变得不能自恰。这时候,迭代子就应当立即抛出一个异常。这种迭代子就是实现了Fail Fast功能的迭代子。

Fail Fast在JAVA聚集中的使用
JAVA语言以接口java.util.Iterator的方式支持迭代子模式,Collection接口要求提供iterator()方法,此方法在调用时返还一个Iterator类型的对象。而作为Collection接口的子类型,AbstractList类的内部成员类Itr便是实现Iterator接口的类。

优缺点

优点

  1. 更好的封装性,聚集对象不需要暴露内部实现即可实现访问;
  2. 每一个聚集对象都可以有一个或多个迭代子对象,每一个迭代子的迭代状态可以是彼此独立的。因此,一个聚集对象可以同时有几个迭代在进行之中。
  3. 聚集内容和迭代器的分离,提高了扩展性,即可以使用不同的迭代器来遍历聚集内容。

缺点

  1. 迭代器种类过多,会导致类的个数增加,提高了系统的复杂性;
  2. 在动态迭代器中易发生异常。

参考资料

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容