设计模式(19)行为型模式 - 迭代器模式

前言

温故而知新

先复习前面学的行为型模式

  • 模板方法模式:父类定义一个算法骨架,一些特定的步骤延迟到子类实现(请客流程:点单 - 》吃 - 》买单,具体吃什么延迟到子类实现)
  • 命令模式:命令封装成命令对象,命令对象聚合命令执行者,请求者仅需调用命令对象即可完成命令,将命令的请求者与执行者解耦合(遥控器的按钮是命令对象,遥控器仅需按下相应的按钮即可完成命令执行 - 开关灯)
  • 访问者模式:将施加与某种对象结构上的元素之上的操作封装成访问者,即将访问者隔离开这个对象结构,元素角色拥有接收访问者操作的接口,新增访问者并不会改动对象结构(购物车是这个对象结构,内部存放着商品(元素角色),顾客可以查看商品的质量,收银员可以查看商品的价格)

接下来,学习一个熟悉的模式:迭代器模式


现实中的问题

现实中,对于元素的存放,常常是使用各种列表、数组等将元素存放在一起,我们也经常需要访问集合中的这些元素

在这里插入图片描述

描述上面的目录结构,是不是首先想到用一个集合存放?

一般的做法是把集合的创建和遍历放在一个类中,这种做法简单,但是不利于系统的扩展,如果要更换遍历方式,就需要改动类的源代码,而且也会造作类里遍历方法的臃肿

既然不要放在集合类中,可以自己在用户类中选择遍历方式?

这种方法简单,但是会增加客户类的负担,且会暴露集合类的内部信息,一般都要求客户类黑箱化

使用迭代器模式就可以很好的解决,在集合类和客户类中加一个迭代器,分离集合的创建和遍历

迭代器大家也都很熟悉,遍历集合时都会用到,那些迭代器就是使用了迭代器模式(后续会分析)


迭代器模式

为什么要有迭代器模式?

一个聚合对象,如一个列表(List)或者一个集合(Set),应该提供一种方法来让别人可以访问它的元素,而又不需要暴露它的内部结构

针对不同的需要,可能还要以不同的方式遍历整个聚合对象,但是我们并不希望在聚合对象的抽象层接口中充斥着各种不同遍历的操作

这就是迭代器模式所要解决的问题

在迭代器模式中,提供一个外部的迭代器来对聚合对象进行访问和遍历,迭代器定义了一个访问该聚合元素的接口,并且可以跟踪当前遍历的元素,了解哪些元素已经遍历过而哪些没有

模式定义

迭代器模式(Iterator Pattern) :提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示,其别名为游标(Cursor)。迭代器模式是一种对象行为型模式

模式结构

在这里插入图片描述

迭代器模式的角色:

  • 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
  • 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例(createIterator)。
  • 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
  • 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置
  • 聚合对象:在聚合角色中存放的对象,在实际使用中一般是Object

模式实现案例

对于前面的Linux目录,可以用迭代器模式描述

这里不自己实现迭代器,使用Java提供的迭代器接口

有三层目录,第一层根目录不描述,描述第二层目录,具体目录是root和usr
第三层目录描述root的下一级目录:Desktop、Maildir;usr的下层目录:bin、lib
root目录存放下层目录使用数组,usr存放下层目录使用List列表

在这里插入图片描述

package com.company.Behavioral.Iterator;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;


//聚合对象,目录对象
class Directory{
    //名字
    private String name;

    public Directory(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

}
//抽象迭代器角色就不写了,通过实现Java提供的Iterator实现自己的迭代器

//具体迭代器角色:迭代以数组方式存放的目录
class ArrayIterator implements Iterator{
    //需要知道聚合对象以怎样的方式存放在聚合角色中
    //当目录角色以数组的方式存放
    private Directory[] directories;
    //遍历的位置
    private int position = 0;

    public ArrayIterator(Directory[] directories) {
        this.directories = directories;
    }

    @Override
    public boolean hasNext() {
        //位置到了数组尾或者遍历到空
        if (position >= directories.length || directories[position] == null){
            return false;
        }else {
            return true;
        }
    }

    @Override
    public Object next() {
        //如果还有下一个
        if (hasNext()) {
            Directory directory = directories[position];
            position++;
            return directory;
        }
        return null;
    }
}
//具体迭代角色:迭代以List存放的目录
class ListIterator implements Iterator{
    //当目录以List存放
    private List<Directory> directoryList;
    //list索引
    private int index = -1;

    public ListIterator(List<Directory> directoryList) {
        this.directoryList = directoryList;
    }

    @Override
    public boolean hasNext() {
        //索引到了列表尾
        if (index >= directoryList.size() - 1){
            return false;
        }
        else {
            return true;
        }
    }


    @Override
    public Object next() {
        //因为index=-1,先+1,再取值
        index++;
        return directoryList.get(index);
    }
}
//抽象聚合角色
interface SecondDirectory{
    //设置目录的名字
    public String getName();
    //添加第三层目录
    public void addSecondDirectory(String name);
    //返回一个迭代器去遍历
    public Iterator createIterator();
}
//具体聚合角色:第二层目录中的管理员角色目录
class RootDirectory implements SecondDirectory{
    //管理员角色目录通过数组存放下一级的目录
    private Directory[] directories;
    //保存当前数组对象个数
    private int numDirectories = 0;

    public RootDirectory() {
        //假设存放5个directory数组
        this.directories = new Directory[5];
        addSecondDirectory("Desktop");
        addSecondDirectory("Maildir");
    }

    @Override
    public String getName() {
        return "root";
    }

    @Override
    public void addSecondDirectory(String name) {
        //添加目录数组
        Directory directory = new Directory(name);
        directories[numDirectories] = directory;
        numDirectories++;
    }

    @Override
    public Iterator createIterator() {
        //创建数组迭代器
        return new ArrayIterator(directories);
    }
}
//具体聚合角色:第二层目录中的usr目录
class UsrDirectory implements SecondDirectory{
    //以List方式存放目录对象
    List<Directory> directoryList;

    public UsrDirectory() {
        this.directoryList = new ArrayList<Directory>();
        addSecondDirectory("bin");
        addSecondDirectory("lib");
    }

    @Override
    public String getName() {
        return "usr";
    }

    @Override
    public void addSecondDirectory(String name) {
        Directory directory = new Directory(name);
        directoryList.add(directory);
    }
    @Override
    public Iterator createIterator() {
        return new ListIterator(directoryList);
    }
}
class OutPutImpl{
    //第二层目录的集合
    List<SecondDirectory> directories;
    public OutPutImpl(List<SecondDirectory> directories){
        this.directories = directories;
    }
    //遍历第二层目录,调用printThirdDirectory,输出第二层目录和第三层目录
    public void printDirectory(){
        //从directories中取出所有的第二层目录,直接使用List中的Iterator
        Iterator<SecondDirectory> iterator = directories.iterator();
        while (iterator.hasNext()){
            SecondDirectory secondDirectory = iterator.next();
            System.out.println("======="+secondDirectory.getName()+"==========");
            printThirdDirectory(secondDirectory.createIterator());
        }
    }
    //输出第三层目录
    public void printThirdDirectory(Iterator iterator){
        while (iterator.hasNext()){
            Directory next = (Directory) iterator.next();
            System.out.println(next.getName());
        }
    }
}

public class Client {
    public static void main(String[] args) {

        List<SecondDirectory> secondDirectories = new ArrayList<>();
        //创建第二层目录
        RootDirectory rootDirectory = new RootDirectory();
        UsrDirectory usrDirectory = new UsrDirectory();
        //将第二层的具体目录加入一个List
        secondDirectories.add(rootDirectory);
        secondDirectories.add(usrDirectory);
        //遍历输出
        OutPutImpl outPut = new OutPutImpl(secondDirectories);
        outPut.printDirectory();
    }
}

在这里插入图片描述

运行后,两种存放方法都可以遍历打印
定义了两种具体迭代器用于处理数组、列表


模式分析

  • 聚合角色是一个管理和组织数据对象的数据结构,在迭代器模式中应用了工厂方法模式,聚合角色充当了工厂类,而迭代器充当了产品类,一种具体的聚合角色针对一种迭代器
  • 将遍历聚合角色中数据对象的行为提出取出来,封装到一个迭代器中,仅让聚合角色负责存储数据对象,通过专门的迭代器遍历特定的聚合对象的内部数据对象,是 “单一职责原则”

模式优缺点

优点

  • 它支持以不同的方式遍历一个聚合对象
  • 迭代器简化了聚合类
  • 在同一个聚合上可以有多个遍历
  • 在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足“开闭原则”的要求
  • 聚合角色仅负责存储数据对象,符合“单一职责原则”

缺点

既然是单一职责原则,且解耦合,付出的代价就是类增加

由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性


适用环境

  • 访问一个聚合对象的内容而无须暴露它的内部表示
  • 需要为聚合对象提供多种遍历方式。
  • 为遍历不同的聚合结构提供一个统一的接口

List中的迭代器模式

其实,学过Java集合都知道,集合都会提供一个方法iterator,提供对应的迭代器

我们可以看看源码中迭代器模式的实现

List接口:

在这里插入图片描述

List接口是所有列表类的公共接口,所有的 列表类必须实现该接口

它提供了一个方法:Iterator<E> iterator();返回一个Iterator

List接口就是抽象聚合角色:Aggregate

List下有很多实现子类,例如ArrayList、LinkedList
我们来看看他们的源码:

ArrayList:
在这里插入图片描述

ArrayList中实现了iterator方法,返回了Itr对象
Itr对象是什么?
它是一个具体迭代器角色,在ArrayList中,具体迭代器角色是一个内部类

在这里插入图片描述

是不是就是具体迭代器角色,和我们案例中实现的一样

而ArrayList就是具体聚合角色,以内部类的方式组合了具体迭代器角色

Iterator接口是抽象迭代器角色,只定义了一些方法规范:
在这里插入图片描述

现在理清了ArrayList中迭代器模式的实现了,还有个数据对象,它是什么呢?
在这里插入图片描述

它是Object对象,名字是elementData 数据元素

在迭代器Itr中的next方法可以看到它的使用
(这里迭代器是聚合角色的内部类,直接.this即可获得数据对象)
在这里插入图片描述
cursor是指向下一元素的指针,当指针大于列表大小(size),报错,当指针大于元素数组真实大小时,报错,指针+1,返回当前元素

和我们的next方法实现类似(List内部也是维护一个数组,实现的功能)

实际上,Java集合中,各种集合数据结构都使用了迭代器模式

集合的最顶级父类:Collection接口都定义了iterator方法,且继承了Iterable接口


总结

  • 迭代器模式是将聚合角色的数据对象存储和遍历分割开,将遍历功能封装到特定的迭代器角色中,实现了在不暴露聚合类的情况下能够轻易的扩展遍历聚合角色的方式
  • 迭代器模式包含五个角色:抽象迭代器、具体迭代器、抽象聚合类、具体聚合类、数据对象
  • 具体聚合类实现了抽象聚合类定义的方法,以工厂方法模式创建了对应的具体迭代器;具体迭代器实现了抽象迭代器接口,完成了对聚合对象的遍历;数据对象是存放在聚合类中的数据,一般是Object类
  • 迭代器模式优点:支持以不同的方式遍历聚合对象,简化聚合类,便于扩展遍历方式;缺点:类的个数成对增加,增加了系统的复杂性
  • 迭代器模式适用于:访问一个聚合对象无须暴露它的内部结构;需要为聚合对象提供多种遍历方式;为遍历不同的聚合结构提供统一接口
  • 迭代器模式是使用较多的模式,在Java集合中都使用到了迭代器模式
发布了121 篇原创文章 · 获赞 31 · 访问量 7869

猜你喜欢

转载自blog.csdn.net/key_768/article/details/105464739