【一起學系列】之剩下的設計模式們

前言

開發】:老大,你教了我挺多設計模式的,已經全部教完了嗎?

BOSS】:沒呢,還有好幾個設計模式沒說過呢,今天再傳授你三個吧,分別是建造者模式,責任鏈模式,備忘錄模式,如何?

開發】:好啊,我最喜歡學習了!

建造者模式

意圖

將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。

核心代碼

定義建造接口

public interface Builder {

    void buildPartOne();

    void buildPartTwo();

    void buildPartThr();

    /***
     * 一般情況肯定是一個複雜的對象
     */
    String getResult();
}

定義實際建造工人

public class ConcreteBuilder implements Builder {

    private StringBuffer buffer = new StringBuffer();

    @Override
    public void buildPartOne() {
        buffer.append("i am part one\n");
    }

    @Override
    public void buildPartTwo() {
        buffer.append("i am part two\n");
    }

    @Override
    public void buildPartThr() {
        buffer.append("i am part Thr\n");
    }

    @Override
    public String getResult() {
        return buffer.toString();
    }
}

如何創建不同的表示?

定義督公

public class Director {

    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    public void setBuilder(Builder builder) {
        this.builder = builder;
    }

    public void construct() {
        builder.buildPartOne();
        builder.buildPartTwo();
        builder.buildPartThr();
    }
}

模擬調用

public class App {

    /***
     * 建造者模式
     *     建造者模式(Builder Pattern)使用多個簡單的對象一步一步構建成一個複雜的對象,這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式
     *
     * 主要解決
     *     主要解決在軟件系統中,有時候面臨着"一個複雜對象"的創建工作,其通常由各個部分的子對象用一定的算法構成;由於需求的變化,這個複雜對象的各個部分經常面臨着劇烈的變化,但是將它們組合在一起的算法卻相對穩定
     *
     * 何時使用
     *      一些基本部件不會變,而其組合經常變化的時候。
     *
     * 如何解決
     *     將變與不變分離開。
     *
     * 關鍵代碼
     *     建造者:創建和提供實例
     *     建造者接口:依賴接口編程
     *     指導者:管理建造出來的實例的依賴關係
     *     產品:建造者所生產的產品
     * 建造者作爲參數進入指導者構造方法,通過特定普遍的構造順序或算法執行,得到產品
     *
     * 應用實例:
     *     1.去肯德基,漢堡、可樂、薯條、炸雞翅等是不變的,而其組合是經常變化的,生成出所謂的"套餐"
     *     2.StringBuilder
     */
    public static void main(String[] args){
        // 創建建造者接口並指向具體建造者 - 包含最終產品
        Builder concreteBuilder = new ConcreteBuilder();

        // 創建指導者, 把具體建造者即工人作爲參數傳入, 通過統一方法執行相應的構建命令
        Director director = new Director(concreteBuilder);
        director.construct();

        // 從工人即具體建造者獲取產品
        String result = concreteBuilder.getResult();
        System.out.println(result);
    }
}

建造者的延展思考:鏈式調用

鏈式調用讓代碼更優雅~

public class MyBuilder {

    // 省略不必要的代碼

    MyBuilder withName(String name) {
        this.setName(name);
        return this;
    }


    MyBuilder withYear(String year) {
        this.setYear(year);
        return this;
    }


    MyBuilder withSex(String sex) {
        this.setSex(sex);
        return this;
    }
}

UML圖

代碼見下方~

責任鏈模式

意圖

使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係。將這些對象連城一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止

典型應用

Log4J 日誌系統即是使用了責任鏈的思想,通過不同日誌級別的傳遞,按級別處理日誌

簡單實現一個Log等級系統

抽象類

定義日誌等級,設置下一個處理器,抽象出寫入方法

public abstract class AbstractLogger {

    // 責任級別
    public static int INFO = 1;
    public static int DEBUG = 2;
    public static int ERROR = 3;

    // 當前級別
    int level;

    //責任鏈中的下一個元素
    AbstractLogger nextLogger;
    public void setNextLogger(AbstractLogger nextLogger){
        this.nextLogger = nextLogger;
    }

    // 記錄日誌
    public void logMessage(int level, String message){
        if(this.level <= level){
            write(message);
        }

        if(nextLogger != null){
            nextLogger.logMessage(level, message);
        }
    }

    // 抽象方法 -> 重寫具體日誌輸出類型
    abstract protected void write(String message);
}

具體日誌類

public class InfoLoger extends AbstractLogger {

    public InfoLoger(int level){
        this.level = level;
    }

    @Override
    protected void write(String message) {
        System.out.println("InfoLoger Console::Logger: " + message);
    }
}

爲了避免重複,只展示一個類

實際調用

public class App {
    
    public static void main(String[] args){
        AbstractLogger log = getChainOfLoggers();
        log.logMessage(AbstractLogger.INFO, "i am info");
        log.logMessage(AbstractLogger.DEBUG, "i am debug");
        log.logMessage(AbstractLogger.ERROR, "i am error");
    }

    private static AbstractLogger getChainOfLoggers(){
        AbstractLogger error = new ErrorLoger(AbstractLogger.ERROR);
        AbstractLogger debug = new DebugLoger(AbstractLogger.DEBUG);
        AbstractLogger info = new InfoLoger(AbstractLogger.INFO);

        error.setNextLogger(debug);
        debug.setNextLogger(info);
        return error;
    }
}

// 輸出結果:
// InfoLoger Console::Logger: i am info
//
// ------------------------
//
// DebugLoger Console::Logger: i am debug
// InfoLoger Console::Logger: i am debug
//
// ------------------------
//
// ErrorLoger Console::Logger: i am error
// DebugLoger Console::Logger: i am error
// InfoLoger Console::Logger: i am error

總結

多種形式

  • 當前pattern下類似日誌級別形式, 只要等級比A大,那B,C都會處理
  • 如A->B->C 由低到高級別執行,只要執行就返回等
  • 最高級形式: 低級發起請求後, 高級任一處理後,請求反饋即可(涉及到異步相關,線程通信)

優點

  • 降低耦合度。它將請求的發送者和接收者解耦
  • 簡化了對象。使得對象不需要知道鏈的結構
  • 增強給對象指派職責的靈活性,通過改變鏈內的成員或者調動它們的次序,允許動態地新增或者刪除責任
  • 增加新的請求處理類很方便

缺點

  • 不能保證請求一定被接收
  • 系統性能將受到一定影響,而且在進行代碼調試時不太方便,可能會造成循環調用
  • 可能不容易觀察運行時的特徵,有礙於除錯

使用場景

  • 有多個對象可以處理同一個請求,具體哪個對象處理該請求由運行時刻自動確定
  • 在不明確指定接收者的情況下,向多個對象中的一個提交一個請求
  • 可動態指定一組對象處理請求

UML圖

代碼見下方~

備忘錄模式

意圖

在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態,這樣以後可將對象恢復到原先保存的狀態

核心代碼

備忘錄

/**
 * ******************************
 * description:  備忘錄,確定數據結構即可
 * ******************************
 */
public class Memento {
    Map<String, String> data;
}

模擬短信場景

/**
 * ******************************
 * description:  模擬短信場景
 * ******************************
 */
public class MessageData {

    private String time;

    private String message;

    /**
     * 存儲數據
     */
    public Memento saveMemento () {
        Map<String, String> map = Maps.newHashMap();
        map.put("TIME",    time);
        map.put("MESSAGE", message);
        return new Memento(map);
    }

    /**
     * 取出數據
     */
    public void getFromMemento(Memento memento){
        time    = memento.getData().get("TIME");
        message = memento.getData().get("MESSAGE");
    }

    // 省略部分代碼
}

備忘錄存儲容器

public class MementoTaker {

    private List<Memento> mementoList = new ArrayList<>();

    public void add(Memento state){
        mementoList.add(state);
    }

    public Memento get(int index){
        return mementoList.get(index);
    }
}

核心調用代碼

public class App {
    public static void main(String[] args) throws InterruptedException {

        // 創建備忘錄管理者
        MementoTaker mementoTaker = new MementoTaker();

        MessageData messageData = new MessageData();
        messageData.setTime(System.currentTimeMillis() + "");
        messageData.setMessage("This is messgae first.");
        mementoTaker.add(messageData.saveMemento());

        System.out.println("First: -> " + messageData);

        Thread.sleep(2000);

        messageData.setTime(System.currentTimeMillis() + "");
        messageData.setMessage("This is messgae second.");
        mementoTaker.add(messageData.saveMemento());

        System.out.println("Second: -> " + messageData);

        Thread.sleep(2000);

        // 回覆初次狀態
        messageData.getFromMemento(mementoTaker.get(0));

        System.out.println("********************檢測數據是否回到初始狀態******************");
        System.out.println(messageData);
    }
}

模式總結:其實該模式非常簡單,即確定好數據結構在容器中存儲一份,以便後續恢復,或者重新使用等等

總結

主要解決

所謂備忘錄模式就是在不破壞封裝的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態,這樣可以在以後將對象恢復到原先保存的狀態

備忘錄思想的實踐

  • 打遊戲時的存檔
  • Windows 裏的 ctri + z
  • 數據庫的事務管理

UML圖

相關代碼鏈接

GitHub地址

  • 兼顧了《HeadFirst》以及《GOF》兩本經典書籍中的案例
  • 提供了友好的閱讀指導
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章