設計模式之迭代器模式

2022-09-14 12:03:04

本文介紹設計模式中的迭代器模式,首先通俗的解釋迭代器模式的基本概念和對應的四個角色,並根據四個角色舉一個典型的範例,為了加強知識的連貫性,我們以Jdk原始碼集合中使用迭代器模式的應用進一步說明,最後說明迭代器模式的應用場景和優缺點。

讀者可以拉取完整程式碼本地學習,實現程式碼均測試通過上傳到碼雲

一、概念理解

迭代器模式官方解釋就是提供一個物件來順序存取聚合物件中的一系列資料,而不暴露聚合物件的內部表示。何為聚合物件呢?最典型的就是集合類。

大白話也就是,集合中的資料是私有的,集合中不應該提供直接遍歷的方法,要定義一個新的物件用於存取這個集合。

既然是一個專門用來遍歷的物件,一個被遍歷的聚合物件,很顯然至少有兩個物件,迭代器物件、聚合物件,由於遵循面向介面程式設計的原則,迭代器物件和聚合物件應該抽象出來介面,那自然而然就是應該有四個角色:

抽象聚合(InterfaceAggregate)角色:定義儲存、新增、刪除聚合元素以及建立迭代器物件的介面。

具體聚合(ConcreteAggregate)角色:實現抽象聚合類,返回一個具體迭代器的範例。

抽象迭代器(Iterator)角色:定義存取和遍歷聚合元素的介面,通常包含 hasNext()、next() 等方法。

具體迭代器(Concretelterator)角色:實現抽象迭代器介面中所定義的方法,完成對聚合物件的遍歷,記錄遍歷的當前位置。

基於四個角色我們舉一個典型案例。

二、案例實現

應該是有四個類

抽象聚合角色,用於定義增刪改查元素的統一規範介面,和建立迭代器物件的方法

具體聚合角色,實現抽象聚合角色方法

抽象迭代器角色,定義遍歷元素的統一規範介面

具體迭代器,實現抽象迭代器角色的方法。

抽象聚合角色:

/**
 * 抽象聚合角色
 * @author tcy
 * @Date 13-09-2022
 */
public interface InterfaceAggregate {
    /**
     * 增加物件
     * @param obj 物件
     */
    void add(Object obj);

    /**
     * 移除物件
     * @param obj 物件
     */
    void remove(Object obj);

    /**
     * 呼叫迭代器
     * @return 迭代器
     */
    Iterator getIterator();
}

具體聚合角色:

/**
 * 具體聚合角色
 * @author tcy
 * @Date 13-09-2022
 */
public class ConcreteAggregate implements InterfaceAggregate{
    private List<Object> list = new ArrayList<>();
    @Override
    public void add(Object obj) {
        list.add(obj);
    }

    @Override
    public void remove(Object obj) {
        list.remove(obj);
    }

    @Override
    public Iterator getIterator() {
        return new Concretelterator(list);
    }
}

抽象迭代器角色:

/**
 * 抽象迭代器
 * @author tcy
 * @Date 13-09-2022
 */
public interface Iterator<E> {

    /**
     * 刪除物件
     * @return 物件
     */
    Object remove();

    /**
     * 呼叫下一個物件
     * @return 物件
     */
    E next();

    /**
     * 迭代器中是否還有下一個物件
     * @return
     */
    boolean hasNext();

    /**
     * 遍歷迭代器中剩餘的物件
     * @param action
     */
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }

}

具體迭代器角色:

/**
 * 具體迭代器角色
 * @author tcy
 * @Date 13-09-2022
 */
public class Concretelterator implements Iterator{
    private List<Object> list = null;
    private int index = -1;

    public Concretelterator(List<Object> list) {
        this.list = list;
    }

    @Override
    public Object remove() {
        index = list.size();
        Object obj = list.get(index);
        list.remove(obj);
        return obj;
    }

    @Override
    public Object next() {
        Object obj = null;
        if (this.hasNext()) {
            obj = list.get(++index);
        }
        return obj;
    }

    @Override
    public boolean hasNext() {
        if (index < list.size() - 1) {
            return true;
        } else {
            return false;
        }
    }
}

使用者端呼叫:

/**
 * @author tcy
 * @Date 13-09-2022
 */
public class Client {

    public static void main(String[] args) {
        ConcreteAggregate concreteAggregate=new ConcreteAggregate();
        concreteAggregate.add("老王");
        concreteAggregate.add("小王");
        concreteAggregate.add("小張");

        System.out.println("Aggregate聚合物件有:");

        Iterator iterator=concreteAggregate.getIterator();

        while (iterator.hasNext()){
            Object next = iterator.next();
            System.out.println(next.toString());
        }
        //遍歷剩下的角色
        iterator.forEachRemaining(ele -> System.out.println(ele));

    }

}

迭代器實現邏輯比較清晰,理解起來難度也不大,瞭解了該設計模式,趁熱打鐵看迭代器模式在原始碼中的應用。

三、原始碼應用

迭代器模式在Jdk中的集合類中有著廣泛的應用,我們以ArrayList作為典型。

在ArrayList實現迭代器時,同樣是有四個角色。

List抽象聚合類;

ArrayList具體聚合角色;

Iterator抽象迭代器;

ArrayList內部類Itr是具體迭代器;

我們可以看到ArrayList是把具體聚合角色和具體迭代器都寫在一個類中,Itr作為具體迭代物件是以內部類的形式。

ArrayList其實和我們案例中的方法長的很像,只不過ArrayList中定義了更多的方法,而且ArrayList還有一個內部類ListItr。

其實是迭代器的增強版,在繼承Itr的基礎之上實現ListIterator介面。

Iterator迭代器除了,hasNext()、next()、remove()方法以外,還有一個特別的forEachRemaining()方法,我們重點說下forEachRemaining()方法,該方法代表的意思是遍歷剩下的集合。

比如我們已經呼叫了該集合中的第一個元素,那麼遍歷時候就會自動忽略第一個元素,遍歷剩下的元素。

我們寫一個測試方法看效果:

public class Client {

    public static void main(String[] args) {
      
        // jdk ArrayList迭代器
        //建立一個元素型別為Integer的集合
        Collection<String> collection =  new ArrayList<>();

            //向集合中新增元素
            collection.add("老王");
            collection.add("小王");
            collection.add("小張");

        //獲取該集合的迭代器
        java.util.Iterator<String> iteratorJdk= collection.iterator();
        System.out.println("Arraylist聚合物件有:");
        //呼叫迭代器的經過集合實現的抽象方法遍歷集合元素
        while(iteratorJdk.hasNext())
        {
            System.out.println(iteratorJdk.next());
        }
        //呼叫forEachRemaining()方法遍歷集合元素
        iteratorJdk.forEachRemaining(ele -> System.out.println(ele));

    }

}
Arraylist聚合物件有:
老王
小王
小張

正常情況下,會列印兩次集合物件中的資訊,實際上只列印了一次,正是由於next呼叫過的元素,forEachRemaining不會再調。

看到這,想必你對迭代器已經有了初步的瞭解,當在遍歷元素時,除了使用for迴圈遍歷元素以外,提供了另外一種方式遍歷元素。

案例很好理解,原始碼中的應用也看得懂,但是實際開發中迭代器物件什麼時候用呢?想必大部分人並不是很清晰。

接著看迭代器物件的應用場景和優缺點,看從中能不能找到答案。

四、總結

當一個物件是一個聚合物件且需要對外提供遍歷方法時,可以使用迭代器模式,也即實際業務中定義的有聚合物件,裡面存放了我們需要的業務資料,為了讓業務資料的職責更清晰,我們就可以將編輯的方法提取出來,另外定義一個迭代器物件用於遍歷資料。

迭代器方式提供了不同的方式遍歷聚合物件,增加新的聚合類和迭代器類都是比較方便的,Java集合類中龐大的家族採用迭代器模式就是基於這種優點。

迭代器模式有設計模式的通用缺點——系統複雜性,迭代器模式將資料儲存和資料遍歷分開,增加了類的個數。

我已經連續更新了十幾篇設計模式部落格,推薦你一起學習。

一、設計模式概述

二、設計模式之工廠方法和抽象工廠

三、設計模式之單例和原型

四、設計模式之建造者模式

五、設計模式之代理模式

六、設計模式之介面卡模式

七、設計模式之橋接模式

八、設計模式之組合模式

九、設計模式之裝飾器模式

十、設計模式之外觀模式

十一、外觀模式之享元模式

十二、設計模式之責任鏈模式

十三、設計模式之命令模式

十四、設計模式之直譯器模式