Java设计模式之从[魔兽争霸、星际争霸、DOTA编队]分析迭代器(Iterator)形式

Java设计模式之从[魔兽争霸、星际争霸、DOTA编队]分析迭代器(Iterator)模式

  在即时战略游戏、DOTA中,我们可以多选我们部队,让他们组成一个队伍。在星际1、魔兽3中,一支队伍的最大单位数量为12个,当我们选中一支队伍后,可以命令他们集体朝着哪个方向移动或者进攻,而不用一个一个控制我们的单位。在程序中,我们是如何实现向这支队伍“群发”命令的呢?最开始想到的就是循环——把队伍中的每一个单位加入一个列表中,写一个for循环,依次访问这个列表中的每一个成员,让它们接收命令。

  而这一次,我将介绍一下迭代器模式。迭代器对于很多人来说是一个很熟悉的名词,它又叫做游标模式,它可以提供一种顺序访问一个集合对象中每一个元素,而又不暴露此对象的内部表示的方法。简单来说,对于一个集合Team,我们只需要通过next来获取集合中的下一个元素,以及用hasNext来判断是否集合还存在下一个元素,就可以遍历到该Team集合的所有元素了。

  在下面这个例子中,假设我有3个英雄单位,分别叫做路西法、卡尔和奈文摩尔,现在我把他们加入了一支队伍,并且遍历输出他们的名字:

import java.util.Arrays;
import java.util.List;

interface Iterator<T>{
    T next();
    boolean hasNext();
}

class Unit{
    private String name;
    public Unit(String name){
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

class Team<T>{
    List<T> members ;
    int cursor = 0;
    public Iterator<T> getIterator(){
        return new Iterator<T>(){
            public T next(){
                T result = members.get(cursor);
                cursor ++;
                return result;
            }
            public boolean hasNext(){
                return cursor < members.size();
            }
        };
    }

    public Team(List<T> members){
        this.members = members;
    }
}

class IteratorExample
{
    public static void main(String[] args) throws InterruptedException {
        Unit u1 = new Unit("路西法");
        Unit u2 = new Unit("卡尔");
        Unit u3 = new Unit("奈文摩尔");
        List<Unit> units = Arrays.asList(u1, u2, u3);
        Team<Unit> team = new Team<Unit>(units);
        Iterator<Unit> iter = team.getIterator(); //获得一个新的迭代器
        while (iter.hasNext()){
            System.out.println(iter.next().getName());
        }
    }
}

  可以看到,迭代器接口主要定义了两个方法:next()是返回下一个元素,hasNext()返回是否有下一个元素。在Team类中,我们通过getIterator()返回了一个匿名的迭代器,它的内部有一个cursor,其实就是Team类中members列表的游标,每使用迭代器的next(),cursor就会自增1。我们可以在main方法中看到,通过while、hasNext()和next()的配合使用,我们可以遍历到team中的每一个元素,因此程序输出的结果为:

路西法

卡尔

奈文摩尔

  下面来解释几个关键的问题:

  1、为什么要将Iterator写为内部类?因为我们希望每次使用getIterator返回的都是一个新的迭代器(即每一个迭代器的cursor都是从0开始)。

  2、这是否是一个健壮的迭代器?答案:不是。一个健壮的迭代器要求保证插入和删除操作都不会干扰遍历,而在此迭代器中,如果对members进行了添加、删除操作,由于cursor的值不会因此改变,所以会导致遍历被干扰(如在cursor位置之前插入了一个元素,则遍历的时候,会输出两个一模一样的元素)。迭代器在Java、C#中用在foreach语句中最为广泛,且它们不允许在用foreach语句进行迭代器遍历的时候对原集合的对象进行添加、删除元素的操作,因为这样会干扰迭代器。

  3、如果想在Java中为一个类实现兼容foreach语句,这个类必须要继承Iterable<T>接口,它会要求这个类必须有一个iterator()方法来返回一个Iterator<T>迭代器。同理,在C#中如果要实现一个类兼容foreach语句,必须要继承IEnumerable<T>接口,它要求此类必须实现GetEnumerator()返回一个IEnumerator迭代器。它们仅仅是名称不同而已,其用途和含义是一样的。