這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
在上一篇部落格設計模式-單例模式(Go語言描述)中我們介紹了在golang中如何去實現單例模式,在文章的最後我們也介紹到了golang專屬的一種實現單例的方式-sync.Once.Do(),可以讓golang輕鬆的實現可以應對並發請求的單利.今天我們繼續探索設計模式,來介紹一下命令模式的實現.
說起命令,大家第一反應可能就是我們平時敲的各種命令,啪啪啪幾行命令下去就可以完成一些功能,在看到命令模式這個詞後,可能大家也會和我一樣認為這裡的命令就是執行一些簡單任務的功能,然而並不是,這裡的命令更多的像是我們發出的請求或者電視遙控器的按鍵.這樣吧,咱們先來看看命令模式的定義,然後再從生活中找實際的例子對比一下.
命令模式是將一個請求封裝為一個對象,從而使我們可用不同的請求對客戶進行參數化;對請求排隊或者記錄請求日誌,以及支援可撤銷的操作.命令模式是一種對象行為型模式,其別名為動作模式或事務模式.
在命令模式中有如下幾個角色:
Command: 命令
Invoker: 調用者
Receiver: 接受者
Client: 用戶端
他們的關係可以這麼來描述:
用戶端通過調用者發送命令,命令調用接收者執行相應操作.
其實命令模式也很簡單,不過不知道大家發現沒有,在上述描述中調用者和接收者並不知道對方的存在,也就是說他們之間是解耦合的.
還是用遙控器的例子來解釋一下吧,遙控器對應上面的角色就是調用者,電視就是接收者,命令呢?對應的就是遙控器上的按鍵,最後用戶端就是我們人啦,當我們想開啟電視的時候,就會通過遙控器(調用者)的電源開關(命令)來開啟電視(接收者),在這個過程中遙控器是不知道電視的,但是電源開關是知道他要控制誰的什麼操作.
… 哎呀, 語文不太好,突然發現越描述越不容易讓人理解了呢? 還是用代碼來實現一下上面遙控器的例子吧.我們對照著上面的角色一個個的去實現.
接收者,也就是一台大大的電視,
// receivertype TV struct{}func (p TV) Open() { fmt.Println("play...")}func (p TV) Close() { fmt.Println("stop...")}
這台電視弱爆了,只有開啟和關閉兩個功能,對應的就是上面代碼中的Open和Close兩個方法,雖然很簡單,不過我們確實造出了一台電視(估計還是彩色的).
下面,我們再來實現命令,也就是遙控器上的按鍵,因為電視只有開啟和關閉功能,所以按鍵我們也只提供兩個,多了用不上.
// commandtype 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,當我們按下按鍵時會去調用這個方法,然後我們果然只實現了兩個按鍵,分別是OpenCommand和CloseCommand,這兩個實現中都儲存著一台電視的控制代碼,並且在Press方法中根據功能去調用了這個tv的相應方法來完成正確的操作.
還有什麼我們沒有實現? 調用者,也就是遙控器了,來看看遙控器怎麼實現吧.
// sendertype 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的可能性,快來想一想你的代碼中哪些地方可以用命令模式來解決吧.