设计模式学习笔记(十九):迭代器模式

文章目录

  • 1 概述
    • 1.1 引言
    • 1.2 定义
    • 1.3 结构图
    • 1.4 角色
  • 2 典型实现
    • 2.1 步骤
    • 2.2 抽象迭代器
    • 2.3 抽象聚合类
    • 2.4 具体聚合类
    • 2.5 具体迭代器
    • 2.6 客户端
  • 3 实例
  • 4 内部类实现
  • 5 JDK迭代器
    • 5.1 `Iterator`
    • 5.2 `ListIterator`
  • 6 主要优点
  • 7 主要缺点
  • 8 适用场景
  • 9 总结

1 概述

1.1 引言

在软件开发中,有一些类可以存储多个成员对象(元素),这些类通常称为聚合类,对应的对象称为聚合对象。聚合对象拥有两个职责,一个是存储数据,一个是遍历数据,前者是聚合对象的基本职责,后者是可以变化以及分离的,因此,可以将遍历数据的行为从聚合对象中分离出来,封装在一个被称之为“迭代器”的对象中,又迭代器来提供遍历聚合对象内部数据的行为。

1.2 定义

迭代器模式:提供一种方法来访问对象,而不用暴露这个对象的内部表示,别名叫游标。

迭代器模式是一种对象行为型模式。

1.3 结构图

设计模式学习笔记(十九):迭代器模式_第1张图片

1.4 角色

  • Iterator(抽象迭代器):定义了访问和遍历元素的接口,声明了用于遍历数据元素的方法,比如first(获取第一个元素),next(获取下一个元素),hasNext(判断是否有下一个元素),currentItem(获取当前元素)
  • ConcreteIterator(具体迭代器):实现了抽象迭代器,完成对聚合对象的遍历,同时在具体迭代器中通过游标来记录聚合对象中所处的当前位置,通常游标是一个非负整数
  • Aggregate(抽象聚合类):用于存储和管理元素对象,声明一个createIterator方法创建一个迭代器对象,充当抽象迭代器的工厂角色
  • ConcreteAggregate(具体聚合类):实现了抽象聚合类中的createIterator方法,返回一个具体迭代器实例

2 典型实现

2.1 步骤

  • 定义抽象迭代器:一般为接口,声明具体迭代器的方法
  • 定义抽象聚合类:一般为接口,包含管理聚合元素的方法以及创建抽象迭代器的方法
  • 定义具体聚合类:内部创建集合存储聚合元素,在创建迭代器方法中将集合作为构造方法参数注入到具体迭代器中并返回该具体迭代器
  • 定义具体迭代器类:实现抽象迭代器的方法,一般包含一个来自具体聚合类的集合引用以及一个表示元素位置的整型的游标

2.2 抽象迭代器

interface Iterator
{
    String first();
    String next();
    boolean hasNext();
    String currentItem();
}

2.3 抽象聚合类

interface Aggregate
{
    Iterator createIterator();
    void add(String s);
}

创建迭代器通过createIteratoradd用于增添元素。

2.4 具体聚合类

class ConcreteAggregate implements Aggregate
{
    List<String> list = new ArrayList<>();
    @Override
    public Iterator createIterator()
    {
        return new ConcreteIterator(list);
    }
    @Override
    public void add(String s)
    {
        list.add(s);
    }
}

在创建迭代器方法中,通过构造方法把集合对象注入到具体迭代器中。

2.5 具体迭代器

class ConcreteIterator implements Iterator
{
    private int cursor;
    private List<String> list;
    public ConcreteIterator(List<String> list)
    {
        this.list = list;
        this.cursor = -1;
    }

    @Override
    public String first()
    {
        return list.size() > 0 ?
        list.get(cursor = 0) :
        null;
    }

    @Override
    public String next()
    {
        return list.get(
            cursor + 1 < list.size() ? ++cursor : cursor
        );
    }

    @Override
    public boolean hasNext()
    {
        return cursor+1 < list.size();
    }

    @Override
    public String currentItem()
    {
        return list.get(cursor);
    }
}

具体迭代器中包含了一个游标,用于记录当前访问的位置。构造方法中将游标初始化为-1而不是初始化为0,这样第一次使用next时便会访问第一个元素。

2.6 客户端

public static void main(String[] args) 
{
    Aggregate aggregate = new ConcreteAggregate();
    aggregate.add("111");
    aggregate.add("222");
    aggregate.add("jksdfjksdjkfk");
    aggregate.add("m,xcvm,xcm,v");
    Iterator iterator = aggregate.createIterator();
    while(iterator.hasNext())
    {
        System.out.println(iterator.next());
    }
}

客户端针对抽象聚合类以及抽象迭代器编程,通过聚合对象创建迭代器后,首先使用haxNext判断,接着使用next获取其中元素。

3 实例

设计一个系统对客户数据以及商品数据进行遍历,使用迭代器模式进行设计。

这个例子和上面的其实差不多,不过是反向迭代器方法,另外为了更贴近实际环境使用,抽象迭代器以及聚合类都使用了泛型设计:

  • 抽象迭代器:Iterator
  • 抽象聚合类:AbstarctList
  • 具体聚合类:ObjectList
  • 具体迭代器:ObjectIterator
  • 模拟产品以及顾客类:Product+Customer

首先设计抽象迭代器:

interface Iterator<T>
{
    T next();
    boolean hasNext();
    String nextName() throws UnsupportedOperationException;
    boolean hasNextName() throws UnsupportedOperationException;
    void setProduct();
}

nextName()以及hasNextName()方法是对Customer类型生效的,对于Product会抛出异常。setProduct()表示设置聚合元素的类型为Product

接着是抽象聚合类的设计:

interface AbstractList<T>
{
    Iterator<T> iterator();
    Iterator<T> reversedIterator();
    void add(T s);
}

添加了一个反向迭代器实现。

然后是具体聚合类的设计:

class ObjectList<T> implements AbstractList<T>
{
    List<T> list = new ArrayList<>();
    @Override
    public Iterator<T> iterator()
    {
        return new ObjectIterator<T>(list,false);
    }
    @Override
    public void add(T s)
    {
        list.add(s);
    }
    @Override
    public Iterator<T> reversedIterator()
    {
        return new ObjectIterator<T>(list,true);
    }
}

内部还有一个List存储聚合元素,iterator返回正向迭代器,构造方法里面的布尔值表示是否为反向迭代器,reversedIterator表示返回一个单向迭代器,与正向的唯一不同就是传入具体迭代器的构造方法中的布尔值。true表示是反向迭代器,否则是正向。

最后是具体迭代器类:

class ObjectIterator<T> implements Iterator<T>
{
    private int cursor;
    private List<T> list;
    private boolean reversed;
    private boolean isProduct = false;
    public ObjectIterator(List<T> list,boolean reversed)
    {
        this.list = list;
        this.reversed = reversed;
        this.cursor = (reversed ? list.size() : -1);
    }

    @Override
    public void setProduct()
    {
        isProduct = true;
    }

    @Override
    public T next()
    {
        return list.get(
            reversed ? 
            ( cursor - 1 >= 0 ? --cursor : cursor ) :
            ( cursor + 1 < list.size() ? ++cursor : cursor )
        );
    }

    @Override
    public boolean hasNext()
    {
        return reversed ?
        cursor-1 >= 0 :
        cursor+1 < list.size();
    }

    @Override
    public String nextName() throws UnsupportedOperationException
    {
        if(isProduct)
            throw new UnsupportedOperationException("商品迭代器不支持该操作");
        return ((Customer)next()).getName();
    }

    @Override
    public boolean hasNextName() throws UnsupportedOperationException
    {
        if(isProduct)
            throw new UnsupportedOperationException("商品迭代器不支持该操作");
        return hasNext();
    }
}

构造方法中初始化聚合元素以及一个布尔值reversed,表示是否为反向迭代器,游标根据reversed设置为-1list.size()。对于next以及hasNext方法,都需要判断是否为反向迭代器,返回对应的结果。对于nextName以及hasNextName,由于这两个方法仅对Customer类生效,因此如果是Product类直接抛出异常。

其他:

class Product
{
    private String id;
    private int num;

    public Product(){}

    public Product(String id,int num) {
        this.id = id;
        this.num = num;
    }

    public String getId() {
        return this.id;
    }

    public int getNum() {
        return this.num;
    }

    @Override
    public String toString()
    {
        return "商品id:"+id+"\t商品数量:"+num;
    }
}

class Customer
{
    private String id;
    private String name;

    public Customer(String id,String name)
    {
        this.id = id;
        this.name = name;
    }

    public String getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

    @Override
    public String toString()
    {
        return "顾客id:"+id+"\t顾客名字:"+name;
    }
}

测试类:

public static void main(String[] args) 
{
    Customer customer1 = new Customer("id1","name1");
    Customer customer2 = new Customer("id2","name2");
    Customer customer3 = new Customer("id3","name3");
    AbstractList<Customer> customerList = new ObjectList<>();
    customerList.add(customer1);
    customerList.add(customer2);
    customerList.add(customer3);

    Iterator<Customer> customerIterator = customerList.iterator();
    while(customerIterator.hasNext())
        System.out.println(customerIterator.next());
    customerIterator = customerList.reversedIterator();
    while(customerIterator.hasNext())
        System.out.println(customerIterator.next());
    System.out.println();

    customerIterator = customerList.iterator();
    while(customerIterator.hasNextName())
        System.out.println(customerIterator.nextName());
    customerIterator = customerList.reversedIterator();
    while(customerIterator.hasNextName())
        System.out.println(customerIterator.nextName());
    System.out.println();
        
    Product product1 = new Product("product id 1",1);
    Product product2 = new Product("product id 2",2);
    Product product3 = new Product("product id 3",3);
    AbstractList<Product> productList = new ObjectList<>();
    productList.add(product1);
    productList.add(product2);
    productList.add(product3);

    Iterator<Product> productIterator = productList.iterator();
    while(productIterator.hasNext())
        System.out.println(productIterator.next());
    productIterator = productList.reversedIterator();
    while(productIterator.hasNext())
        System.out.println(productIterator.next());
    System.out.println();
    try
    {
        productIterator = productList.iterator();
        productIterator.setProduct();
        while(productIterator.hasNextName())
            System.out.println(productIterator.nextName());
    }
    catch(Exception e)
    {
        e.printStackTrace();
    }
}

首先创建了三个Customer,接着添加到customerList中,从customerList中的iterator获取正向迭代器以及从reversedIterator获取正向迭代器,两种遍历方式的迭代器可以使用同样的语句实现遍历:

while(customerIterator.hasNext())
	System.out.println(customerIterator.next());

对于Product,由于hasNextName以及nextName声明了抛出异常,因此测试输出如下:

设计模式学习笔记(十九):迭代器模式_第2张图片

4 内部类实现

上面的例子可以看到在具体聚合类以及具体迭代器之间存在关联关系,具体迭代器需要维持一个对具体聚合对象(或里面的集合)的引用,除了使用关联关系外,还可以将迭代器设计为聚合类的内部类,比如JDK中的AbstractList

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
	//...
	private class Itr implements Iterator<E> {
		//...
	}
	//...
}

使用类似的方式重新设计上面的例子:

class ObjectList<T> implements AbstractList<T>
{
    List<T> list = new ArrayList<>();
    @Override
    public Iterator<T> iterator()
    {
        return new ObjectIterator(false);
    }
    @Override
    public void add(T s)
    {
        list.add(s);
    }
    @Override
    public Iterator<T> reversedIterator()
    {
        return new ObjectIterator(true);
    }

    private class ObjectIterator implements Iterator<T>
    {
        private int cursor;
        private boolean reversed;
        private boolean isProduct = false;
        public ObjectIterator(boolean reversed)
        {
            this.reversed = reversed;
            this.cursor = (reversed ? list.size() : -1);
        }
        //...
    }
}

改变的就是具体迭代器的构造方法,不需要注入聚合对象了,另外也取消了泛型的声明。

5 JDK迭代器

5.1 Iterator

JDK(OpenJDK11.0.2)中的Collection方法摘录如下:

public interface Collection<E> extends Iterable<E> {
	//...
	Iterator<E> iterator();
	//...
}

该方法用于返回一个迭代器,以便遍历聚合类中的元素,其中Iterator定于如下:

public interface Iterator<E> {
	boolean hasNext();
	E next();
	default void remove() {
		//...
	}
	default void forEachRemaining(Consumer<? super E> action) {
		//...
	}
}

其中:

  • hasNext:用于判断聚合对象是否存在下一个元素,需要在调用next之前调用
  • next:将下标移至下一个元素,并返回游标所越过的那个元素的引用,也就是获取下一个元素
  • remove:删除上一次next的返回的元素
  • forEachRemaining:用于对剩余元素进行的操作,比如一个集合有10个元素,使用迭代器遍历了前5个,则使用该方法会遍历剩下的元素,也就是后5个

Java迭代器原理如图:
设计模式学习笔记(十九):迭代器模式_第3张图片
第一个next被调用时,迭代器游标由0号位置移到1号位置,也就是移动到元素1以及元素2之间,接着返回游标越过的元素,也就是元素1。下一次调用next时,游标继续移动,从1号位置移动到2号位置,并返回越过的元素,也就是元素2。对于remove来说,删除上一次next返回的元素,也就是如果此时调用remove会删除元素2。

也就是在调用remove之前至少需要调用一次next,如果不调用next的话,会抛出异常:
在这里插入图片描述

5.2 ListIterator

JDK中的List接口除了继承Collection接口的iterator外,还增加一个listIterator,专门用于创建ListIterator类型的迭代器。用于遍历集合已经有了Iterator,但是这个迭代器只能用于正向遍历,而ListIterator的出现能解决逆向遍历的问题,因为其中提供了hasPrevious以及previous等方法。例子如下:

public static void main(String[] args) {
    List<String> s = new ArrayList<>();
    s.add("1111");
    s.add("2222");
    s.add("3333");
    ListIterator<String> it = s.listIterator();
    while(it.hasNext())
        System.out.println(it.next());
    System.out.println();
    while(it.hasPrevious())
        System.out.println(it.previous());
}

实现完整的逆向遍历时,需要先将游标移动到末尾,也就是不断调用next直到末尾,才能实现调用previous进行逆向遍历。

6 主要优点

  • 多种遍历方式:支持以不同方式遍历聚合对象,在同一聚合对象上可以定义多种遍历方法,只需要用一个不同的聚合器替换原来的迭代器即可改变遍历算法
  • 简化聚合类:原有的聚合对象不需要再自行提供数据遍历方法
  • 满足OCP:由于引入了抽象层,增加新的聚合类以及迭代器类都很方便,无须修改源码

7 主要缺点

  • 复杂度增加:迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,增加了复杂性
  • 抽象迭代器较难设计:考虑到以后的扩展,抽象迭代器的设计难度可能非常大,比如JDK的内置迭代器Iterator就无法实现逆向遍历,设计一个考虑全面的抽象迭代器并不是一件容易的事

8 适用场景

  • 访问一个聚合对象的内容而无须暴露它的内部表示。将聚合对象的访问与内部数据存储分离,使得访问聚合对象时无须了解内部实现细节
  • 需要为一个聚合对象提供多种遍历方式
  • 为遍历不同的聚合结构提供一个统一的接口,在该接口的实现类为不同的聚合结构提供不同的遍历方式,而客户端可以一致性地操作该接口

9 总结

在这里插入图片描述

如果觉得文章好看,欢迎点赞。

同时欢迎关注微信公众号:氷泠之路。

在这里插入图片描述

你可能感兴趣的:(设计模式,设计模式)