Memento

January 9, 2022

你設計了一個圖片處理軟體,目前沒有 undo 的功能,因此圖片處理到一定進度,你就會存個備份檔 backup_xx,xx 識別順序用的,後續萬一畫爛了,就載入備份檔,這就是 Gof 說的 Memento 模式了!全劇終…XD

來個程式碼?

好吧!身為開發者,總是要把 undo 實現吧!很久很久前,我曾經用 Java 2D 寫了個 EasyJShop-Toy,也可以擷取螢幕畫面,做些簡單的圖片處理,其中就有 undo 的功能。

圖片處理的部份,主要是寫在 CanvasComponent 裡,如果對編輯中的圖片有變更的動作,例如清除選取區塊時,會先取得變更前的影像,加入至 mementoManager

public void setUpUndo(Image image) {
    mementoManager.addImage(image);
}

public void cleanSelectedArea() {
    setUpUndo(getImage());
    Graphics g = getImage().getGraphics();
    g.setColor(getMainFrame().getColorBoxBackground());
    g.fillRect((int) rect.getX(), (int) rect.getY(),
            (int) rect.getWidth(), (int) rect.getHeight());
}

mementoManagerImageMementoManager 實例,用來管理 Image,也就是 CanvasComponent 的狀態,因為這只是個簡單的圖片編輯軟體,直接把整個 Image 備份下來就可以了,若是更複雜的圖片編輯,例如還有圖層之類的話,那麼狀態可能就需要有個 State 之類的物件來封裝。

如果使用者在選單按下 undo 的按鈕,按鈕的事件處理器會呼叫 CanvasComponentundo,其中透過 mementoManager 取得 Image,設定給 CanvasComponent

public void undo() {
    setImage(undoImage());
}

public Image undoImage() {
    return mementoManager.undoImage();
}

簡單來說,你會從 CanvasComponent 取得 Image,使用 ImageMementoManager 來管理,需要 undo 時,從 ImageMementoManager 取回 Image,用來還原 CanvasComponent 的圖片。

模式就是相似性

當年 Gof 的書太紅了,讓許多初學者以為,模式就是有一堆角色,得使用圖來表現角色間的關係,要有程式碼來實現,不是這樣的,模式就是一群人,在解決某個問題時,出現的相似性,有時會是結構上的相似性,有時會是行為上的相似性,有時會是其他的相似性。

上面一開始談到的,就是在沒有 undo 功能時,可能出現的行為相似性,這就是模式!

如果你要逐一套上 Gof 中 Memento 模式中的角色名稱才安心的話,那麼圖片檔案就是 Memento,圖片處理軟體是 Originator,因為它是 Memento 的來源,圖片處理軟體中負責儲存/載入圖片功能的角色就是 Caretaker,管理 Memento 的是硬碟。

如果要有程式碼,並逐一套上 Gof 中 Memento 模式中的角色名稱才安心的話,那麼 EasyJShop-ToyImage 就是 Memento,CanvasComponent 是 Originator,會呼叫 undo 方法的客戶端(例如 undo 按鈕或 undo 快速鍵的事件處理器)是 Caretaker,管理 Image 的是 ImageMementoManager

只不過我覺得,你還是忘了 Caretaker、Memento、Originator 比較好,因為 Memento 的概念很簡單,簡單到不需要用這些名詞來溝通了,因為生活中的經驗,就足以彌補溝通時需要的情境資訊了。

反而是你硬要用這些名詞,然後還要想著哪個角色對應哪個名詞,才是對溝通造成妨礙吧!