1. 程式人生 > >設計模式是什麼鬼(迭代器)講得形象化,圖形加助理解和記憶

設計模式是什麼鬼(迭代器)講得形象化,圖形加助理解和記憶

文章出處:https://www.javazhiyin.com/tag/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F

作者:凸凹裡歐

        方法迭代:代的更迭,從初代到末代的遍歷,指對某類集合中的每個元素按順序取出的行為。舉個例子,通常我們讀小說是從前往後翻,一頁接著一頁地讀,這樣我們才可以瞭解一個連續完整的故事,那這就需要我們順序地迭代整本書的每一頁內容。

設計模式是什麼鬼(迭代器)

        相信大家都用過集合類吧,最常用的比如List,Set,Map以及各種各樣不同資料表示實現,總之是把某一批類似的元素按某種資料結構集合起來作為一個整體來引用,不至於元素丟的到處都是難以維護,當要用到每個元素的時候,我們需要將它們一個個的取出來,但是對不同的資料型別訪問方式各有不同,於是我們就需要定義統一的迭代器來標準化這種遍歷行為。

設計模式是什麼鬼(迭代器)

         在此著重補充下:介面繼承介面,介面不實現介面;抽象類繼承介面 ;類繼承類/抽象類。

        為何會有各種各樣的遍歷方式呢?比如說彈夾,裝填子彈的時候要一顆一顆的進行壓棧,等到射擊的時候就需要迭代操作,先出棧拿出最後裝填的子彈再進行射擊,然後反向往前遍歷直到最初裝填的子彈直到射完為止,此刻也代表著迭代的結束,整個過程像是記憶體棧的操作,先進後出,後進先出,當然這並不代表其迭代器非要先進後出,這裡只是舉例說明針對不同資料型別進行不同的迭代方式。

設計模式是什麼鬼(迭代器)

        以上資料結構及迭代器其實都有現成的類去實現,那麼我們這裡來自定義一種全新的資料結構,可以防止碰瓷!?牛吹得有點大了,我們就以行車記錄儀舉例,大家先想想怎麼來記錄一段一段的視訊呢?如果我們簡單的利用ArrayList去記錄,那它得有多大空間去支援一直拍攝視訊?

設計模式是什麼鬼(迭代器)

        我們知道其實它是迴圈覆寫的,待空間不夠用時,最新的視訊總會去覆蓋掉最老的視訊,以首尾相接的環形結構解決空間有限的問題。好,開始程式碼實戰,首先我們定義一個行車記錄儀類。

public class DrivingRecorder {
    private int index = -1;// 當前記錄位置
    private String[] records = new String[10];// 假設只能記錄10條視訊

    public void append(String record) {
        if (index == 9) {// 迴圈覆蓋
            index = 0;
        } else {
            index++;
        }
        records[index] = record;
    }

    public void display() {// 迴圈陣列並顯示所有10條記錄
        for (int i = 0; i < 10; i++) {
            System.out.print("[" + i + "]:" + records[i] + "  ");
        }

    }

    public void displayInOrder() {//按順序從新到舊顯示10條記錄
        for (int i = index, loopCount = 0; loopCount < 10; i = i == 0 ? i = 9 : i - 1, loopCount++) {
            System.out.print(records[i]+" ");
        }
    }

    public static void main(String[] args) {
        DrivingRecorder drivingRecorder = new DrivingRecorder();
        for (int i = 0; i < 11; i++) {
            drivingRecorder.append(String.valueOf(i));
        }

        drivingRecorder.display();
        System.out.println();
        drivingRecorder.displayInOrder();
        System.out.println();

    }
}

輸出結果:

[0]:10  [1]:1  [2]:2  [3]:3  [4]:4  [5]:5  [6]:6  [7]:7  [8]:8  [9]:9  
10 9 8 7 6 5 4 3 2 1  

        假設我們的記錄儀儲存空間只夠錄10段視訊,我們定義一個原始的字串陣列(第3行)來模擬記錄,並且用一個遊標(第2行)來記錄當前記錄所在位置。當插入視訊的時候(第5行)我們得先看有沒有錄滿到頭了,如果是的話就要把遊標調整到頭以後再記錄視訊。視訊目前可以迴圈記錄了,但總得給使用者顯示出來看吧,於是我們又提供了兩個顯示方法,一個是按預設陣列順序顯示,一個是按使用者習慣從新到舊地顯示內容(邏輯稍微複雜了點但這裡不是重點,讀者可以略過),開始寫使用者類來使用這個記錄儀。

public class Client {
    public static void main(String[] args) {
        DrivingRecorder dr = new DrivingRecorder();
        //假設記錄了12條視訊
        for (int i = 0; i < 12; i++) {
            dr.append("視訊_" + i);
        }
        dr.display();
                /*按原始順序顯示,視訊0與1分別被10與11覆蓋了。
            0: 視訊_10
            1: 視訊_11
            2: 視訊_2
            3: 視訊_3
           4: 視訊_4
            5: 視訊_5
            6: 視訊_6
            7: 視訊_7
            8: 視訊_8
            9: 視訊_9
        */
        dr.displayInOrder();
                /*按順序從新到舊顯示
            視訊_11
            視訊_10
            視訊_9
            視訊_8
            視訊_7
            視訊_6
            視訊_5
            視訊_4
            視訊_3
           視訊_2
        */
    }
}

輸出結果:

[0]:視訊_10  [1]:視訊_11  [2]:視訊_2  [3]:視訊_3  [4]:視訊_4  [5]:視訊_5  [6]:視訊_6  [7]:視訊_7  [8]:視訊_8  [9]:視訊_9  視訊_11 視訊_10 視訊_9 視訊_8 視訊_7 視訊_6 視訊_5 視訊_4 視訊_3 視訊_2 

        我們以視訊_0開始,假設空間已經記錄到視訊_11,一共12條視訊會不會撐爆空間呢?我們來執行以下看會發生什麼。奇蹟出現了,視訊_10和視訊_11分別覆蓋了最早記錄的視訊_0和視訊_1,完美!產品可以量產了!

正當我們要舉杯歡慶的時候客戶開始吐槽了,你只是簡單在螢幕上顯示一下就完事了麼?功能也太差了點!我要的是把原始視訊拿出來給我,我好上報交警作為證據,總之你甭管我怎麼加工處理,你總得把原始資料拿出來給我。

        這可把我們難住了,這些資料都是在記錄儀內部封裝好的私有資料,如果直接改成public暴露出去,試想使用者隨意增加刪除,完全不管遊標位置,這會破壞掉內部邏輯機制,資料封裝的意義何在?我們鬼斧神工設計將瞬間崩塌,使用者資料安全無法保證,bug肆虐。

設計模式是什麼鬼(迭代器)

所以,我們絕不能更改資料的私有化封裝,而之前暴露給使用者的顯示方法顯得非常死板,擴充套件性極差,我們決定以迭代器取而代之,如此提供給使用者遍歷資料的功能,拿出去的資料使用者便可以隨意使用,這樣就避免了使用者染指內部機件的危險。首先我們需要定義一個迭代器介面標準來規範抽象,看程式碼。

package com.liuxd;

/**
 * Created by Liuxd on 2018/11/7.
 */
public interface Iterator<E> {
    E next();//返回下一個元素

    boolean hasNext();//是否還有下一個元素
}

很簡單吧?此介面標準定義了兩個方法,next方法用於返回下一個資料元素,而hasNext用於詢問迭代器是否還有下一個元素,當然我們也可以不定義這個介面,而是直接用JDK中util包自帶的。接下來更改我們的行車記錄儀,加入iterator方法用於獲取迭代器,開始我們的迭代器實現。

package com.liuxd;

/**
 * Created by Liuxd on 2018/11/7.
 */
public class DrivingRecorder2 {
    private int index = -1;// 當前記錄位置
    private String[] records = new String[10];// 假設只能記錄10條視訊

    public void append(String record) {
        if (index == 9) {// 迴圈覆蓋
            index = 0;
        } else {
            index++;
        }
        records[index] = record;
    }

    public Iterator<String> iterator() {
        return new Itr();
    }

    private class Itr implements Iterator<String> {
        int cursor = index;// 迭代器遊標,不染指原始遊標。
        int loopCount = 0;

        @Override
        public boolean hasNext() {
            return loopCount < 10;
        }

        @Override
        public String next() {
            int i = cursor;// 記錄即將返回的遊標位置
            if (cursor == 0) {
                cursor = 9;
            } else {
                cursor--;
            }
            loopCount++;
            return records[i];
        }
    }

    ;
}

這裡我們加入內部類(第18行)來定義迭代器實現,為的是能輕鬆訪問到記錄儀私有資料集。內部類實現了兩個標配方法hasNext與next,內部邏輯看起來簡單多了,大家可以自行理解,這裡就不做講解了。最後重點來了,使用者可以進行如下操作了。

package com.liuxd;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Liuxd on 2018/11/7.
 */
public class Client2 {
    public static void main(String[] args) {
        DrivingRecorder2 dr = new DrivingRecorder2();
        // 假設記錄了12條視訊
        for (int i = 0; i < 12; i++) {
            dr.append("視訊_" + i);
        }
        //使用者要獲取交通事故視訊,定義事故列表。
        List<String> accidents = new ArrayList<>();
        //使用者拿到迭代器
        Iterator<String> it = dr.iterator();

        while (it.hasNext()) {//如果還有下一條則繼續迭代
            String video = it.next();
            System.out.println(video);
            //使用者翻看視訊發現10和8可作為證據。
            if ("視訊_10".equals(video) || "視訊_8".equals(video)) {
                accidents.add(video);
            }
        }
        //拿到兩個視訊集accidents交給交警檢視。
        System.out.println("事故證據:" + accidents);
                /*
        視訊_11
        視訊_10
        視訊_9
        視訊_8
        視訊_7
        視訊_6
        視訊_5
        視訊_4
        視訊_3
        視訊_2 
        事故證據:[視訊_10, 視訊_8]
        */
    }
}

輸出結果:

視訊_11
視訊_10
視訊_9
視訊_8
視訊_7
視訊_6
視訊_5
視訊_4
視訊_3
視訊_2
事故證據:[視訊_10, 視訊_8]

    使用者拿到迭代器進行遍歷檢視,注意第18行,使用者將這12條視訊中的10和8拿出來拷貝U盤並交給交警作為呈堂證供判對方碰瓷,以證明自己的清白。

     當然,我們這裡只是保持極簡說明問題,讀者可以自行重構程式碼,尤其是實現迭代器的remove方法非常重要(注意遊標的調整),這樣使用者便可以刪除資料了。

      總之,對於任何的集合類,既要保證內部資料表示不暴露給外部以防搞亂內部機制,還要提供給使用者遍歷並訪問到每個資料的許可權,迭代器模式則成就了魚與熊掌兼得的可能,它提供了所有集合對外開放的統一標準介面,內政容不得干涉,但是經濟依舊要開放。

設計模式是什麼鬼(迭代器)