重學 Java 設計模式:實戰備忘錄模式「模擬互聯網系統上線過程當中,配置文件回滾場景」


做者:小傅哥
博客:https://bugstack.cn - 原創系列專題文章html

沉澱、分享、成長,讓本身和他人都能有所收穫!😄

1、前言

實現不了是研發的藉口?java

實現不了,有時候是功能複雜度較高難以實現,有時候是工期較短實現不完。而編碼的行爲又是一個不太好量化的過程,一樣一個功能每一個人的實現方式不同,遇到開發問題解決問題的速度也不同。除此以外還很很差給產品解釋具體爲何要這個工期時間,這就像蓋樓的圖紙最終要多少水泥砂漿同樣。那麼這時研發會盡量的去經過一些經驗,制定流程規範、設計、開發、評審等,肯定一個能夠完成的時間範圍,又避免風險的時間點後。再被壓縮,每每會出一些矛盾點,能壓縮要解釋爲何以前要那麼多時間,不能壓縮又有各方不斷施加的壓力。所以有時候不必定是藉口,是要考慮如何讓整個團隊健康的發展。程序員

鼓勵有時比壓力要重要!數據庫

在學習的過程當中,不少時候咱們聽到的都是,你要怎樣,怎樣,你瞧瞧誰誰誰,哪怕今天聽不到這樣的聲音了,但由於曾經反覆聽到過而致使心裏抗拒。雖然也知道本身要去學,可是很難堅持,學着學着就沒有了方向,看到還有那麼多不會的就更慌了,以致於最後心態崩了,更不肯意學。其實程序員的壓力並不小,想成長几乎是須要一直的學習,就像彷佛不再敢說精通java了同樣,知識量實在是隨着學習的深刻,愈來愈深,愈來愈廣。因此須要,開心學習,快樂成長!設計模式

臨陣的你好像一直很着急!安全

常常的聽到;老師明天就要了你幫我弄弄吧你給我寫一下完事我就學此次着急如今這不是沒時間學嗎快給我看看。其實看到的相似的還有不少,很納悶你的着急怎麼來的,不太可能,人在家中坐,禍從天上落。老師怎麼就那個時間找你了,老闆怎麼就今天管你要了,還不是日積月累你沒有學習,臨時抱佛腳亂着急!即便後來真的有人幫你了,但最好不要放鬆,要儘快學會,躲得過初一還有初二呢!微信

2、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程一個,能夠經過關注公衆號bugstack蟲洞棧,回覆源碼下載獲取(打開獲取的連接,找到序號18)
工程 描述
itstack-demo-design-17-00 開發配置文件備忘錄

3、備忘錄模式介紹

備忘錄模式,圖片來自 refactoringguru.cn

備忘錄模式是以能夠恢復或者說回滾,配置、版本、悔棋爲核心功能的設計模式,而這種設計模式屬於行爲模式。在功能實現上是以不破壞原對象爲基礎增長備忘錄操做類,記錄原對象的行爲從而實現備忘錄模式。數據結構

這個設計在咱們日常的生活或者開發中也是比較常見的,好比:後悔藥、孟婆湯(一下回滾到0),IDEA編輯和撤銷、小霸王遊戲機存檔。固然還有咱們很是常見的Photoshop,以下;app

Photoshop 歷史記錄

4、案例場景模擬

場景模擬;系統發佈上線配置回滾

在本案例中咱們模擬系統在發佈上線的過程當中記錄線上配置文件用於緊急回滾單元測試

在大型互聯網公司系統的發佈上線必定是易用、安全、可處理緊急情況的,同時爲了能夠隔離線上和本地環境,通常會把配置文件抽取出來放到線上,避免有人誤操做致使本地的配置內容發佈出去。同時線上的配置文件也會在每次變動的時候進行記錄,包括;版本號、時間、MD五、內容信息和操做人。

在後續上線時若是發現緊急問題,系統就會須要回滾操做,若是執行回滾那麼也能夠設置配置文件是否回滾。由於每個版本的系統可能會隨着帶着一些配置文件的信息,這個時候就能夠很方便的讓系統與配置文件一塊兒回滾操做。

咱們接下來就使用備忘錄模式,模擬如何記錄配置文件信息。實際的使用過程當中還會將信息存放到庫中進行保存,這裏暫時只是使用內存記錄。

5、備忘錄模式記錄配置文件版本信息

備忘錄的設計模式實現方式,重點在於不更改原有類的基礎上,增長備忘錄類存放記錄。可能平時雖然不必定非得按照這個設計模式的代碼結構來實現本身的需求,可是對於功能上可能也完成過相似的功能,記錄系統的信息。

除了如今的這個案例外,還能夠是運營人員在後臺erp建立活動對信息的記錄,方便運營人員能夠上下修改本身的版本,而不至於由於誤操做而丟失信息。

1. 工程結構

itstack-demo-design-17-00
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── Admin.java
    │           ├── ConfigFile.java
    │           ├── ConfigMemento.java
    │           └── ConfigOriginator.java
    └── test
        └── java
            └── org.itstack.demo.design.test
                └── ApiTest.java

備忘錄模式模型結構

備忘錄模式模型結構

  • 以上是工程結構的一個類圖,其實相對來講並不複雜,除了原有的配置類(ConfigFile)之外,只新增長了三個類。
  • ConfigMemento:備忘錄類,至關因而對原有配置類的擴展
  • ConfigOriginator:記錄者類,獲取和返回備忘錄類對象信息
  • Admin:管理員類,用於操做記錄備忘信息,好比你一些列的順序執行了什麼或者某個版本下的內容信息

2. 代碼實現

2.1 配置信息類

public class ConfigFile {

    private String versionNo; // 版本號
    private String content;   // 內容
    private Date dateTime;    // 時間
    private String operator;  // 操做人
    
    // ...get/set
}
  • 配置類能夠是任何形式的,這裏只是簡單的描述了一個基本的配置內容信息。

2.2 備忘錄類

public class ConfigMemento {

    private ConfigFile configFile;

    public ConfigMemento(ConfigFile configFile) {
        this.configFile = configFile;
    }

    public ConfigFile getConfigFile() {
        return configFile;
    }

    public void setConfigFile(ConfigFile configFile) {
        this.configFile = configFile;
    }
    
}
  • 備忘錄是對原有配置類的擴展,能夠設置和獲取配置信息。

2.3 記錄者類

public class ConfigOriginator {

    private ConfigFile configFile;

    public ConfigFile getConfigFile() {
        return configFile;
    }

    public void setConfigFile(ConfigFile configFile) {
        this.configFile = configFile;
    }

    public ConfigMemento saveMemento(){
        return new ConfigMemento(configFile);
    }

    public void getMemento(ConfigMemento memento){
        this.configFile = memento.getConfigFile();
    }

}
  • 記錄者類除了對ConfigFile配置類增長了獲取和設置方法外,還增長了保存saveMemento()、獲取getMemento(ConfigMemento memento)
  • saveMemento:保存備忘錄的時候會建立一個備忘錄信息,並返回回去,交給管理者處理。
  • getMemento:獲取的以後並非直接返回,而是把備忘錄的信息交給如今的配置文件this.configFile,這部分須要注意。

2.4 管理員類

public class Admin {

    private int cursorIdx = 0;
    private List<ConfigMemento> mementoList = new ArrayList<ConfigMemento>();
    private Map<String, ConfigMemento> mementoMap = new ConcurrentHashMap<String, ConfigMemento>();

    public void append(ConfigMemento memento) {
        mementoList.add(memento);
        mementoMap.put(memento.getConfigFile().getVersionNo(), memento);
        cursorIdx++;
    }

    public ConfigMemento undo() {
        if (--cursorIdx <= 0) return mementoList.get(0);
        return mementoList.get(cursorIdx);
    }

    public ConfigMemento redo() {
        if (++cursorIdx > mementoList.size()) return mementoList.get(mementoList.size() - 1);
        return mementoList.get(cursorIdx);
    }

    public ConfigMemento get(String versionNo){
        return mementoMap.get(versionNo);
    }

}
  • 在這個類中主要實現的核心功能就是記錄配置文件信息,也就是備忘錄的效果,以後提供能夠回滾和獲取的方法,拿到備忘錄的具體內容。
  • 同時這裏設置了兩個數據結構來存放備忘錄,實際使用中能夠按需設置。List<ConfigMemento>Map<String, ConfigMemento>
  • 最後是提供的備忘錄操做方法;存放(append)、回滾(undo)、返回(redo)、定向獲取(get),這樣四個操做方法。

3. 測試驗證

3.1 編寫測試類

@Test
public void test() {
    Admin admin = new Admin();
    ConfigOriginator configOriginator = new ConfigOriginator();
    configOriginator.setConfigFile(new ConfigFile("1000001", "配置內容A=哈哈", new Date(), "小傅哥"));
    admin.append(configOriginator.saveMemento()); // 保存配置
    configOriginator.setConfigFile(new ConfigFile("1000002", "配置內容A=嘻嘻", new Date(), "小傅哥"));
    admin.append(configOriginator.saveMemento()); // 保存配置
    configOriginator.setConfigFile(new ConfigFile("1000003", "配置內容A=麼麼", new Date(), "小傅哥"));
    admin.append(configOriginator.saveMemento()); // 保存配置
    configOriginator.setConfigFile(new ConfigFile("1000004", "配置內容A=嘿嘿", new Date(), "小傅哥"));
    admin.append(configOriginator.saveMemento()); // 保存配置  

    // 歷史配置(回滾)
    configOriginator.getMemento(admin.undo());
    logger.info("歷史配置(回滾)undo:{}", JSON.toJSONString(configOriginator.getConfigFile()));  

    // 歷史配置(回滾)
    configOriginator.getMemento(admin.undo());
    logger.info("歷史配置(回滾)undo:{}", JSON.toJSONString(configOriginator.getConfigFile()));  

    // 歷史配置(前進)
    configOriginator.getMemento(admin.redo());
    logger.info("歷史配置(前進)redo:{}", JSON.toJSONString(configOriginator.getConfigFile()));   

    // 歷史配置(獲取)
    configOriginator.getMemento(admin.get("1000002"));
    logger.info("歷史配置(獲取)get:{}", JSON.toJSONString(configOriginator.getConfigFile()));
}
  • 這個設計模式的學習有一部分重點是體如今了單元測試類上,這裏包括了四次的信息存儲和備忘錄歷史配置操做。
  • 經過上面添加了四次配置後,下面分別進行操做是;回滾1次再回滾1次以後向前進1次最後是獲取指定的版本配置。具體的效果能夠參考測試結果。

3.2 測試結果

23:12:09.512 [main] INFO  org.itstack.demo.design.test.ApiTest - 歷史配置(回滾)undo:{"content":"配置內容A=嘿嘿","dateTime":159209829432,"operator":"小傅哥","versionNo":"1000004"}
23:12:09.514 [main] INFO  org.itstack.demo.design.test.ApiTest - 歷史配置(回滾)undo:{"content":"配置內容A=麼麼","dateTime":159209829432,"operator":"小傅哥","versionNo":"1000003"}
23:12:09.514 [main] INFO  org.itstack.demo.design.test.ApiTest - 歷史配置(前進)redo:{"content":"配置內容A=嘿嘿","dateTime":159209829432,"operator":"小傅哥","versionNo":"1000004"}
23:12:09.514 [main] INFO  org.itstack.demo.design.test.ApiTest - 歷史配置(獲取)get:{"content":"配置內容A=嘻嘻","dateTime":159320989432,"operator":"小傅哥","versionNo":"1000002"}

Process finished with exit code 0
  • 從測試效果上能夠看到,歷史配置按照咱們的指令進行了回滾和前進,以及最終經過指定的版本進行獲取,符合預期結果。

6、總結

  • 此種設計模式的方式能夠知足在不破壞原有屬性類的基礎上,擴充了備忘錄的功能。雖然和咱們平時使用的思路是同樣的,但在具體實現上還能夠細細品味,這樣的方式在一些源碼中也有所體現。
  • 在以上的實現中咱們是將配置模擬存放到內存中,若是關機了會致使配置信息丟失,由於在一些真實的場景裏仍是須要存放到數據庫中。那麼此種存放到內存中進行回覆的場景也不是沒有,好比;Photoshop、運營人員操做ERP配置活動,那麼也就是即時性的通常不須要存放到庫中進行恢復。另外若是是使用內存方式存放備忘錄,須要考慮存儲問題,避免形成內存大量消耗。
  • 設計模式的學習都是爲了更好的寫出可擴展、可管理、易維護的代碼,而這個學習的過程須要本身不斷的嘗試實際操做,理論的知識與實際結合還有很長一段距離。切記多多上手!

7、推薦閱讀

相關文章
相關標籤/搜索