重學 Java 設計模式:實戰訪問者模式「模擬家長與校長,對學生和老師的不同視角資訊的訪問場景」

小傅哥發表於2020-07-10


作者:小傅哥
部落格:https://bugstack.cn - 原創系列專題文章

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

一、前言

能力,是你前行的最大保障

年齡會不斷的增長,但是什麼才能讓你不慌張。一定是能力,即使是在一個看似還很安穩的工作中也是一樣,只有擁有能留下的本事跳出去的能力,你才會是安穩的。而能力的提升是不斷突破自己的未知也就是擴充寬度,以及在專業領域建設個人影響力也就是深度。如果日復日365天,天天搬磚,一切都沒有變化的重複只能讓手上增長點老繭,歲月又嘆人生苦短。

站得高看的遠嗎?

站得高確實能看得遠,也能給自己更多的追求。但,站的高了,原本看的清的東西就變得看不清了。視角和重點的不同,會讓我們有很多不同的選擇,而腳踏實地是給自己奠定能攀升起來的基石,當真的可以四平八穩的走向山頭的時候,才是適合看到更遠的時候。

數學好才能學編碼嗎

往往很多時候學程式設計的初學者都會問數學不好能學會嗎?其實可以想想那為什麼數學不好呢?在這條沒學好的路上,你為它們付出了多少時間呢?如果一件事情你敢做到和寫自己名字一樣熟悉,還真的有難的東西嗎。從大學到畢業能寫出40萬行程式碼的,還能愁找不到工作嗎,日積月累,每一天並沒有多難。難的你想用最後一個月的時間學完人家四年努力的成績的。學習,要趁早。

二、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程三個,可以通過關注公眾號bugstack蟲洞棧,回覆原始碼下載獲取(開啟獲取的連結,找到序號18)
工程 描述
itstack-demo-design-22-00 場景模擬工程;模擬學生和老師資訊不同視角訪問

三、訪問者模式介紹

訪問者模式,圖片來自 refactoringguru.cn

訪問者要解決的核心事項是,在一個穩定的資料結構下,例如使用者資訊、僱員資訊等,增加易變的業務訪問邏輯。為了增強擴充套件性,將這兩部分的業務解耦的一種設計模式。

美女吃冰激凌

說白了訪問者模式的核心在於同一個事物不同視角下的訪問資訊不同,比如一個美女手裡拿個冰激凌。小朋友會注意冰激凌,大朋友會找自己喜歡的地方觀測敵情。

四、案例場景模擬

場景模擬;校園中的學生和老師對於不同使用者的訪問視角

在本案例中我們模擬校園中的學生和老師對於不同使用者的訪問視角

這個案例場景我們模擬校園中有學生和老師兩種身份的使用者,那麼對於家長和校長關心的角度來看,他們的視角是不同的。家長更關心孩子的成績和老師的能力,校長更關心老師所在班級學生的人數和升學率{此處模擬的}。

那麼這樣學生老師就是一個固定資訊的內容,而想讓不同視角的使用者獲取關心的資訊,就比較適合使用觀察者模式來實現,從而讓實體與業務解耦,增強擴充套件性。但觀察者模式的整體類結構相對複雜,需要梳理清楚再開發

五、訪問者模式搭建工程

訪問者模式的類結構相對其他設計模式來說比較複雜,但這樣的設計模式在我看來更加燒氣有魅力,它能闊開你對程式碼結構的新認知,用這樣思維不斷的建設出更好的程式碼架構。

關於這個案例的核心邏輯實現,有以下幾點;

  1. 建立使用者抽象類和抽象訪問方法,再由不同的使用者實現;老師和學生。
  2. 建立訪問者介面,用於不同人員的訪問操作;校長和家長。
  3. 最終是對資料的看板建設,用於實現不同視角的訪問結果輸出。

1. 工程結構

itstack-demo-design-22-00
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── user
    │           │	  ├── impl
    │           │	  │     ├── Student.java
    │           │	  │     └── Teacher.java
    │           │	  └── User.java   
    │           ├── visitor
    │           │	  ├── impl
    │           │	  │     ├── Parent.java
    │           │	  │     └── Principal.java
    │           │	  └── Visitor.java
    │           └──  DataView.java
    └── test
        └── java
            └── org.itstack.demo.design.test
                └── ApiTest.java

訪問者模式模型結構

訪問者模式模型結構

以上是檢視展示了程式碼的核心結構,主要包括不同視角下的不同使用者訪問模型。

在這裡有一個關鍵的點非常重要,也就是整套設計模式的核心組成部分;visitor.visit(this),這個方法在每一個使用者實現類裡,包括;StudentTeacher。在以下的實現中可以重點關注。

2. 程式碼實現

2.1 定義使用者抽象類

// 基礎使用者資訊
public abstract class User {

    public String name;      // 姓名
    public String identity;  // 身份;重點班、普通班 | 特級教師、普通教師、實習教師
    public String clazz;     // 班級

    public User(String name, String identity, String clazz) {
        this.name = name;
        this.identity = identity;
        this.clazz = clazz;
    }

    // 核心訪問方法
    public abstract void accept(Visitor visitor);

}
  • 基礎資訊包括;姓名、身份、班級,也可以是一個業務使用者屬性類。
  • 定義抽象核心方法,abstract void accept(Visitor visitor),這個方法是為了讓後續的使用者具體實現者都能提供出一個訪問方法,共外部使用。

2.2 實現使用者資訊(老師和學生)

老師類

public class Teacher extends User {

    public Teacher(String name, String identity, String clazz) {
        super(name, identity, clazz);
    }

    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    // 升本率
    public double entranceRatio() {
        return BigDecimal.valueOf(Math.random() * 100).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

}

學生類

public class Student extends User {

    public Student(String name, String identity, String clazz) {
        super(name, identity, clazz);
    }

    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public int ranking() {
        return (int) (Math.random() * 100);
    }

}
  • 這裡實現了老師和學生類,都提供了父類的建構函式。
  • accept方法中,提供了本地物件的訪問;visitor.visit(this),這塊需要加深理解。
  • 老師和學生類又都單獨提供了各自的特性方法;升本率(entranceRatio)、排名(ranking),類似這樣的方法可以按照業務需求進行擴充套件。

2.3 定義訪問資料介面

public interface Visitor {

    // 訪問學生資訊
    void visit(Student student);

    // 訪問老師資訊
    void visit(Teacher teacher);

}
  • 訪問的介面比較簡單,相同的方法名稱,不同的入參使用者型別。
  • 讓具體的訪問者類,在實現時可以關注每一種使用者型別的具體訪問資料物件,例如;升學率和排名。

2.4 實現訪問型別(校長和家長)

訪問者;校長

public class Principal implements Visitor {

    private Logger logger = LoggerFactory.getLogger(Principal.class);

    public void visit(Student student) {
        logger.info("學生資訊 姓名:{} 班級:{}", student.name, student.clazz);
    }

    public void visit(Teacher teacher) {
        logger.info("學生資訊 姓名:{} 班級:{} 升學率:{}", teacher.name, teacher.clazz, teacher.entranceRatio());
    }

}

訪問者;家長

public class Parent implements Visitor {

    private Logger logger = LoggerFactory.getLogger(Parent.class);

    public void visit(Student student) {
        logger.info("學生資訊 姓名:{} 班級:{} 排名:{}", student.name, student.clazz, student.ranking());
    }

    public void visit(Teacher teacher) {
        logger.info("老師資訊 姓名:{} 班級:{} 級別:{}", teacher.name, teacher.clazz, teacher.identity);
    }

}
  • 以上是兩個具體的訪問者實現類,他們都有自己的視角需求。
  • 校長關注;學生的名稱和班級,老師對這個班級的升學率
  • 家長關注;自己家孩子的排名,老師的班級和教學水平

2.5 資料看版

public class DataView {

    List<User> userList = new ArrayList<User>();

    public DataView() {
        userList.add(new Student("謝飛機", "重點班", "一年一班"));
        userList.add(new Student("windy", "重點班", "一年一班"));
        userList.add(new Student("大毛", "普通班", "二年三班"));
        userList.add(new Student("Shing", "普通班", "三年四班"));
        userList.add(new Teacher("BK", "特級教師", "一年一班"));
        userList.add(new Teacher("娜娜Goddess", "特級教師", "一年一班"));
        userList.add(new Teacher("dangdang", "普通教師", "二年三班"));
        userList.add(new Teacher("澤東", "實習教師", "三年四班"));
    }

    // 展示
    public void show(Visitor visitor) {
        for (User user : userList) {
            user.accept(visitor);
        }
    }

}
  • 首先在這個類中初始化了基本的資料,學生和老師的資訊。
  • 並提供了一個展示類,通過傳入不同的觀察者(校長、家長)而差異化的列印資訊。

3. 測試驗證

3.1 編寫測試類

@Test
public void test(){
    DataView dataView = new DataView();      

    logger.info("\r\n家長視角訪問:");
    dataView.show(new Parent());     // 家長

    logger.info("\r\n校長視角訪問:");
    dataView.show(new Principal());  // 校長
}
  • 從測試類可以看到,家長和校長分別是不同的訪問視角。

3.2 測試結果

23:00:39.726 [main] INFO  org.itstack.demo.design.test.ApiTest - 
家長視角訪問:
23:00:39.730 [main] INFO  o.i.demo.design.visitor.impl.Parent - 學生資訊 姓名:謝飛機 班級:一年一班 排名:62
23:00:39.730 [main] INFO  o.i.demo.design.visitor.impl.Parent - 學生資訊 姓名:windy 班級:一年一班 排名:51
23:00:39.730 [main] INFO  o.i.demo.design.visitor.impl.Parent - 學生資訊 姓名:大毛 班級:二年三班 排名:16
23:00:39.730 [main] INFO  o.i.demo.design.visitor.impl.Parent - 學生資訊 姓名:Shing 班級:三年四班 排名:98
23:00:39.730 [main] INFO  o.i.demo.design.visitor.impl.Parent - 老師資訊 姓名:BK 班級:一年一班 級別:特級教師
23:00:39.730 [main] INFO  o.i.demo.design.visitor.impl.Parent - 老師資訊 姓名:娜娜Goddess 班級:一年一班 級別:特級教師
23:00:39.730 [main] INFO  o.i.demo.design.visitor.impl.Parent - 老師資訊 姓名:dangdang 班級:二年三班 級別:普通教師
23:00:39.730 [main] INFO  o.i.demo.design.visitor.impl.Parent - 老師資訊 姓名:澤東 班級:三年四班 級別:實習教師
23:00:39.730 [main] INFO  org.itstack.demo.design.test.ApiTest - 
校長視角訪問:
23:00:39.731 [main] INFO  o.i.d.design.visitor.impl.Principal - 學生資訊 姓名:謝飛機 班級:一年一班
23:00:39.731 [main] INFO  o.i.d.design.visitor.impl.Principal - 學生資訊 姓名:windy 班級:一年一班
23:00:39.731 [main] INFO  o.i.d.design.visitor.impl.Principal - 學生資訊 姓名:大毛 班級:二年三班
23:00:39.731 [main] INFO  o.i.d.design.visitor.impl.Principal - 學生資訊 姓名:Shing 班級:三年四班
23:00:39.733 [main] INFO  o.i.d.design.visitor.impl.Principal - 學生資訊 姓名:BK 班級:一年一班 升學率:70.62
23:00:39.733 [main] INFO  o.i.d.design.visitor.impl.Principal - 學生資訊 姓名:娜娜Goddess 班級:一年一班 升學率:23.15
23:00:39.734 [main] INFO  o.i.d.design.visitor.impl.Principal - 學生資訊 姓名:dangdang 班級:二年三班 升學率:70.98
23:00:39.734 [main] INFO  o.i.d.design.visitor.impl.Principal - 學生資訊 姓名:澤東 班級:三年四班 升學率:90.14

Process finished with exit code 0
  • 通過測試結果可以看到,家長和校長的訪問視角同步,資料也是差異化的。
  • 家長視角看到學生的排名;排名:62排名:51排名:16排名:98
  • 校長視角看到班級升學率;升學率:70.62升學率:23.15升學率:70.98升學率:90.14
  • 通過這樣的測試結果,可以看到訪問者模式的初心和結果,在適合的場景運用合適的模式,非常有利於程式開發。

六、總結

  • 從以上的業務場景中可以看到,在嵌入訪問者模式後,可以讓整個工程結構變得容易新增和修改。也就做到了系統服務之間的解耦,不至於為了不同型別資訊的訪問而增加很多多餘的if判斷或者類的強制轉換。也就是通過這樣的設計模式而讓程式碼結構更加清晰。
  • 另外在實現的過程可能你可能也發現了,定義抽象類的時候還需要等待訪問者介面的定義,這樣的設計首先從實現上會讓程式碼的組織變得有些難度。另外從設計模式原則的角度來看,違背了迪米特原則,也就是最少知道原則。因此在使用上一定要符合場景的運用,以及提取這部分設計思想的精髓。
  • 好的學習方式才好更容易接受知識,學習程式設計的更需要的不單單是看,而是操作。二十多種設計模式每一種都有自己的設計技巧,也可以說是巧妙之處,這些巧妙的地方往往是解決複雜難題的最佳視角。親力親為,才能為所欲為,為了自己的慾望而努力!

七、推薦閱讀

相關文章