关于设计模式:设计模式学习笔记十九迭代器模式

1 概述

1.1 引言

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

1.2 定义

迭代器模式:提供一种办法来拜访对象,而不必裸露这个对象的外部示意,别名叫游标。

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

1.3 结构图

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<T>
  • 形象聚合类:AbstarctList<T>
  • 具体聚合类:ObjectList<T>
  • 具体迭代器:ObjectIterator<T>
  • 模仿产品以及顾客类: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申明了抛出异样,因而测试输入如下:

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迭代器原理如图:

第一个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 总结

如果感觉文章难看,欢送点赞。

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理