`
frank-liu
  • 浏览: 1667007 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

从Iterator Pattern说起

 
阅读更多

疑惑之初:

记得初次编程的时候,看到书上说过当要想实现对某个对象进行迭代器访问的方式时,需要实现一些特定的接口。

比如说我们想用如下的一段代码:

 

for(ListItem item : itemSet)
    item.xxx();

 那么我们就需要实现java.lang.Iterable<T>接口。这个接口包含的方法细节如下:

 

public interface Iterable<T>
{
        Iterator<T> iterator();
}

同时,我们还需要实现接口Iterator<T> 。

Iterator<T>接口的定义如下:

 

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
}

 

这么粗粗看来,好像是一种什么很奇怪的手法,但是用了这种手法之后,我们就能用迭代的方式来遍历这个对象元素了。那个这个手法是怎么来的呢?它后面的设计思想是什么呢?我们可以从头开始慢慢推导一下。

 

推导:

假设我们有一个用户信息的类,定义如下:

 

public class UserInfo {
    String firstname;
    String  lastname;
    String address;
    public UserInfo(String firstname, String lastname, String address) {
        this.firstname = firstname;
        this.lastname = lastname;
        this.address = address;
    }
    
    public String getFirstname() {
        return firstname;
    }

    public String getLastname() {
        return lastname;
    }

    public String getAddress() {
        return address;
    }
}

这是一个最简单的类定义。为了保存和处理UserInfo,假设有两个用户用两种不同的方法来存储了一系列的用户信息。一个使用了ArrayList,一个使用了UserInfo[]。

两个类的分别定义如下:

UserInfoList:

 

public class UserInfoList
{
    ArrayList<UserInfo> userList;

    public UserInfoList()
    {
        userList = new ArrayList<UserInfo>();

        addUser("a", "a", "aa");
        addUser("b", "b", "bb");
    }

    public ArrayList<UserInfo> getUserList()
    {
        return userList;
    }
}

UserInfoArray:

 

public class UserInfoArray
{
    static final int MAX_ITEMs = 6;
    int numberOfItems = 0;
    UserInfo[] userArray;

    public UserInfoArray()
    {
        userArray = new UserInfo[MAX_ITEM];
    }

    public void addUser(String firstname, String lastname, String address)
    {
        UserInfo user = new UserInfo(firstname, lastname, address);
        if(numberOfItem >= MAX_ITEMS)
        {
            System.err.println("Can't add user");
        }
        else
        {
            userArray[numberOfItems] = user;
            numberOfItems++;
        }
    }

    public UserInfo[] getUserArray()
    {
        return userArray;
    }
}

 

现在,假设我们要遍历两个包含UserInfo集合的类,我们可能需要写如下的代码:

 

UserInfoList list = new UserInfoList();
ArrayList<UserInfo> userList = list.getUserList();
// traverse
for(int i = 0; i < userList.size(); i++)
{
    UserInfo user = userList.get(i);
    // xxx
}


UserInfoArray array = new UserInfoArray();
UserInfo[] = array.getUserArray();
// traverse
for(int i = 0; i < array.length; i++)
{
    UserInfo user = array[i];
    // xxx
}

前面两个类的定义不同,为了遍历其中的元素,我们执行的细节也不同。这样可能会存在一个问题。如果后面我们还有通过链表实现的元素集合类呢?如果要遍历多个的话,岂不是每个都要不同来处理?

 问题:

仔细看上面的问题,我们针对每个保存集合元素的类,其本身的存储方式是不同的。如果直接用for循环遍历的话,其中必然牵涉到里面的保存实现细节。所以说,问题的根源就是为了能够遍历不同集合元素类,我们把其中的细节给暴露出来了,这样就使得我们每次要遍历的时候不得不面对具体的细节,没法用一个统一的方式来遍历所有类。那么,有没有可能我们用一种统一的方式来遍历他们而不用每个都去具体的处理呢?

 

第一步抽象:

我们前面已经意识到了,问题的根源就是在于遍历的时候细节暴露出来了。那么,如果我们定义一个统一的所谓遍历形式,将他们不同的地方都封装起来,是不是就可以了呢?

比如说,用如下的一种方法来遍历集合元素的话,可以用这种写法:

 

Iterator iterator = userInfoList.createIterator();
while(iterator.hasNext())
{
    UserInfo user = iterator.next();
}

 

 采用这种抽象的一个好处就是,我们只需要提供一个封装了hasNext()和next()方法的对象。hasNext()判断是否有下一个元素,以及用next()方法返回当前元素并移动到下一个元素。具体这个对象是怎么来判断下一个以及移动的我们都不需要管。这就是对变化进行封装的好处。既然我们将变化的部分进行封装了,不同变化在iterator肯定都要有一个实现,而且具体的实现才是针对特定问题自身的。

那么,针对封装了变化这一部分所对应的uml图如下:

封装变化

UserInfoArrayIterator的具体实现如下:

 

public class UserInfoArrayIterator implements Iterator<UserInfo>
{
    UserInfo[] users;
    int position = 0;
    public UserInfoArrayIterator(UserInfo[] users)
    {
        this.users = users;
    }

    public UserInfo next()
    {
        UserInfo user = users[position];
        position++;
        return user;
    }

    public boolean hasNext()
    {
        if(position >= users.length || users[position] == null)
        {
            return false;
        }
        else
            return true;
    }
}

 

 

整体的uml图则如下:

整体uml

进一步改进:

实际上,从图中我们可以看到,两个类UserInfoList和UserInfoArray都有一个方法createIterator()。它主要的目的就是用来返回自己特定的Iterator。如果我们将其进一步抽象的话。可以提取一个专门的接口,它主要就是返回一个Iterator类型的数据。这一步,又和我们面向对象设计原则中面向接口和抽象这一条暗合的。这么一步改进之后,对应的uml图则如下:

改进后

 

 

再回首:

再回到开头提到的部分,我们要实现遍历一个对象的话,需要实现Iterable和Iterator两个接口。实际上实现Iterable接口是为了返回一个实际遍历元素的具体Iterator实现。而实现Iterator接口则是对具体类的遍历细节实现。

结论:

总的来说,如果我们用一句话来概括Iterator Pattern的话,就是提供不同集合元素类的通用遍历方法。

1:为了能够采用通用的形式,我们将变化的部分封装成一个Iterator接口,通过实现这个接口来实现具体的遍历细节。对于使用迭代器的代码来说,就不需要关注遍历的细节了。

2: 为了更好的面向抽象而不是具体类,我们将返回一个具体Iterator的方法也抽象成了一个接口Iterable。

这两点就是Iterator Pattern的核心要旨。

参考材料:

《Head first design patterns》 Iterator Pattern章节部分。

http://docs.oracle.com/javase/7/docs/api/java/util/Iterator.html

 

 

后续部分:

Iterator Pattern在Java中间的实现和使用虽然带来了一定的便利,不过因为要定义一堆的接口和实现,看起来稍显麻烦。于是在别的语言里通过语法糖或者内部增强的方式极大的简化了Iterator Pattern定义和实现。比如说在c#语言中,采用yield return的语法糖,编译器把一些细节帮你给做了。而典型的动态语言如python,则有一套iterator的定义机制。进而演化出来了iterator和generator.这也是后续文章要继续讨论的话题。

  • 大小: 108.1 KB
  • 大小: 93.2 KB
  • 大小: 86.9 KB
分享到:
评论
1 楼 kensunhu 2012-06-12  
Great explanation for iterator pattern,thanks!

相关推荐

Global site tag (gtag.js) - Google Analytics