Go 代碼應用設計模式: 責任鏈模式、命令模式
今天我們開始講解設計模式中的行爲模式,首先我們來看看責任鏈模式。
責任鏈模式就是把一系列的工作組成鏈路處理。其優勢是對於調用者無需關注一項工作的實現細節,不需要自己去組裝每一步的調用,同時對於如果有鏈路變更,只需要對鏈路的 next 進行修改,而無需對對象實現代碼進行更改。
我們來看下一個實際場景,去醫院看病的過程。我們需要經過掛號、就診、醫生開處方、繳費、取藥這個過程,我們看看責任鏈模式是如何實現的?
type Process interface {
Exec() error
}
// 掛號
type Register struct {
next Process
}
// 就診
type visitDoctor struct {
next Process
}
// 支付
type Pay struct {
next Process
}
// 取藥
type pickMedicine struct {
next Process
}
func (r Register) Exec() error {
fmt.Println("掛號")
if r.next == nil {
return nil
}
return r.next.Exec()
}
func (v visitDoctor) Exec() error {
fmt.Println("就診")
if v.next == nil {
return nil
}
return v.next.Exec()
}
func (p Pay) Exec() error {
fmt.Println("支付")
if p.next == nil {
return nil
}
return p.next.Exec()
}
func (p pickMedicine) Exec() error {
fmt.Println("取藥")
if p.next == nil {
return nil
}
return p.next.Exec()
}
func NewProcess() Process {
return Register{
next: visitDoctor{
next: Pay{
next: pickMedicine{},
},
},
}
}
func main() {
NewProcess().Exec()
}
在上述例子中,用戶只需要關注 Process 的 Exec() 方法,而無需關注其內部調用流程,同時流程發生更改,也不會影響用戶側。
提到流程處理,大家有沒有回憶起 go 語言系列 3 - Channel 應用流水線模型 這一節的內容。個人認爲通過流水線模型實現的正是責任鏈模式,而這種實現纔是更體現了 Go 語言特色。
接下來我們再瞭解下行爲模式的另一種命令模式。
命令模式解耦了觸發事件和真正的執行,最常見的案例是在編輯器的 Undo 和 Redo 操作。其中 UI 層只需要觸發點擊按鈕或者鍵盤響應的事件,而真正的邏輯處理交給邏輯層結合具體的對象來處理。
我們來簡化編輯器的邏輯,只有插入字符或者刪除字符操作。通過以下邏輯實現 Undo 和 Redo 操作:
-
存在一個 undo 隊列和一個 redo 隊列;
-
當插入 / 刪除字符時,undo 隊列增加相應的操作事件,清空 redo 隊列;
-
當執行 undo 操作時,取出 undo 隊列中最後一個事件並執行,同時增加該事件對應的 redo 事件添加到 redo 隊列中;
-
當執行 redo 操作時,取出 redo 隊列中最後一個事件並執行,同時增加該事件對應的 undo 事件添加到 undo 隊列中;
我們通過代碼來實現上述過程:
type Command interface {
undo()
redo()
}
type InsertCommand struct {
content string
pos int
}
func (i InsertCommand) undo() {
fmt.Printf("undo: 在第 %d 位置刪除 %s \n", i.pos, i.content)
}
func (i InsertCommand) redo() {
fmt.Printf("redo: 在第 %d 位置插入 %s \n", i.pos, i.content)
}
func OnInsert(content string, pos int) {
fmt.Printf("在第 %d 插入 %s \n", pos, content)
undos = append(undos, InsertCommand{
content: content,
pos: pos,
})
}
func OnUndo() {
if len(undos) == 0 {
return
}
command := undos[len(undos)-1]
command.undo()
undos = undos[:len(undos)-1]
redos = append(redos, command)
}
func OnRedo() {
if len(redos) == 0 {
return
}
command := redos[len(redos)-1]
command.redo()
redos = redos[:len(redos)-1]
undos = append(undos, command)
}
var (
undos []Command
redos []Command
)
func main() {
OnInsert("abc", 1)
OnInsert("ef", 4)
OnUndo()
OnUndo()
OnRedo()
}
這個簡單過程的例子顯得代碼比較多**,這也是命令模式的一個確定,代碼閱讀起來不是那麼容易**。這只是實現了插入字符串這一個操作的 undo、redo 事件(還並未實現完整邏輯,只是用輸出語句代替),在真正的編輯器中存在刪除字符串、插入 / 刪除圖片等一系列操作,代碼將會比現在複雜得多。
同時通過命令模式實現的 undo、redo 需要隊列來存儲命令,會產生內存佔用,避免內存佔用過多,可限制 undo、redo 的次數(隊列長度來實現)。這個例子中對 undo、redo 的操作並沒有使用鎖來保持同步性,在生產實際中需要注意。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/uv0ZDXcYjk3gPu4MntDsUQ