天天看點

重學 Java 設計模式:實戰疊代器模式「模拟公司組織架構樹結構關系,深度疊代周遊人員資訊輸出場景」

作者:小傅哥

部落格:https://bugstack.cn -

原創系列專題文章

沉澱、分享、成長,讓自己和他人都能有所收獲!😄

一、前言

相信相信的力量!

從懵懂的少年,到拿起鍵盤,可以寫一個HelloWorld。多數人在這并不會感覺有多難,也不會認為做不出來。因為這樣的例子,有老師的指導、有書本的例子、有前人的經驗。但随着你的開發時間越來越長,要解決更複雜的問題或者技術創新,是以在網上搜了幾天幾夜都沒有答案,這個時候是否想過放棄,還是一直堅持不斷的嘗試一點點完成自己心裡要的結果。往往這種沒有前車之鑒需要自己解決問題的時候,可能真的會折磨到要崩潰,但你要願意執着、願意倔強,願意選擇相信相信的力量,就一定能解決。哪怕解決不了,也可以在這條路上摸索出其他更多的收獲,為後續前進的道路填充好墊腳石。

時間緊是寫垃圾代碼的理由?

擰螺絲?Ctrl+C、Ctrl+V?貼膏藥一樣寫代碼?沒有辦法,沒有時間,往往真的是借口,胸中沒用筆墨,才隻能湊合。難道一定是好好寫代碼就浪費時間,拼湊CRUD就快嗎,根本不可能的。因為不會,沒用實操過,很少架構出全場景的設計,才很難寫出優良的代碼。多增強自身的編碼(

國術

)修為,在各種編碼場景中讓自己變得老練,才好應對緊急情況下的需求開發和人員安排。就像韓信一樣有謀有略,才能執掌百萬雄兵。

不要隻是做個工具人!

因為日常的編寫簡單業務需求,導緻自己像個工具人一樣,日久天長的也就很少去深入學習更多技術棧。看見有工具、有元件、有架構,拿來就用用,反正沒什麼體量也不會出什麼問題。但如果你想要更多的收入,哪怕是重複的造輪子,你也要去嘗試造一個,就算不用到生産,自己玩玩總可以吧。有些事情隻有自己經曆過,才能有最深的感觸,參與過實踐過,才好總結點評學習。

二、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程一個,可以通過關注公衆号:

    bugstack蟲洞棧

    ,回複

    源碼下載下傳

    擷取(打開擷取的連結,找到序号18)
工程 描述
itstack-demo-design-15-00 開發樹形組織架構關系疊代器

三、疊代器模式介紹

重學 Java 設計模式:實戰疊代器模式「模拟公司組織架構樹結構關系,深度疊代周遊人員資訊輸出場景」

疊代器模式,常見的就是我們日常使用的

iterator

周遊。雖然這個設計模式在我們的實際業務開發中的場景并不多,但卻幾乎每天都要使用

jdk

為我們提供的

list

集合周遊。另外增強的for循環雖然是循環輸出資料,但是他不是疊代器模式。疊代器模式的特點是實作

Iterable

接口,通過

next

的方式擷取集合元素,同時具備對元素的删除等操作。而增強的for循環是不可以的。

這種設計模式的優點是可以讓我們以相同的方式,周遊不同的資料結構元素,這些資料結構包括;

數組

連結清單

等,而使用者在使用周遊的時候并不需要去關心每一種資料結構的周遊處理邏輯,從讓使用變得統一易用。

四、案例場景模拟

重學 Java 設計模式:實戰疊代器模式「模拟公司組織架構樹結構關系,深度疊代周遊人員資訊輸出場景」

在本案例中我們模拟疊代周遊輸出公司中樹形結構的組織架構關系中雇員清單

大部分公司的組織架構都是金字塔結構,也就這種樹形結構,分為一級、二級、三級等部門,每個組織部門由雇員填充,最終展現出一個整體的樹形組織架構關系。

一般我們常用的周遊就是jdk預設提供的方法,對list集合周遊。但是對于這樣的偏業務特性較大的樹形結構,如果需要使用到周遊,那麼就可以自己來實作。接下來我們會把這個組織層次關系通過樹形資料結構來實作,并完成疊代器功能。

五、疊代器模式周遊組織結構

在實作疊代器模式之前可以先閱讀下

java

list

方法關于

iterator

的實作部分,幾乎所有的疊代器開發都會按照這個模式來實作,這個模式主要分為以下幾塊;

  1. Collection,集合方法部分用于對自定義的資料結構添加通用方法;

    add

    remove

    iterator

    等核心方法。
  2. Iterable,提供擷取疊代器,這個接口類會被

    Collection

    繼承。
  3. Iterator,提供了兩個方法的定義;

    hasNext

    next

    ,會在具體的資料結構中寫實作方式。

除了這樣通用的疊代器實作方式外,我們的組織關系結構樹,是由節點和節點間的關系鍊構成,是以會比上述的内容多一些入參。

1. 工程結構

itstack-demo-design-15-02
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── group
    │           │	├── Employee.java
    │           │	├── GroupStructure.java
    │           │	└── Link.java
    │           └──  lang
    │            	├── Collection.java
    │            	├── Iterable.java
    │            	└── Iterator.java
    └── test
        └── java
            └── org.itstack.demo.design.test
                └── ApiTest.java
           

疊代器模式模型結構

重學 Java 設計模式:實戰疊代器模式「模拟公司組織架構樹結構關系,深度疊代周遊人員資訊輸出場景」
  • 以上是我們工程類圖的模型結構,左側是對疊代器的定義,右側是在資料結構中實作疊代器功能。
  • 關于左側部分的實作與jdk中的方式是一樣的,是以在學習的過程中可以互相參考,也可以自己擴充學習。
  • 另外這個周遊方式一個樹形結構的深度周遊,為了可以更加讓學習的小夥伴容易了解,這裡我實作了一種比較簡單的樹形結構深度周遊方式。後續讀者也可以把周遊擴充為橫向周遊也就是寬度周遊。

2. 代碼實作

2.1 雇員實體類

/**
 * 雇員
 */
public class Employee {

    private String uId;   // ID
    private String name;  // 姓名
    private String desc;  // 備注
    
    // ...get/set
}
           
  • 這是一個簡單的雇員類,也就是公司員工的資訊類,包括必要的資訊;id、姓名、備注。

2.2 樹節點鍊路

/**
 * 樹節點鍊路
 */
public class Link {

    private String fromId; // 雇員ID
    private String toId;   // 雇員ID    
    
    // ...get/set
}
           
  • 這個類用于描述結構樹中的各個節點之間的關系鍊,也就是

    A to B

    B to C

    B to D

    ,以此描述出一套完整的樹組織結構。

2.3 疊代器定義

public interface Iterator<E> {

    boolean hasNext();

    E next();
    
}
           
  • 這裡的這個類和

    java

    jdk

    中提供的是一樣的,這樣也方面後續讀者可以對照

    list

    Iterator

    進行源碼學習。
  • 方法描述;

    hasNext

    ,判斷是否有下一個元素、

    next

    ,擷取下一個元素。這個在

    list

    的周遊中是經常用到的。

2.4 可疊代接口定義

public interface Iterable<E> {

    Iterator<E> iterator();

}
           
  • 這個接口中提供了上面疊代器的實作

    Iterator

    的擷取,也就是後續在自己的資料結構中需要實作疊代器的功能并交給

    Iterable

    ,由此讓外部調用方進行擷取使用。

2.5 集合功能接口定義

public interface Collection<E, L> extends Iterable<E> {

    boolean add(E e);

    boolean remove(E e);

    boolean addLink(String key, L l);

    boolean removeLink(String key);

    Iterator<E> iterator();

}
           
  • 這裡我們定義集合操作接口;

    Collection

    ,同時繼承了另外一個接口

    Iterable

    的方法

    iterator()

    。這樣後續誰來實作這個接口,就需要實作上述定義的一些基本功能;

    添加元素

    删除元素

    周遊

  • 同時你可能注意到這裡定義了兩個泛型

    <E, L>

    ,因為我們的資料結構一個是用于添加元素,另外一個是用于添加樹節點的鍊路關系。

2.6 (核心)疊代器功能實作

public class GroupStructure implements Collection<Employee, Link> {

    private String groupId;                                                 // 組織ID,也是一個組織鍊的頭部ID
    private String groupName;                                               // 組織名稱
    private Map<String, Employee> employeeMap = new ConcurrentHashMap<String, Employee>();  // 雇員清單
    private Map<String, List<Link>> linkMap = new ConcurrentHashMap<String, List<Link>>();  // 組織架構關系;id->list
    private Map<String, String> invertedMap = new ConcurrentHashMap<String, String>();       // 反向關系鍊

    public GroupStructure(String groupId, String groupName) {
        this.groupId = groupId;
        this.groupName = groupName;
    }

    public boolean add(Employee employee) {
        return null != employeeMap.put(employee.getuId(), employee);
    }

    public boolean remove(Employee o) {
        return null != employeeMap.remove(o.getuId());
    }

    public boolean addLink(String key, Link link) {
        invertedMap.put(link.getToId(), link.getFromId());
        if (linkMap.containsKey(key)) {
            return linkMap.get(key).add(link);
        } else {
            List<Link> links = new LinkedList<Link>();
            links.add(link);
            linkMap.put(key, links);
            return true;
        }
    }

    public boolean removeLink(String key) {
        return null != linkMap.remove(key);
    }

    public Iterator<Employee> iterator() {

        return new Iterator<Employee>() {

            HashMap<String, Integer> keyMap = new HashMap<String, Integer>();

            int totalIdx = 0;
            private String fromId = groupId;  // 雇員ID,From
            private String toId = groupId;   // 雇員ID,To

            public boolean hasNext() {
                return totalIdx < employeeMap.size();
            }

            public Employee next() {
                List<Link> links = linkMap.get(toId);
                int cursorIdx = getCursorIdx(toId);

                // 同級節點掃描
                if (null == links) {
                    cursorIdx = getCursorIdx(fromId);
                    links = linkMap.get(fromId);
                }

                // 上級節點掃描
                while (cursorIdx > links.size() - 1) {
                    fromId = invertedMap.get(fromId);
                    cursorIdx = getCursorIdx(fromId);
                    links = linkMap.get(fromId);
                }

                // 擷取節點
                Link link = links.get(cursorIdx);
                toId = link.getToId();
                fromId = link.getFromId();
                totalIdx++;

                // 傳回結果
                return employeeMap.get(link.getToId());
            }
             
            // 給每個層級定義寬度周遊進度
            public int getCursorIdx(String key) {
                int idx = 0;
                if (keyMap.containsKey(key)) {
                    idx = keyMap.get(key);
                    keyMap.put(key, ++idx);
                } else {
                    keyMap.put(key, idx);
                }
                return idx;
            }
        };
    }

}
           
  • 以上的這部分代碼稍微有點長,主要包括了對元素的添加和删除。另外最重要的是對周遊的實作

    new Iterator<Employee>

  • 添加和删除元素相對來說比較簡單,使用了兩個map數組結構進行定義;

    雇員清單

    組織架構關系;id->list

    。當元素添加元素的時候,會分别在不同的方法中向

    map

    結構中進行填充指向關系(A->B),也就建構出了我們的樹形組織關系。

疊代器實作思路

  1. 這裡的樹形結構我們需要做的是深度周遊,也就是左側的一直周遊到最深節點。
  2. 當周遊到最深節點後,開始周遊最深節點的橫向節點。
  3. 當橫向節點周遊完成後則向上尋找橫向節點,直至樹結構全部周遊完成。

3. 測試驗證

3.1 編寫測試類

@Test
public void test_iterator() { 
    // 資料填充
    GroupStructure groupStructure = new GroupStructure("1", "小傅哥");  
    
    // 雇員資訊
    groupStructure.add(new Employee("2", "花花", "二級部門"));
    groupStructure.add(new Employee("3", "豆包", "二級部門"));
    groupStructure.add(new Employee("4", "蹦蹦", "三級部門"));
    groupStructure.add(new Employee("5", "大燒", "三級部門"));
    groupStructure.add(new Employee("6", "虎哥", "四級部門"));
    groupStructure.add(new Employee("7", "玲姐", "四級部門"));
    groupStructure.add(new Employee("8", "秋雅", "四級部門"));   
    
    // 節點關系 1->(1,2) 2->(4,5)
    groupStructure.addLink("1", new Link("1", "2"));
    groupStructure.addLink("1", new Link("1", "3"));
    groupStructure.addLink("2", new Link("2", "4"));
    groupStructure.addLink("2", new Link("2", "5"));
    groupStructure.addLink("5", new Link("5", "6"));
    groupStructure.addLink("5", new Link("5", "7"));
    groupStructure.addLink("5", new Link("5", "8"));       

    Iterator<Employee> iterator = groupStructure.iterator();
    while (iterator.hasNext()) {
        Employee employee = iterator.next();
        logger.info("{},雇員 Id:{} Name:{}", employee.getDesc(), employee.getuId(), employee.getName());
    }
}
           

3.2 測試結果

22:23:37.166 [main] INFO  org.itstack.demo.design.test.ApiTest - 二級部門,雇員 Id:2 Name:花花
22:23:37.168 [main] INFO  org.itstack.demo.design.test.ApiTest - 三級部門,雇員 Id:4 Name:蹦蹦
22:23:37.169 [main] INFO  org.itstack.demo.design.test.ApiTest - 三級部門,雇員 Id:5 Name:大燒
22:23:37.169 [main] INFO  org.itstack.demo.design.test.ApiTest - 四級部門,雇員 Id:6 Name:虎哥
22:23:37.169 [main] INFO  org.itstack.demo.design.test.ApiTest - 四級部門,雇員 Id:7 Name:玲姐
22:23:37.169 [main] INFO  org.itstack.demo.design.test.ApiTest - 四級部門,雇員 Id:8 Name:秋雅
22:23:37.169 [main] INFO  org.itstack.demo.design.test.ApiTest - 二級部門,雇員 Id:3 Name:豆包

Process finished with exit code 0
           
  • 從周遊的結果可以看到,我們是順着樹形結構的深度開始周遊,一直到右側的節點3;

    雇員 Id:2、雇員 Id:4...雇員 Id:3

六、總結

  • 疊代器的設計模式從以上的功能實作可以看到,滿足了單一職責和開閉原則,外界的調用方也不需要知道任何一個不同的資料結構在使用上的周遊差異。可以非常友善的擴充,也讓整個周遊變得更加幹淨整潔。
  • 但從結構的實作上可以看到,疊代器模式的實作過程相對來說是比較負責的,類的實作上也擴增了需要外部定義的類,使得周遊與原資料結構分開。雖然這是比較麻煩的,但可以看到在使用java的jdk時候,疊代器的模式還是很好用的,可以非常友善擴充和更新。
  • 以上的設計模式場景實作過程可能對新人有一些不好了解點,包括;疊代器三個和接口的定義、樹形結構的資料關系、樹結構深度周遊思路。這些都需要反複實作練習才能深入的了解,事必躬親,親曆親為,才能讓自己掌握這些知識。

七、推薦閱讀

  • 1. 重學 Java 設計模式:實戰工廠方法模式「多種類型商品不同接口,統一發獎服務搭建場景」

  • 2. 重學 Java 設計模式:實戰原型模式「上機考試多套試,每人題目和答案亂序排列場景」

  • 3. 重學 Java 設計模式:實戰橋接模式「多支付管道(微信、支付寶)與多支付模式(刷臉、指紋)場景」

  • 4. 重學 Java 設計模式:實戰組合模式「營銷差異化人群發券,決策樹引擎搭建場景」

  • 5. 重學 Java 設計模式:實戰外觀模式「基于SpringBoot開發門面模式中間件,統一控制接口白名單場景」

  • 6. 重學 Java 設計模式:實戰享元模式「基于Redis秒殺,提供活動與庫存資訊查詢場景」

公衆号:bugstack蟲洞棧 | 作者小傅哥多年從事一線網際網路 Java 開發的學習曆程技術彙總,旨在為大家提供一個清晰詳細的學習教程,側重點更傾向編寫Java核心内容。如果能為您提供幫助,請給予支援(關注、點贊、分享)!

繼續閱讀