乐趣区

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

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 总结

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

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

退出移动版