生活中的设计模式之迭代器模式

定义

Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

提供一种方法访问集合中的元素,而不需要暴露集合底层的数据结构。

实列

出国旅游时,为了避免把时间浪费在熟悉该国各个景点的地理位置以及规划景点的游玩次序,这时我们可以选择熟悉该国景点的导游,按他规划好的次序游玩该国的各景点,这样我们就可以不用熟悉该国各个景点的地理位置。
国家与景点之间是集合与元素的关系,导游是国家提供的服务,而我们只和导游交互而不直接与国家交互,这种关系和迭代器模式各个角色之间的关系非常相似。
迭代器模式在程序中最典型的应用是在Java集合框架中,其中每个具体的集合类如LinkedList、ArrayList都会暴露一个iterator操作,客户端使用迭代器遍历集合中的元素就可以不用关心该集合底层的数据结构。

故事

前不久,我入职了一家开发高考刷题软件的教育公司,刚入职就领到了一个需求:他们希望我将试卷中的题目按默认顺序依次转化到PDF文件中,供用户下载。
原来的程序中有一个试卷类Paper,它里面有一个getQuestion操作可以根据次序获取试卷中的题目。
我想这很简单,新建一个Client类使用for循环取出试卷中的题然后存储到PDF中便可以了。
可是,后来他们又希望我再写一个单独的模块将试卷中的题目按随机的顺序转化到word文件中,而且还说遍历顺序不是固定,以后可能要按难度遍历试卷。
伪代码如下:

/**模拟遍历题目 */
public class Client {

    public static void main(String[] args) {
        IPaper paper = new Paper();
        for (int i = 0; i < paper.getSize(); i++) {
            Question question = paper.getQuestion(i);
            appendToPDF(question);
        }
    }
}

问题

故事中,试卷和题目是集合与元素之间的关系,主要的需求是支持不同场景下按不同的次序遍历集合中的元素。
但是,如果像上面一样由客户端使用for循环来遍历,那么多个客户端的遍历算法就会重复,而且客户端还需要了解集合底层的实现结构,如果是简单的列表结构倒是可以依次获取,但如果是树结构或者图结构那么客户端的遍历算法就会变得很复杂。
这会造成客户端与集合底层的实现结构耦合,而且不便于扩展。所以,有没有一种方式即可以将客户端与具体的遍历行为分离达到解藕、复用的目的,又能支持多种遍历次序的扩展?这便是迭代器模式。

方案

迭代器模式通过一个被称之为迭代器的对象,将如何遍历集合元素的逻辑封装到这个对象中,由它向客户端提供获取下一个元素的操作,这样就对客户端隐藏了集合的底层实现结构。
而且,这个对象又实现自一个迭代器接口,这样在新增遍历方式时,就可以实现该接口而不需要修改客户端的代码就可以扩展新的遍历方式。

实现

接下来,我们使用迭代器模式实现一下故事中的程序。

首先,为试卷声明一个迭代器接口,它有两个操作hasNext、next。


/**试卷迭代器*/
public interface Iterator {

    public boolean hasNext();

    public Question next();
}


然后,创建两个实现迭代器接口的具体迭代器:默认迭代器、随机迭代器,它们封装了遍历题目的逻辑。

/**具体迭代器*/
public class DefaultIterator implements Iterator{
    protected Paper paper;
    protected int index=0;
    public ConcreteIterator(Paper paper){
        this.paper = paper;
    }
    @Override
    public boolean hasNext() {
        if(index<paper.getSize()){
            return true;
        }else{
            return false;
        }
    }

    @Override
    public Question next() {
        if(index<paper.getSize()){
            Question question = paper.getQuestion(index);
            index++;
            return question;
        }
        return null;

    }
}

/**随机迭代器*/
public class RandomIterator implements Iterator{......}

现在,我们还要为试卷接口添加两个创建具体迭代器的操作。如果你希望期望创建迭代器更具有动态性,那么可以考虑使用工厂模式封装创建行为。

/**试卷接口*/
public interface IPaper {
    /**创建默认迭代器*/
    public  Iterator createDefaultIterator();
    /**创建随机迭代器*/
    public  Iterator createRandomIterator();
    /**获取指定位置的题目*/
    public Question getQuestion(int index);
    /**获取题目数量*/
    public int getSize();
}

/**试卷*/
public class Paper implements IPaper{
    /**题目列表*/
    protected List<Question> questionList = new LinkedList<>();
    
    @Override
    public Iterator createDefaultIterator() {
        return new DefaultIterator(this);
    }

    @Override
    public Iterator createRandomIterator() {
        return new RandomIterator(this);
    }

    public void addQuestion(Question question){
        questionList.add(question);
    }


    public Question getQuestion(int index){
       return questionList.get(index);
    }

    public int getSize(){
        return questionList.size();
    }


}

最后,让我们在看看客户端如何使用不同迭代器遍历试卷中的题目。


/**模拟遍历题目*/
public class Client {

    public static void main(String[] args) {
        Paper paper = new Paper();
        //使用模式迭代器
        Iterator defaultIterator = paper.createDefaultIterator();
        while (defaultIterator.hasNext()){
            Question question = defaultIterator.next();
            appendToPDF(question);
        }
        //使用随机迭代器
        Iterator randomIterator = paper.createRandomIterator();
        while (randomIterator.hasNext()){
            Question question = randomIterator.next();
            appendToPDF(question);
        }

    }
}

结构

avatar

抽象迭代器角色(Iterator):它声明了遍历集合的操作。

具体迭代器角色(ConcreteIterator):它持有一个指向集合对象的引用,并实现了遍历集合的特定算法。

抽象聚合角色(Aggregate):它声明了创建具体迭代器的操作,这部分职责类似于迭代器的简单工厂。

具体聚合角色(ConcreteAggregate):它通常是一个集合,负责创建具体的迭代器。

总结

当集合对象底层的数据结构过于复杂,如果我们期望屏蔽客户端操作集合时的复杂度,那么我们应该考虑使用迭代器模式。
它可以解藕客户端和特定数据结构的依赖,这样即使集合底层数据结构变化,也不影响客户端。

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