23天設計模式之迭代器模式

孤影的部落格發表於2021-07-02

23天設計模式之迭代器模式

文章簡介

今天在看Vector類的原始碼時發現集合基本上都使用到了迭代器模式,去了解了迭代器模式的相關知識,於是就有了這篇文章!在文章中我會拿我們最熟悉的ArrayList類來舉例,對應迭代器中的幾種角色一一闡釋清楚。廢話不多說,文章獻上。

是什麼

迭代器模式(Iterator),提供一種方法順序訪問一個聚合物件中的各種元素,而又不暴露該物件的內部表示。簡單來說就是提供了對集合等聚合結構的遍歷所有元素的方法。

物件的內部表示指的是什麼?這個我暫時也不清楚。不過我理解的大體意思是指,使用者不用關心這個集合中元素是怎麼排列的,使用者只需要通過這個介面就能依次拿到所有元素。

作用

  1. 訪問一個聚合物件的內容而無需暴露它的內部表示
  2. 支援對聚合物件的多種遍歷
  3. 為遍歷不同的聚合結構提供一個統一的介面

當然,上述幾個概念都是從百度百科看來的,都比較容易理解,我也就不再一一囉嗦了。

角色

  1. Iterator(迭代器介面):迭代器定義訪問和遍歷元素的介面。
  2. ConcreteIterator (具體迭代器):實現迭代器介面,真正地實現迭代元素。
  3. Aggregate (聚合介面):建立相應迭代器物件的介面,比如 List<E> 介面。
  4. ConcreteAggregate (具體聚合):實現聚合介面,並提供一個方法返回一個具體迭代器。比如 ArrayList<E>

角色之間的關係圖示:(配色可還行?)

下面來找找ArrayList類中對應的上述幾個角色。

圖示如下:(idea中可以顯示diagram圖,可以去鼓搗鼓搗?)

這樣一來就很清楚角色之間的關係了。

接下來我們再看看ArrayList的原始碼中,上述角色的體現。

// 寫一個測試類呼叫iterator()方法
public class Test {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        // 按住Ctrl+左鍵點選iterator()檢視它的實現
        Iterator<String> iterator = list.iterator();
    }
}

// 發現list.iterator();呼叫的是ArrayList的iterator()方法
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public Iterator<E> iterator() {
        // 該方法直接構造了一個Itr物件,說明Itr也是一個迭代器,再往下看Itr類
        return new Itr();
    }
    
    // Itr類是一個ArrayList的內部類,並且實現了迭代器介面,也就是一個具體的迭代器物件,就是上述的 ConcreteIterator 
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        Itr() {}    
// 再回到上面看ArrayList,它實現了List介面,List介面又繼承了Collection<E>介面
public interface List<E> extends Collection<E> {
    // List介面也有iterator()方法,說明ArrayList的iterator()方法就是實現List後的來的
    Iterator<E> iterator();
    
// 再看Collection介面,繼承了Iterable介面
public interface Collection<E> extends Iterable<E> {
    Iterator<E> iterator();
    
// Iterable介面就是頂層介面了,它提供了3個方法,常用的是前兩個方法
public interface Iterable<T> {
    
    // 因此,Collection下的所有集合的迭代器都是重寫此方法獲得的
    Iterator<T> iterator();
    
    // Collection下的所有集合都能使用foreach方法也是從這裡來的
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
    
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }

自定義實現倒序輸出的迭代器

接下來,我們通過自己定義四種角色來完成倒序輸出的迭代器模式。(實戰一波?)

  • 定義抽象介面,包含返回迭代器的方法
public interface MyIterable<T> {
    MyIterator<T> iterator();
}
  • 定義抽象迭代器
public interface MyIterator<E> {
    boolean hasNext();
    E next();
}
  • 定義聚合介面
public interface MyList<E> extends MyIterable<E> {

    @Override
    MyIterator<E> iterator();

    int size();

    boolean add(E e);
}
  • 實現聚合介面,並定義具體迭代器內部類
// 這個類是參考Vector類寫的, 只寫了add方法和size方法
public class MyInvertedList<E> implements MyList<E> {

    protected Object[] elementData;

    protected int initialCapacity;

    protected int increment;

    protected int elementCount;

    public MyInvertedList(int initialCapacity, int increment) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                    initialCapacity);
        if (increment < 0)
            throw new IllegalArgumentException("Illegal increment: "+
                    increment);
        this.initialCapacity = initialCapacity;
        this.increment = increment;
        this.elementData = new Object[initialCapacity];
    }

    // size是元素的個數而不是陣列的長度
    @Override
    public int size() {
        return elementCount;
    }

    @Override
    public boolean add(E e) {
        if (elementData.length == elementCount) {
            // 新增時超出陣列長度時自動擴容
            elementData = Arrays.copyOf(elementData, elementData.length + increment);
        }
        elementData[elementCount++] = e;
        return true;
    }

    @Override
    public MyIterator<E> iterator() {
        return new InvertedIterator();
    }

    // 使用內部類的好處是可以直接拿到this物件
    private class InvertedIterator implements MyIterator<E> {

        // 因為我們要倒敘輸出,所以index是從後往前,而不是從0往後
        int index = elementCount;

        @Override
        public boolean hasNext() {
            return index > 0;
        }

        @Override
        public E next() {
            index--;
            return (E) elementData[index];
        }
    }
}
  • 測試,使用迭代器倒序輸出元素
public class Test {
    public static void main(String[] args) {
        MyList<String> list = new MyInvertedList<>(5, 1);
        list.add("111");
        list.add("111");
        list.add("222");
        list.add("333");
        list.add("444");
        System.out.println("擴容前 size:" + list.size());
        list.add("555");
        list.add("666");
        System.out.println("擴容後 size:" + list.size());
        MyIterator<String> iterator = list.iterator();
        // 倒序輸出
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

// 輸出
擴容前 size:5
擴容後 size:7
666
555
444
333
222
111
111

小結

可以說,迭代器模式是最常用的設計模式了,在元素遍歷的地方基本上都能使用到迭代器模式。

完結撒花!!!???

以上

感謝您花時間閱讀我的部落格,以上就是我對迭代器模式的一些理解,若有不對之處,還望指正,期待與您交流。

本篇博文系原創,僅用於個人學習,轉載請註明出處。???

相關文章