設計模式 in Go: Memento

行爲模式旨在解決對象間通信和交互相關的問題,專注於定義那些複雜到無法靜態設計的協作策略,這些協作策略使得程序可以在運行時動態地進行職責派遣以實現更好的擴展。

今天我們開始第 3 個行爲模式的學習 —— Memento(備忘錄模式)。

問題背景:

在我們需要記錄和恢復對象的內部狀態,而又不希望破壞封裝原則時,可以使用備忘錄模式。有時,我們需要在特定時間點保存對象的狀態,並能夠在以後恢復該狀態。然而,直接暴露內部狀態可能導致封裝問題。備忘錄模式提供瞭解決這個問題的方法。

解決方案:

備忘錄模式,涉及 3 個核心組件:

備忘錄模式允許我們保存和還原對象的狀態,同時避免了對 OOP 封裝的破壞,展開說就是通過上述 3 個核心組件,通過 Originator 創建的 Memento 來記錄內部狀態細節,而且 Originator 能理解狀態數據並還原,以及 Caretaker 控制何時可以保存、還原狀態。

下面通過繪製工具的 undo、redo 操作,來演示下備忘錄模式。

Memento Pattern

示例代碼:

// Package memento provides an example of how the Memento pattern can be
// implemented in Go for a drawing tool that supports undo and redo
// functionality:
//
// This demo is really simple. In real world, it could be really complex.
// For example, drawing a line operation may consider:
// - straight line or handwritten?
// - the line color?
// - the line thickness?
// - the line length?
// - the line position? starting point and ending point.
// And a real drawing tool may support lines, circles, squares, etc.
//
// So the `originator.state` and `memento.state` may be complex and different.
// And, then the `caretaker“ will have many originators, which may create
// different mementos. Each kind of memento records the state for different
// kinds of shape.
//
// Maybe a real editor should do like this.
package memento

import"fmt"

// Memento represents the saved state of the drawing tool
type Mementostruct {
 statestring
}

// Originator represents the drawing tool
type Originatorstruct {
 statestring
}

func(o *Originator) SetState(statestring) {
 fmt.Println(state)
 o.state = state
}

func(o *Originator) GetState()string {
return o.state
}

func(o *Originator) CreateMemento() *Memento {
return &Memento{state: o.state}
}

func(o *Originator) RestoreMemento(m *Memento) {
 o.state = m.state
}

// Caretaker manages the Memento objects and provides undo and redo functionality
type Caretakerstruct {
 mementos []*Memento
 currentint
}

func(c *Caretaker) AddMemento(m *Memento) {
 c.mementos =append(c.mementos, m)
 c.current =len(c.mementos) -1
}

func(c *Caretaker) Undo(originator *Originator) {
if c.current >0 {
 c.current--
 originator.RestoreMemento(c.mementos[c.current])
 fmt.Println("Undo: ", originator.GetState())
 }else {
 fmt.Println("Cannot undo further.")
 }
}

func(c *Caretaker) Redo(originator *Originator) {
if c.current <len(c.mementos)-1 {
 c.current++
 originator.RestoreMemento(c.mementos[c.current])
 fmt.Println("Redo: ", originator.GetState())
 }else {
 fmt.Println("Cannot redo further.")
 }
}

下面通過測試用例進行演示:

package memento_test

import (
"memento"
"testing"
)

funcTestMemento(t *testing.T) {
 caretaker := &memento.Caretaker{}

 originator := &memento.Originator{}
 originator.SetState("Drawing 1")
 caretaker.AddMemento(originator.CreateMemento())

 originator.SetState("Drawing 2")
 caretaker.AddMemento(originator.CreateMemento())

 originator.SetState("Drawing 3")
 caretaker.AddMemento(originator.CreateMemento())

// Undo and redo operations
 caretaker.Undo(originator)// Undo: Drawing 2
 caretaker.Undo(originator)// Undo: Drawing 1
 caretaker.Redo(originator)// Redo: Drawing 2
 caretaker.Redo(originator)// Redo: Drawing 3
 caretaker.Redo(originator)// Cannot redo further.
 caretaker.Undo(originator)// Undo: Drawing 2
}

ps:此處我們優先考慮可讀性,不會太關注編碼標準,如註釋、camelCase 類型名等。我們將多個文件的代碼組織到一個 codeblock 中僅僅是爲了方便閱讀,如果您想測試可以通過 git 下載源碼 github.com/hitzhangjie/go-patterns。

備忘錄模式變體:

備忘錄模式可以擴展爲多種形式,例如多個備忘錄、寬備忘錄或使用具有額外功能的管理者。這些變體根據具體需求提供了管理並恢復對象狀態的靈活性。

與其他模式關係:

備忘錄模式可以與以下幾種設計模式結合使用:

本文總結:

備忘錄模式,提供了一種在不違反封裝原則的情況下捕獲和恢復對象內部狀態的方法。它涉及發起者(Originator)、備忘錄(Memento)和管理者(Caretaker)三個組件。管理者負責管理備忘錄對象,並與發起者交互以保存和恢復狀態。該模式可以通過變體進行擴展,並且可以與其他模式如命令(Command)、原型(Prototype)和狀態(State)模式相配合以解決更復雜的問題場景。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/os1CFk6kB5rUHnWHMLpqcA