1. 程式人生 > >設計模式大總結(五):迭代器模式

設計模式大總結(五):迭代器模式

前言

說到迭代器,所有的Java開發者都不陌生,例如HashSet,當我們需要遍歷HashSet的時候,通過iterator不停的next就可以迴圈遍歷集合中的所有元素。但是這麼做到底有什麼好處呢?

1、使用者不需要關心HashSet內部的實現,不關心遍歷的規則(正序倒序等)

2、正因為使用者不需要關心HashSet的內部實現,所以設計者可以隨意更改遍歷的規則,對使用無影響。

回想一下我們之前分析過HashMap的內部實現,實際上和HashSet是差不多的,面對如此多的程式碼,我們只需要使用iterator能夠遍歷就可以了。

正文

使用迭代器模式,我們可以自己定義介面,但是在JDK中已經有定義好的迭代器介面,可以滿足大部分的使用場景:

public interface Iterator<E> {
    boolean hasNext();
    E next();
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

一般情況,我們只需要實現hasNext和next方法就可以了。

接下來我們就下一個簡單的例子:

定義自己的集合MyCollection,通過迭代器iterator,按照正序返回集合中的物件。

首先,定義我們的MyCollection類:

/**
 * Created by li.zhipeng on 2017/9/21.
 *
 *      自定義集合類
 */

public class MyCollection<T>{

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

    public void
add(T item){ list.add(item); } public void remove(T item){ list.remove(item); } public Iterator<T> iterator(){ return new MyIterator(list); } /** * 自定義迭代器 * */ private class MyIterator implements Iterator<T>{ private List<T> dataSource; /** * 當前位置的指標 * */ private int curPos; public MyIterator(List<T> data){ this.dataSource = data; } @Override public boolean hasNext() { return curPos < dataSource.size(); } @Override public T next() { T item = dataSource.get(curPos); curPos ++; return item; } } }

為了簡單方便,我在MyCollection內部使用了ArrayList,然後封裝了兩個方法,新增和刪除ArrayList中的元素,iterator()方法返回我們自定義的迭代器,這裡還使用了泛型,規定了新增元素的型別,MyCollection的泛型肯定是要和迭代器是一樣的,這裡就不多說了。

請注意這裡的迭代器最好不要公用,儘量返回一個新的迭代器物件,因為如果多執行緒同時遍歷迭代器,這裡可能會影響到迭代器的指標,所以我每次都是建立一個新的迭代器。如果你更加關心多執行緒的併發操作,這裡最好每次建立Iterator的時候,應該加入同步鎖,傳入List的副本,提高多執行緒併發的安全性。

然後在MyCollection中定義了內部迭代器MyIterator,有一個私有屬性curPos,記錄遍歷的位置指標。

使用private修飾迭代器的原因

之前說過Iterator需要隱藏集合遍歷時獲取元素的規則,並且這個Iterator也只為我們當前的MyCollection服務,所以公開這個類是一個不明智的選擇,我這裡選擇隱藏這個類,只能通過MyCollection的iterator方法來獲取。

大部分的迭代器模式開發中,往往都是隱藏自定義迭代器。

定義完畢,測試一下我們的MyCollection:

public void test(){
        MyCollection<Student> collection = new MyCollection<>();
        collection.add(new Student("1111"));
        collection.add(new Student("2222"));

        Iterator<Student> iterator = collection.iterator();
        while (iterator.hasNext()){
            Student student = iterator.next();
            System.out.println(student.getName());
        }
}

這裡寫圖片描述

確實是按照我們新增的順序列印了Student的名字。

當我還沉迷於自己的才華無法自拔的時候,產品君說了:我們現在要改成倒序遍歷了,你抓緊時間弄一下,明天我們要上線。

如果我之前使用的是for迴圈,那麼我就需要根據功能邏輯,找到遍歷的程式碼,然後一處一處的修改,如果有個100使用的地方,我覺得今晚是沒法睡了。

但是機智如我,我早就看穿了產品君騷動的內心,早早的使用了迭代器模式,我只需要對MyIterator做一點小調整:

/**
     * 自定義迭代器
     * */
    private class MyIterator implements Iterator<T>{

        private List<T> dataSource;

        /**
         * 當前位置的指標
         * */
        private int curPos;

        public MyIterator(List<T> data){
            this.dataSource = data;
            // 把遍歷開始的指標,放到最後一個
            curPos = data.size() - 1;
        }

        @Override
        public boolean hasNext() {
            // 修改是否遍歷的結束條件,這裡需要大於0
//            return curPos < dataSource.size();
            return curPos >= 0;
        }

        @Override
        public T next() {
            T item = dataSource.get(curPos);
            // 這裡要把位置--
//            curPos ++;
            curPos--;
            return item;
        }
    }

這裡寫圖片描述

執行完美~

總結

經過Demo的實際使用,我們看到了迭代器模式是對於集合遍歷規則的封裝,當一個集合類被大量使用,而這個時候需要修改遍歷規則,如果沒有迭代器模式,對於開發者來說是多麼的蛋疼。

如果集合類需要多種遍歷規則,你也可以指定多套迭代器,這個大家按需開發就好,之後的維護我們就能深刻感受到,幾行程式碼就輕鬆解放我們的雙手的快感。

今天的內容就到此結束了,有問題歡迎留言討論,一起進步。