研磨设计模式 之 迭代器模式(Iterator)3——跟着cc学设计系列

14.3  模式讲解

14.3.1  认识迭代器模式

(1)迭代器模式的功能

迭代器模式的功能主要在于提供对聚合对象的迭代访问。迭代器就围绕着这个“访问”做文章,延伸出很多的功能来。比如:

  • 以不同的方式遍历聚合对象,比如向前、向后等
  • 对同一个聚合同时进行多个遍历
  • 以不同的遍历策略来遍历聚合,比如是否需要过滤等
  • 多态迭代,含义是:为不同的聚合结构,提供统一的迭代接口,也就是说通过一个迭代接口可以访问不同的聚合结构,这就叫做多态迭代。上面的示例就已经实现了多态迭代,事实上,标准的迭代模式实现基本上都是支持多态迭代的。

但是请注意:多态迭代可能会带来类型安全的问题,可以考虑使用泛型。

(2)迭代器模式的关键思想

聚合对象的类型很多,如果对聚合对象的迭代访问跟聚合对象本身融合在一起的话,会严重影响到聚合对象的可扩展性和可维护性。

因此迭代器模式的关键思想就是把对聚合对象的遍历和访问从聚合对象中分离出来,放入到单独的迭代器中,这样聚合对象会变得简单一些;而且迭代器和聚合对象可以独立的变化和发展,会大大加强系统的灵活性。

(3)内部迭代器和外部迭代器

所谓内部迭代器,指的是由迭代器自己来控制迭代下一个元素的步骤,客户端无法干预,因此,如果想要在迭代的过程中完成工作的话,客户端就需要把操作传给迭代器,迭代器在迭代的时候会在每个元素上执行这个操作,类似于Java的回调机制。

所谓外部迭代器,指的是由客户端来控制迭代下一个元素的步骤,像前面的示例一样,客户端必须显示的调用next来迭代下一个元素。

总体来说外部迭代器比内部迭代器要灵活一些,因此我们常见的实现多属于外部迭代器,前面的例子也是实现的外部迭代器。

(4)Java中最简单的统一访问聚合的方式

       如果只是想要使用一种统一的访问方式来访问聚合对象,在Java中有更简单的方式,简单到几乎什么都不用做,利用Java 5以上版本本身的特性即可。

       但是请注意,这只是从访问形式上一致了,但是也暴露了聚合的内部实现,因此并不能算是标准迭代器模式的实现,但是从某种意义上说,可以算是隐含的实现了部分迭代器模式的功能。

       那么怎么做呢?

为了简单,让我们回到没有添加任何迭代器模式的情况下。很简单,只要让聚合对象中的结合实现范型即可,示例如下:

public class PayManager{

    private List<PayModel> list = new ArrayList<PayModel>();

    /**

     * 获取工资列表

     * @return 工资列表

     */

    public List<PayModel> getPayList(){

       return list;

    }

 

 

}

这样一来,客户端的代码就可以改成使用增强的for循环来实现了,对于数组、范型的集合都可以采用一样的方法来实现了,从代码层面上看,就算是统一了访问聚合的方式了,修改后的客户端代码如下:

public class Client {

    public static void main(String[] args) {

       //访问集团的工资列表

       PayManager payManager= new PayManager();

       //先计算再获取

       payManager.calcPay();

 

       Collection<PayModel> payList = payManager.getPayList();

       System.out.println("集团工资列表:");

 

//     Iterator it = payList.iterator();

//     while(it.hasNext()){

//         PayModel pm = (PayModel)it.next();

//         System.out.println(pm);

//     }

 

这两段新的访问方式是否一样呢?

 

       for(PayModel pm : payList){

           System.out.println(pm);

       }

       //访问新收购公司的工资列表

       SalaryManager salaryManager = new SalaryManager();

        //先计算再获取

       salaryManager.calcSalary();

       PayModel[] pms = salaryManager.getPays();

       System.out.println("新收购的公司工资列表:");

//     for(int i=0;i<pms.length;i++){

//         System.out.println(pms[i]);

//     }

       for(PayModel pm : pms){

           System.out.println(pm);

       }

    }

}

14.3.2  使用Java的迭代器

大家都知道,在java.util包里面有一个Iterator的接口,在Java中实现迭代器模式是非常简单的,而且java的集合框架中的聚合对象,基本上都是提供了迭代器的。

下面就来把前面的例子改成用Java中的迭代器实现,一起来看看有些什么改变。

  • 不再需要自己实现的Iterator接口,直接实现java.util.Iterator接口就可以了,所有使用自己实现的Iterator接口的地方都需要修改过来
  • Java中Iterator接口跟前面自己定义的接口相比,需要实现的方法是不一样的
  • 集合已经提供了Iterator,那么CollectionIteratorImpl类就不需要了,直接去掉

好了,还是一起来看看代码吧。

(1)PayModel类没有任何变化,就不示例了

(2)抽象的Aggregate类就是把创建迭代器方法返回的类型换成java中的Iterator了。示例代码如下:

import java.util.Iterator;

public abstract class Aggregate {

    public abstract Iterator createIterator();

}

(3)原来的ArrayIteratorImpl类,实现的接口改变了,实现代码也需要跟着改变,示例代码如下:

/**

 * 用来实现访问数组的迭代接口

 */

public class ArrayIteratorImpl implements Iterator{

    /**

     * 用来存放被迭代的聚合对象

     */

    private SalaryManager aggregate = null;

    /**

     * 用来记录当前迭代到的位置索引

     */

    private int index = 0;

    public ArrayIteratorImpl(SalaryManager aggregate){

       this.aggregate = aggregate;

    }

 

    public boolean hasNext() {

       //判断是否还有下一个元素

       if(aggregate!=null && index<aggregate.size()){

           return true;

       }

       return false;

    }

    public Object next() {

       Object retObj = null;

       if(hasNext()){

           retObj = aggregate.get(index);

           //每取走一个值,就把已访问索引加1

           index++;

       }

       return retObj;

    }

    public void remove() {

       //暂时可以不实现   

    }

}

(4)对于PayManager类,在实现创建迭代器的方法上发生了改变,不再使用自己实现的迭代器,改用java的集合框架实现的迭代器了。示例代码如下:

public class PayManager extends Aggregate{

    private List<PayModel> list = new ArrayList<PayModel>();

    public List<PayModel> getPayList(){

       return list;

    }

    public void calcPay(){

       //计算工资,并把工资信息填充到工资列表里面

       //为了测试,做点数据进去

       PayModel pm1 = new PayModel();

       pm1.setPay(3800);

       pm1.setUserName("张三");

 

       PayModel pm2 = new PayModel();

       pm2.setPay(5800);

       pm2.setUserName("李四");

 

       list.add(pm1);

       list.add(pm2);

    }  

    public Iterator createIterator() {

       return list.iterator();

    }

}

(5)对于SalaryManager类,除了创建迭代器方法返回的类型改变外,其它都没有改变,还是用ArrayIteratorImpl来实现迭代器。

(6)接下来写个客户端来测试看看,示例代码如下:

public class Client {

    public static void main(String[] args) {

       //访问集团的工资列表

       PayManager payManager= new PayManager();

       //先计算再获取

       payManager.calcPay();

       System.out.println("集团工资列表:");

       test(payManager.createIterator());

 

       //访问新收购公司的工资列表

       SalaryManager salaryManager = new SalaryManager();

       //先计算再获取

       salaryManager.calcSalary();

       System.out.println("新收购的公司工资列表:");

       test(salaryManager.createIterator());

    }

    /**

     * 测试通过访问聚合对象的迭代器,是否能正常访问聚合对象

     * @param it 聚合对象的迭代器

     */

    private static void test(Iterator it){

       while(it.hasNext()){

           PayModel pm = (PayModel)it.next();

           System.out.println(pm);

       }

    }

}

很明显改用Java的Iterator来实现,比自己全部重新去做,还是要简单一些的。

14.3.3  带迭代策略的迭代器

由于迭代器模式把聚合对象和访问聚合的机制实现了分离,所以可以在迭代器上实现不同的迭代策略,最为典型的就是实现过滤功能的迭代器。

       在实际开发中,对于经常被访问的一些数据可以使用缓存,把这些数据存放在内存中。但是不同的业务功能需要访问的数据是不同的,还有不同的业务访问权限能访问的数据也是不同的,对于这种情况,就可以使用实现过滤功能的迭代器,让不同功能使用不同的迭代器来访问。当然,这种情况也可以结合策略模式来实现。

       在实现过滤功能的迭代器中,又有两种常见的需要过滤的情况,一种是对数据进行整条过滤,比如只能查看自己部门的数据;另外一种情况是对数据进行部分过滤,比如某些人不能查看工资数据。

       带迭代策略的迭代器实现的一个基本思路,就是先把聚合对象的聚合数据获取到,并存储到迭代器里面来,这样迭代器就可以按照不同的策略来迭代数据了。

1:带迭代策略的迭代器示例

沿用上一个例子,来修改ArrayIteratorImpl来简单的示意一下,不去考虑复杂的算法,大致的修改有:

  • 原来是持有聚合对象的,现在直接把这个聚合对象的内容取出来存放到迭代器里面,也就是迭代的时候,直接在迭代器里面来获取具体的聚合对象的元素,这样才好控制迭代的数据
  • 在迭代器的具体实现上加入过滤的功能

示例代码如下:

/**

 * 用来实现访问数组的迭代接口,加入了迭代策略

 */

public class ArrayIteratorImpl implements Iterator{

    /**

     * 用来存放被迭代的数组

     */

    private PayModel[] pms = null;

    /**

     * 用来记录当前迭代到的位置索引

     */

    private int index = 0;

 

    public ArrayIteratorImpl(SalaryManager aggregate){

       //在这里先对聚合对象的数据进行过滤,比如工资必须在3000以下

       Collection<PayModel> tempCol = new ArrayList<PayModel>();

       for(PayModel pm : aggregate.getPays()){

           if(pm.getPay() < 3000){

              tempCol.add(pm);

           }

       }

       //然后把符合要求的数据存放到用来迭代的数组

       this.pms = new PayModel[tempCol.size()];

       int i=0;

       for(PayModel pm : tempCol){

           this.pms[i] = pm;

           i++;

       }

    }

    public boolean hasNext() {

       //判断是否还有下一个元素

       if(pms!=null && index<=(pms.length-1)){

           return true;

       }

       return false;

    }

    public Object next() {

       Object retObj = null;

       if(hasNext()){

           retObj = pms[index];

           //每取走一个值,就把已访问索引加1

           index++;

       }

       //在这里对要返回的数据进行过滤,比如不让查看工资数据

       ((PayModel)retObj).setPay(0.0);

 

       return retObj;

    }

    public void remove() {

       //暂时可以不实现   

    }

}

2:谁定义遍历算法的问题

       在实现迭代器模式的时候,一个常见的问题就是:谁来定义遍历算法?其实带策略的迭代器讲述的也是这个问题。

       在迭代器模式的实现中,常见有两个地方可以来定义遍历算法,一个就是聚合对象本身,另外一个就是迭代器负责遍历算法。

       在聚合对象本身定义遍历的算法这种情况下,通常会在遍历过程中,用迭代器来存储当前迭代的状态,这种迭代器被称为游标,因为它仅用来指示当前的位置。比如在14.2.4里面示例的迭代器就属于这种情况。

       在迭代器里面定义遍历算法,会易于在相同的聚合上使用不同的迭代算法,同时也易于在不同的聚合上重用相同的算法。比如上面带策略的迭代器的示例,迭代器把需要迭代的数据从聚合对象中取出并存放到自己对象里面,然后再迭代自己的数据,这样一来,除了刚开始创建迭代器的时候需要访问聚合对象外,真正迭代过程已经跟聚合对象无关了。

       当然,在迭代器里面定义遍历算法,如果实现遍历算法的时候需要访问聚合对象的私有变量,那么将遍历算法放入迭代器中会破坏聚合对象的封装性。

       至于究竟使用哪一种方式,要具体问题具体分析。

14.3.4  双向迭代器

       所谓双向迭代器的意思就是:可以同时向前和向后遍历数据的迭代器。

       在Java util包中的ListIterator接口就是一个双向迭代器的示例,当然自己实现双向迭代器也非常容易,只要在自己的Iterator接口中添加向前的判断和向前获取值的方法,然后在实现中实现即可。

       延续前面14.2.4的示例,来自己实现双向迭代器,相同的部分就不去示范了,只演示不同的地方,先看看新的迭代器接口,示例代码如下:

/**

 * 迭代器接口,定义访问和遍历元素的操作,实现双向迭代

 */

public interface Iterator {

    public void first();

    public void next();

    public boolean isDone();

    public Object currentItem();

    /**

     * 判断是否为第一个元素

     * @return 如果为第一个元素,返回true,否则返回false

     */

    public boolean isFirst();

    /**

     * 移动到聚合对象的上一个位置

     */

    public void previous();

}

有了新的迭代器接口,也应该有新的实现。示例代码如下:

/**

 * 用来实现访问数组的双向迭代接口

 */

public class ArrayIteratorImpl implements Iterator{

    private SalaryManager aggregate = null;

    private int index = -1; 

    public ArrayIteratorImpl(SalaryManager aggregate){

       this.aggregate = aggregate;

    }

    public void first(){

       index = 0;

    }

    public void next(){

       if(index < this.aggregate.size()){

           index = index + 1;

       }

    }

    public boolean isDone(){

       if(index == this.aggregate.size()){

           return true;

       }

       return false;

    }

    public Object currentItem(){

       return this.aggregate.get(index);

    }

 

    public boolean isFirst(){

       if(index==0){

           return true;

       }

       return false;

    }

    public void previous(){

       if(index > 0 ){

           index = index - 1;

       }

    }

}

基本实现完了,写个客户端来享受一下双向迭代的乐趣。由于这个实现要考虑同时控制向前和向后迭代取值,而控制当前索引的是同一个值,因此在获取向前取值得时候,要先把已访问索引减去1,然后再取值,这个跟向后取值是反过来的,注意一下。示例代码如下:

public class Client {

    public static void main(String[] args) {

       //访问新收购公司的工资列表

       SalaryManager salaryManager = new SalaryManager();

       //先计算再获取

       salaryManager.calcSalary();

 

       //得到双向迭代器

       Iterator it = salaryManager.createIterator();

       //首先设置迭代器到第一个元素

       it.first();

 

       //先next一个

       if(!it.isDone()){

           PayModel pm = (PayModel)it.currentItem();

           System.out.println("next1 == "+pm);

           //向下迭代一个

           it.next();

       }

       //然后previous一个

       if(!it.isFirst()){

           //向前迭代一个

           it.previous();

           PayModel pm = (PayModel)it.currentItem();

           System.out.println("previous1 == "+pm);         

       }

       //再next一个

       if(!it.isDone()){

           PayModel pm = (PayModel)it.currentItem();

           System.out.println("next2 == "+pm);

           //向下迭代一个

           it.next();

       }

       //继续next一个

       if(!it.isDone()){

           PayModel pm = (PayModel)it.currentItem();

           System.out.println("next3 == "+pm);

           //向下迭代一个

           it.next();

       }

       //然后previous一个

       if(!it.isFirst()){

           //向前迭代一个

           it.previous();

           PayModel pm = (PayModel)it.currentItem();

           System.out.println("previous2 == "+pm);         

       }

 

    }

}

上面的示例故意先向后取值,然后再向前取值,这样反复才能看出双向迭代器的效果。运行的结果如下:

next1 == userName=王五,pay=2200.0

previous1 == userName=王五,pay=2200.0

next2 == userName=王五,pay=2200.0

next3 == userName=赵六,pay=3600.0

previous2 == userName=赵六,pay=3600.0

可能有些人会疑惑:为什么next1和previous1取出来的值是一样的呢?

这是因为现在是顺序迭代,当next显示第一条的时候,内部索引已经指向第二条了,所以这个时候再previous向前一条的时候,数据就是第一条数据了。

再仔细看上面的结果,发现这个时候继续next数据时,数据还是第一条数据,同理,刚才previous向前一条的时候,内部索引已经指向第一条之前了。

14.3.5  迭代器模式的优缺点

l          更好的封装性
    迭代器模式可以让你访问一个聚合对象的内容,而无需暴露该聚合对象的内部表示,从而提高聚合对象的封装性

l          可以以不同的遍历方式来遍历一个聚合
    使用迭代器模式,使得聚合对象的内容和具体的迭代算法分离开,这样就可以通过使用不同的迭代器的实例,就可以使用不同的遍历方式来遍历一个聚合对象了,比如上面示例的带迭代策略的迭代器。

l          迭代器简化了聚合的接口
    有了迭代器的接口,那么聚合本身就不需要再定义这些接口了,从而简化了聚合的接口定义。

l          简化客户端调用
    迭代器为遍历不同的聚合对象提供了一个统一的接口,使得客户端遍历聚合对象的内容变得更简单

l          同一个聚合上可以有多个遍历。
    每个迭代器保持它自己的遍历状态,比如前面实现中的迭代索引位置,因此可以对同一个聚合对象同时进行多个遍历。

14.3.6  思考迭代器模式

1:迭代器模式的本质

迭代器模式的本质:控制访问聚合对象中的元素

迭代器能实现“无需暴露聚合对象的内部实现,就能够访问到聚合对象中各个元素”的功能,看起来其本质应该是“透明访问聚合对象中的元素”。

但仔细思考一下,除了透明外,迭代器就没有别的功能了吗?很明显还有其它的功能,前面也讲到了一些,比如“带迭代策略的迭代器”。那么综合来看,迭代器模式的本质应该是“控制访问聚合对象中的元素”,而非单纯的“透明”,事实上,“透明”访问也是“控制访问”的一种情况。

认识这个本质,对于识别和变形使用迭代器模式很有帮助。大家想想,现在的迭代模式默认的都是向前或者向后获取一个值,也就是说都是单步迭代,那么,如果想要控制一次迭代多条怎么办呢?

这个在实际开发中是很有用的,比如在实际开发中很常用的翻页功能的实现,常见的翻页功能有如下几种实现方式:

(1)纯数据库实现

依靠SQL提供的功能实现翻页,用户每次请求翻页的数据,就会到数据库中获取相应的数据

(2)纯内存实现

就是一次性从数据库中把需要的所有数据都取出来放到内存中,然后用户请求翻页时,从内存中获取相应的数据

(3)上面两种方式各有优缺点:

  • 第一种方案明显是时间换空间的策略,每次获取翻页的数据都要访问数据库,运行速度相对比较慢,而且很耗数据库资源,但是节省内存空间。
  • 第二种方案是典型的空间换时间,每次是直接从内存中获取翻页的数据,运行速度快,但是很耗内存。

在实际开发中,小型系统一般采用第一种方案,基本没有单独采用第二种方案的,因为内存实在是太宝贵了,中大型的系统一般是把两个方案结合起来,综合利用它们的优点,而又规避它们的缺点,从而更好的实现翻页的功能。

(4)纯数据库实现 + 纯内存实现

思路是这样的:如果每页显示10条记录,根据判断,用户很少翻到10页过后,那好了,第一次访问的时候,就一次性从数据库中获取前10页的数据,也就是100条记录,把这100条记录放在内存里面。

这样一来,当用户在前10页内进行翻页操作的时候,就不用再访问数据库了,而是直接从内存中获取数据,这样速度就快了。

当用户想要获取第11页的数据,这个时候才会再次访问数据库,对于这个时候到底获取多少页的数据,简单的处理就是继续获取10页的数据,比较好的方式就是根据访问统计进行衰减访问,比如折半获取,也就是第一次访问数据库获取10页的数据,那么第二次就只获取5页,如此操作直到一次从数据库中获取一页的数据。这也符合正常规律,因为越到后面,被用户翻页到的机会也就越小了。

对于翻页的迭代,后面给大家一个简单的示例。

2:何时选用迭代器模式

建议在如下情况中,选用迭代器模式:

  • 如果你希望提供访问一个聚合对象的内容,但是又不想暴露它的内部表示的时候,可以使用迭代器模式来提供迭代器接口,从而让客户端只是通过迭代器的接口来访问聚合对象,而无需关心聚合对象内部实现。
  • 如果你希望有多种遍历方式可以访问聚合对象,可以使用迭代器模式
  • 如果你希望为遍历不同的聚合对象提供一个统一的接口,可以使用迭代器模式

14.3.7  翻页迭代

在上面讲到的翻页实现机制中,只要使用到内存来缓存数据,就涉及到翻页迭代的实现。简单点说,就是一次迭代,会要求迭代取出一页的数据,而不是一条数据。

其实实现翻页迭代也很简单,主要是把原来一次迭代一条数据的接口,都修改成一次迭代一页的数据就可以。在具体的实现上,又分成顺序翻页迭代器和随机翻页迭代器。

1:顺序翻页迭代器示例

(1)先看看迭代器接口的定义,示例代码如下:

/**

 * 定义翻页访问聚合元素的迭代接口

 */

public interface AggregationIterator {

    /**

     * 判断是否还有下一个元素,无所谓是否够一页的数据,

     * 因为最后哪怕只有一条数据,也是要算一页的

     * @return 如果有下一个元素,返回true,没有下一个元素就返回false

     */

    public boolean hasNext();

    /**

     * 取出下面几个元素

     * @param num 需要获取的记录条数

     * @return 下面几个元素

     */

    public Collection next(int num);

    /**

     * 判断是否还有上一个元素,无所谓是否够一页的数据,

     * 因为最后哪怕只有一条数据,也是要算一页的

     * @return 如果有上一个元素,返回true,没有上一个元素就返回false

     */

    public boolean hasPrevious();

    /**

     * 取出上面几个元素

     * @param num 需要获取的记录条数

     * @return 上面几个元素

     */

    public Collection previous(int num);

}

(2)PayModel跟前面的示例是一样的,这里就不去赘述了

(3)接下来看看SalaryManager的实现,有如下改变:

  • 不用再实现获取聚合对象大小和根据索引获取聚合对象中的元素的功能了
  • 在准备测试数据的时候,多准备几条,好看出翻页的效果

示例代码如下:

/**

 * 被客户方收购的那个公司的工资管理类

 */

public class SalaryManager{

    private PayModel[] pms = null;

    public PayModel[] getPays(){

       return pms;

    }

    public void calcSalary(){

       //计算工资,并把工资信息填充到工资列表里面

       //为了测试,做点数据进去

       PayModel pm1 = new PayModel();

       pm1.setPay(2200);

       pm1.setUserName("王五");

 

       PayModel pm2 = new PayModel();

       pm2.setPay(3600);

       pm2.setUserName("赵六");

 

       PayModel pm3 = new PayModel();

       pm3.setPay(2200);

       pm3.setUserName("王五二号");

 

       PayModel pm4 = new PayModel();

       pm4.setPay(3600);

       pm4.setUserName("赵六二号");

 

       PayModel pm5 = new PayModel();

       pm5.setPay(2200);

       pm5.setUserName("王五三号");

 

       pms = new PayModel[5];

       pms[0] = pm1;

       pms[1] = pm2;

       pms[2] = pm3;

       pms[3] = pm4;

       pms[4] = pm5;

    }

    public AggregationIterator createIterator() {

       return new ArrayIteratorImpl(this);

    }

}

(4)然后再看看如何实现迭代器接口,示例代码如下:

/**

* 用来实现翻页访问聚合元素的迭代接口
*/

public class ArrayIteratorImpl implements AggregationIterator{

    /**

* 用来存放被迭代的数组

*/

    private PayModel[] pms = null;

    /**

* 用来记录当前迭代到的位置索引

*/

    private int index = 0;

    public ArrayIteratorImpl(SalaryManager aggregate){

       this.pms = aggregate.getPays();

    }

    public boolean hasNext() {

       //判断是否还有下一个元素

       if(pms!=null && index<=(pms.length-1)){

           return true;

       }

       return false;

    }

public boolean hasPrevious() {

       if(pms!=null && index > 0){

           return true;

       }

       return false;

    }

    public Collection next(int num) {

       Collection col = new ArrayList();

       int count=0;

       while(hasNext() && count<num){

           col.add(pms[index]);

           //每取走一个值,就把已访问索引加1

           index++;

           count++;

       }

       return col;

    }

    public Collection previous(int num){

       Collection col = new ArrayList();

       int count=0;

       //简单的实现就是把索引退回去num个,然后再取值。

       //但事实上这种实现是有可能多退回去数据的,比如:已经到了最后一页,

//而且最后一页的数据不够一页的数据,那么退回去num个索引就退多了

       //为了示例的简洁性,这里就不去处理了

       index = index - num;

       while(hasPrevious() && count<num){

           col.add(pms[index]);

           index ++;

           count++;

       }

       return col;

    }

}

(5)写个客户端测试看看,示例代码如下:

public class Client {

    public static void main(String[] args) {

       //访问新收购公司的工资列表,先计算再获取

       SalaryManager salaryManager = new SalaryManager();

       salaryManager.calcSalary();

       //得到翻页迭代器

       AggregationIterator it = salaryManager.createIterator();

 

       //获取第一页,每页显示2条

       Collection col = it.next(2);

       System.out.println("第一页数据:");

       print(col);

       //获取第二页,每页显示2条

       Collection col2 = it.next(2);

       System.out.println("第二页数据:");

       print(col2);

 

       //向前一页,也就是再次获取第二页

       Collection col3 = it.previous(2);

       System.out.println("再次获取第二页数据:");

       print(col3);

    }

    private static void print(Collection col){

       Iterator it = col.iterator();

       while(it.hasNext()){

           Object obj = it.next();

           System.out.println(obj);

       }

    }

}

运行的结果如下:

第一页数据:

userName=王五,pay=2200.0

userName=赵六,pay=3600.0

第二页数据:

userName=王五二号,pay=2200.0

userName=赵六二号,pay=3600.0

再次获取第二页数据:

userName=王五二号,pay=2200.0

userName=赵六二号,pay=3600.0

仍然是顺序迭代的,也就是获取完第二页数据,内部索引就指向后面了,这个时候再运行向前一页,取的就还是第二页的数据了。

2:随机翻页迭代器示例

估计看到这里,有些朋友会想,实际应用中,用户怎么会这么老实,按照顺序访问,通常情况都是随意的访问页数,比如看了第一页可能就直接点第三页了,看完第三页他又想看第一页。

这就需要随机翻页迭代器了,也就是可以指定页面号和每页显示的数据来访问数据的迭代器,下面来看看示例。

(1)修改迭代接口的方法,不需要再有向前和向后的方法,取而代之的是指定页面号和每页显示的数据来访问的方法,示例代码如下:

/**

 * 定义随机翻页访问聚合元素的迭代接口

 */

public interface AggregationIterator {

    /**

     * 判断是否还有下一个元素,无所谓是否够一页的数据,

     * 因为最后哪怕只有一条数据,也是要算一页的

     * @return 如果有下一个元素,返回true,没有下一个元素就返回false

     */

    public boolean hasNext();  

    /**

     * 判断是否还有上一个元素,无所谓是否够一页的数据,

     * 因为最后哪怕只有一条数据,也是要算一页的

     * @return 如果有上一个元素,返回true,没有上一个元素就返回false

     */

    public boolean hasPrevious();

    /**

     * 取出指定页数的数据

     * @param pageNum 要获取的页数

     * @param pageShow 每页显示的数据条数

     * @return 指定页数的数据

     */

    public Collection getPage(int pageNum,int pageShow);

}

(2)定义了接口,看看具体的实现,示例代码如下:

/**

 * 用来实现随机翻页访问聚合元素的迭代接口

 */

public class ArrayIteratorImpl implements AggregationIterator{

    /**

     * 用来存放被迭代的数组

     */

    private PayModel[] pms = null;

    /**

     * 用来记录当前迭代到的位置索引

     */

    private int index = 0;  

    public ArrayIteratorImpl(SalaryManager aggregate){

       this.pms = aggregate.getPays();

    }

    public boolean hasNext() {

       //判断是否还有下一个元素

       if(pms!=null && index<=(pms.length-1)){

           return true;

       }

       return false;

    }

    public boolean hasPrevious() {

       if(pms!=null && index > 0){

           return true;

       }

       return false;

    }

    public Collection getPage(int pageNum,int pageShow){

       Collection col = new ArrayList();

       //需要在这里先计算需要获取的数据的开始条数和结束条数

       int start = (pageNum-1)*pageShow;

       int end = start + pageShow-1;

       //控制start的边界,最小是0

       if(start < 0){

           start = 0;

       }

       //控制end的边界,最大是数组的最大索引

       if(end > this.pms.length-1){

           end = this.pms.length - 1;

       }

       //每次取值都是从头开始循环,所以设置index0

       index = 0;

       while(hasNext()  && index<=end){

           if(index >= start){

              col.add(pms[index]);

           }

           //把已访问索引加1

           index++;

       }

       return col;

    }

}

(3)写个客户端,测试看看,是否能实现随机的翻页,示例代码如下:

public class Client {

    public static void main(String[] args) {

       //访问新收购公司的工资列表

       SalaryManager salaryManager = new SalaryManager();

       //先计算再获取

       salaryManager.calcSalary();

       //得到翻页迭代器

       AggregationIterator it = salaryManager.createIterator();

 

       //获取第一页,每页显示2条

       Collection col = it.getPage(1,2);

       System.out.println("第一页数据:");

       print(col);

       //获取第二页,每页显示2条

       Collection col2 = it.getPage(2,2);

       System.out.println("第二页数据:");

       print(col2);

       //再次获取第一页

       Collection col3 = it.getPage(1,2);

       System.out.println("再次获取第一页数据:");

       print(col3);

       //获取第三页

       Collection col4 = it.getPage(3,2);

       System.out.println("获取第三页数据:");

       print(col4);

    }

    private static void print(Collection col){

       Iterator it = col.iterator();

       while(it.hasNext()){

           Object obj = it.next();

           System.out.println(obj);

       }     

    }

}

测试的结果如下:

第一页数据:

userName=王五,pay=2200.0

userName=赵六,pay=3600.0

第二页数据:

userName=王五二号,pay=2200.0

userName=赵六二号,pay=3600.0

再次获取第一页数据:

userName=王五,pay=2200.0

userName=赵六,pay=3600.0

获取第三页数据:

userName=王五三号,pay=2200.0

14.3.8  相关模式

l          迭代器模式和组合模式
    这两个模式可以组合使用。
    组合模式是一种递归的对象结构,在枚举某个组合对象的子对象的时候,通常会使用迭代器模式。

l          迭代器模式和工厂方法模式
    这两个模式可以组合使用。
    在聚合对象创建迭代器的时候,通常会采用工厂方法模式来实例化相应的迭代器对象。

 

---------------------------------------------------------------------------

私塾在线学习网原创内容  跟着cc学设计系列 之 研磨设计模式

研磨设计讨论群【252780326】

原创内容,转载请注明出处【http://sishuok.com/forum/blogPost/list/0/5335.html

---------------------------------------------------------------------------

你可能感兴趣的:(iterator)