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 操作:

我們通過代碼來實現上述過程:

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