1. 程式人生 > >設計模式-命令模式(Go語言描述)

設計模式-命令模式(Go語言描述)

在上一篇部落格設計模式-單例模式(Go語言描述)中我們介紹了在golang中如何去實現單例模式,在文章的最後我們也介紹到了golang獨有的一種實現單例的方式-sync.Once.Do(),可以讓golang輕鬆的實現可以應對併發請求的單利.今天我們繼續探索設計模式,來介紹一下命令模式的實現.

說起命令,大家第一反應可能就是我們平時敲的各種命令,啪啪啪幾行命令下去就可以完成一些功能,在看到命令模式這個詞後,可能大家也會和我一樣認為這裡的命令就是執行一些簡單任務的功能,然而並不是,這裡的命令更多的像是我們發出的請求或者電視遙控器的按鍵.這樣吧,咱們先來看看命令模式的定義,然後再從生活中找實際的例子對比一下.

命令模式是將一個請求封裝為一個物件,從而使我們可用不同的請求對客戶進行引數化;對請求排隊或者記錄請求日誌,以及支援可撤銷的操作.命令模式是一種物件行為型模式,其別名為動作模式或事務模式.

在命令模式中有如下幾個角色:
Command: 命令
Invoker: 呼叫者
Receiver: 接受者
Client: 客戶端

他們的關係可以這麼來描述:

客戶端通過呼叫者傳送命令,命令呼叫接收者執行相應操作.

其實命令模式也很簡單,不過不知道大家發現沒有,在上述描述中呼叫者和接收者並不知道對方的存在,也就是說他們之間是解耦合的.

還是用遙控器的例子來解釋一下吧,遙控器對應上面的角色就是呼叫者,電視就是接收者,命令呢?對應的就是遙控器上的按鍵,最後客戶端就是我們人啦,當我們想開啟電視的時候,就會通過遙控器(呼叫者)的電源按鈕(命令)來開啟電視(接收者),在這個過程中遙控器是不知道電視的,但是電源按鈕是知道他要控制誰的什麼操作.

… 哎呀, 語文不太好,突然發現越描述越不容易讓人理解了呢? 還是用程式碼來實現一下上面遙控器的例子吧.我們對照著上面的角色一個個的去實現.
接收者,也就是一臺大大的電視,

// receiver
type TV struct{}

func (p TV) Open() {
    fmt.Println("play...")
}

func (p TV) Close() {
    fmt.Println("stop...")
}

這臺電視弱爆了,只有開啟和關閉兩個功能,對應的就是上面程式碼中的OpenClose兩個方法,雖然很簡單,不過我們確實造出了一臺電視(估計還是彩色的).
下面,我們再來實現命令,也就是遙控器上的按鍵,因為電視只有開啟和關閉功能,所以按鍵我們也只提供兩個,多了用不上.

// command
type Command interface {
    Press()
}

type OpenCommand struct {
    tv TV
}

func (p OpenCommand) Press() {
    p.tv.Open()
}

type CloseCommand struct {
    tv TV
}

func (p CloseCommand) Press() {
    p.tv.Close()
}

首先我們定義了一個命令介面,只有一個方法就是Press,當我們按下按鍵時會去呼叫這個方法,然後我們果然只實現了兩個按鍵,分別是OpenCommandCloseCommand,這兩個實現中都儲存著一臺電視的控制代碼,並且在Press方法中根據功能去呼叫了這個tv的相應方法來完成正確的操作.

還有什麼我們沒有實現? 呼叫者,也就是遙控器了,來看看遙控器怎麼實現吧.

// sender
type Invoker struct {
    cmd Command
}

func (p *Invoker) SetCommand(cmd Command) {
    p.cmd = cmd
}

func (p Invoker) Do() {
    p.cmd.Press()
}

在遙控器中我們有一個Command型別的變數,並且提供了SetCommand方法來設定命令,那我們按下遙控器上的鍵對應哪個方法呢?看看Do方法,我們直接呼叫了命令的Press方法,這個時候客戶端(就是我們人)拿起遙控器,瞅準了開啟按鈕(SetCommand),並且按鈕該鍵(Do),按鍵被按下(Press),電視打開了(Open).

那麼最後,來看看客戶端怎麼去呼叫吧,

func main() {
    var tv TV
    openCommand := OpenCommand{tv}
    invoker := Invoker{openCommand}
    invoker.Do()

    invoker.SetCommand(CloseCommand{tv})
    invoker.Do()
}

命令模式實現起來也很簡單,它降低了我們這個系統的耦合度,並且我們還可以任意拓展命令使用而不需要修改程式碼,開閉原則體現的淋漓盡致.

現在大家應該對命令模式有了一個直觀的認識吧, 不過大家有沒有發現一個問題,我們的遙控器在使用某個命令的時候只有set後才能用,也就是說每次只能使用一個命令,那有沒有更好的辦法來預裝命令呢?肯定是有的,這就是我們接下來要介紹的複合命令,也叫做巨集命令,複合命令其實說白了就是將多個命令儲存起來,通過遍歷這個集合來分別呼叫各個命令.

func NewOpenCloseCommand() *OpenCloseCommand {
    var openClose *OpenCloseCommand = &OpenCloseCommand{}
    openClose.cmds = make([]Command, 2)
    return openClose
}

type OpenCloseCommand struct {
    index int
    cmds  []Command
}

func (p *OpenCloseCommand) AddCommand(cmd Command) {
    p.cmds[p.index] = cmd
    p.index++
}

func (p OpenCloseCommand) Press() {
    for _, item := range p.cmds {
        item.Press()
    }
}

func main() {
  var openClose *OpenCloseCommand = NewOpenCloseCommand()
  openClose.AddCommand(OpenCommand{tv})
  openClose.AddCommand(CloseCommand{tv})
  invoker.SetCommand(openClose)
  invoker.Do()
}

我們定義了一個OpenCloseCommand,這裡面用一個切片來儲存各個命令,並通過AddCommand方法來王裡面新增命令,OpenCloseCommand實現了Press方法,所以本質上他也是一個命令,我們呼叫他的Press方法來遍歷儲存的Command,並呼叫每一個的Press方法.

到這裡,命令模式我們就介紹完了,命令模式具有很高的擴充套件性,遵循了開閉原則,所以減少了修改程式碼引入bug的可能性,快來想一想你的程式碼中哪些地方可以用命令模式來解決吧.