設計模式 in Go: Memento
行爲模式旨在解決對象間通信和交互相關的問題,專注於定義那些複雜到無法靜態設計的協作策略,這些協作策略使得程序可以在運行時動態地進行職責派遣以實現更好的擴展。
今天我們開始第 3 個行爲模式的學習 —— Memento(備忘錄模式)。
問題背景:
在我們需要記錄和恢復對象的內部狀態,而又不希望破壞封裝原則時,可以使用備忘錄模式。有時,我們需要在特定時間點保存對象的狀態,並能夠在以後恢復該狀態。然而,直接暴露內部狀態可能導致封裝問題。備忘錄模式提供瞭解決這個問題的方法。
解決方案:
備忘錄模式,涉及 3 個核心組件:
-
Originator(發起者):Originator 對象就是我們重點關注的對象了,它的狀態需要能被保存和恢復。Originator 對象狀態記錄在一個稱之爲 Memento(備忘錄)的對象裏。Caretaker 對象則負責管理 Memento 對象、Originator 對象之間的交互。
-
Memento(備忘錄):Originator 對象會創建一個 Memento 備忘錄對象,用來記錄 Originator 的狀態。Memento 對象只能用來記錄 Originator 的內部狀態,但是不能對這份狀態數據做修改。Memento 提供了方法來獲取 Originator 的當前狀態。Originator 則提供了方法用來設置其狀態,比如從 Memento 獲取到某個時刻的狀態後,使用 Originator 的設置狀態方法來還原狀態。
-
Caretaker(管理者):Caretaker 對象負責管理 Originator 和 Memento 對象之間的交互,比如在時間合適的時候,它調用 Originator 創建的 Memento 來保存 Originator 的當前狀態,完成記錄狀態的目的。它也可以在必要的時候,從 Memento 獲取 Originator 之前的狀態來調用 Originator 的設置狀態方法,完成狀態還原的目的。
備忘錄模式允許我們保存和還原對象的狀態,同時避免了對 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。
備忘錄模式變體:
備忘錄模式可以擴展爲多種形式,例如多個備忘錄、寬備忘錄或使用具有額外功能的管理者。這些變體根據具體需求提供了管理並恢復對象狀態的靈活性。
與其他模式關係:
備忘錄模式可以與以下幾種設計模式結合使用:
-
命令模式(Command Pattern):備忘錄模式可以與命令模式一起使用,以實現撤銷和重做功能。命令模式將操作封裝爲對象,而備忘錄模式可用於保存和恢復這些操作所涉及的對象的狀態。
-
原型模式(Prototype Pattern):備忘錄模式可以與原型模式結合使用,創建對象的深拷貝。備忘錄可以捕獲對象的狀態,而原型模式可以在新對象中複製相同的狀態。
-
狀態模式(State Pattern):備忘錄模式可用於實現狀態模式中對象的內部狀態管理。備忘錄記錄了對象的狀態,而狀態模式根據該狀態定義行爲。
本文總結:
備忘錄模式,提供了一種在不違反封裝原則的情況下捕獲和恢復對象內部狀態的方法。它涉及發起者(Originator)、備忘錄(Memento)和管理者(Caretaker)三個組件。管理者負責管理備忘錄對象,並與發起者交互以保存和恢復狀態。該模式可以通過變體進行擴展,並且可以與其他模式如命令(Command)、原型(Prototype)和狀態(State)模式相配合以解決更復雜的問題場景。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/os1CFk6kB5rUHnWHMLpqcA