命令模式筆記 - 前篇(Simple Command Pattern)

命令模式筆記 - 前篇(Simple Commmand Design Pattern)

(以下,除了程式碼、情境與範例,設計模式的定義皆是參考自HeadFirst Design Pattern一書。)

 

曾想過你家的搖控器是不是非常的多呢?若能有一個共用的搖控器,讓冷氣、電視、甚至是電燈都能使用同一個開關的話

那不是非常的方便嗎?(唯一的不方便就是,假如搖控器不見了,不就進入了原始人的生活了)

 

這也是很有趣的部分,我家剛好有一組音響,一台是CD-Player,一台是綜合擴大機!他們都有各附上了一個搖控器,

然而因為是同個廠牌出廠的,所以他們的搖控器竟然都長的一樣!按鈕配置也都一致,更棒的是,我只要一隻就可以

操作兩台機器,而且兩台機器的運作各自會處理的好好的,我的搖控器只需要知道怎麼發出命令,叫兩台機器乖乖工作就好了。

更棒的話,將來這個廠牌假如出了一系列的產品,可能包含了5種,那在遙控器上設定五個插槽,每購買一台就附上一個插卡晶片

而遙控器能讓我能自由插上所有我可以購買機器的話。那我不就有一個萬用搖控器了,這種自由度不是更棒嗎?

 

是啊,正是這樣的理念,搖控器與視聽家電之間的關係,看起來也是一個鬆綁的狀態,這正是我們這次要學習的新工具。

「簡單命令模式」。

 

先簡單的瞭解命令模式的運作吧。(圖1)

image

在電子商務如此熱絡普及的今天,電子商務發展仍以平台業者的規模最大,也實行了B2B2C的平台模式。

例如,PCHome商店街,Yahoo拍賣等。賣家或是商店廠商都可以透過在入口網站或是大型流量的購物網站中

使用他們提供的訂單系統與產品展示系統,藉由這樣取得平台業者所擁有的流量與基礎商流、金流建設。

 

這次我們的例子就是以此為題:首先我們要瞭解這三方的角色與責任

我們假想訂單也是一個「物件」,而訂單中包含了購買的商品有哪些,也就是跟廠商說明,我要買「哪一樣產品」

訂單這個物件中定義了一個OrderUp的方法,裡面的方法參考到廠商那邊需要用來進行準備商品(可能包含包裝,運送等作業)工作的物件。

這一切都被封裝起來,而中間的電子商務網站平台不需要知道訂單上 有什麼,也不需要知道是誰來提供商品;

他只要將訂單,從消費者那邊取得以後,將這個物件傳遞到廠商那邊就好了!傳遞的動作就是調用OrderUp()。

以此為例,OrderUp()方法中,參考了PrepareProduct()。因此呼叫訂單的OrderUP()方法也是間接的叫廠商執行準備商品的工作。

 

廠商知道廠品準備的工作與後續步驟,因此一旦網站一呼叫orderUP方法後,廠商就接手實踐需要準備產品與寄送產品的所有方法。

廠商並不需要跟網站作什麼其他的溝通,因為他看了訂單,就知道他該知道的資訊了。

 

以上的電子商務模式,第三方平台業者藉由訂單而和廠商之間鬆綁了。

命令模式這個模型允許將「發出需求的物件」和「接受與執行這些需求的物件」區分開來。先前的搖控器也是一樣,

比如說搖控器都有一個像是訂單的物件,當遙控器的一個按鈕被按下,只要呼叫該物件的orderUP()方法,並將CD-Player打開

而搖控器不需要知道遠端實際發生的細節,也不需要知道牽扯到什麼物件上了。

 

來見識簡單命令模式的運作吧(圖2)

image

從Client開始,這正是我們的消費者操作電子商務平台網站所進行的動作,它會產生一個訂單的物件!(透過CreateCommandObject)

而訂單物件記得嗎?定義了一個Excute方法,也就是剛剛的OrderUp

Excute方法則是參考了訂單行為該進行的所有方法。(以此為例,就是參考了Action1、Action2)

另一個重點是在建立Command物件後,這個物件會藉由Client呼叫Invoker(電子商務網站平台)的setCommand方法,並傳入命令物件。

該命令物件被儲存在其中,以後需要用到。稍後就是由客戶要求Invoker執行命令,Invoker將呼叫execute方法,這導致接收者(廠商)的動作被調用。

(P.S.這個命令一旦被載入Invoker中,可以被重複使用或是丟棄)

 

OK,接下來就是實作命令模式了,我們先設定一個命令介面。

   1: public interface Command
   2: {
   3:     public void execute();
   4: }

接著關鍵之一就是所謂的命令物件,也就是電子商務模式中的訂單物件囉!以我家的CDPlayer(目前定義了on()跟off()的方法)為例:

   1: public class CDPlayerOnCommand : Command
   2: {
   3:     CDPlayer CDP;
   4:     public CDPlayerOnCommand(CDPlayer cdplayer)
   5:     {
   6:         this.CDP = cdplayer;
   7:     }
   8:  
   9:     public void execute()
  10:     {
  11:         CDP.on();
  12:     }
  13: }

那遙控器呢?簡單而言,假如現在遙控器只有一個將CDPlayer打開的開關,以及對應的機器的插槽(讓我設定控制CDPlayer的地方),未來可以再擴充成n個哦!

這邊先實作一個!

   1: public class SimpleRemoteComtrol
   2: {
   3:     Command slot;
   4:     public SimpleRemoteComtrol() { }
   5:  
   6:     public void setCommand(Command command)
   7:     {
   8:         slot = command;
   9:     }
  10:  
  11:     public void buttonWasPressed()
  12:     {
  13:         slot.execute();
  14:     }
  15: }

上述我們看到了Command物件、Invoker調用者是如何設計了,那客戶呢?

接著換來命令模式客戶實作:

   1: static void Main(string[] args)  //我就是Client啦 
   2: {
   3:     SimpleRemoteComtrol remote = new SimpleRemoteComtrol(); //遙控器就是調用者啦
   4:     CDPlayer cdp = new CDPlayer();  //建立接收者,也就是CDPlayer囉
   5:     CDPlayerOnCommand CDPlayerOn = new CDPlayerOnCommand(cdp); //建立一個命令
   6:     remote.setCommand(CDPlayerOn);  //把命令傳給調用者
   7:     remote.buttonWasPressed();      //模擬按下按鈕
   8:  
   9:     Console.ReadKey();
  10: }

輸入就像這樣:

image

上述程式完整的執行也說明了剛剛電子商務模式所引導出來的命令模式的圖2。

 

我們跟著Head First Design Pattern來定義一下命令模式

命令模式:將「請求」封裝成物件,使用不同的請求、佇列、或者日誌,參數化其他物件。命令模式也支援可復原的作業。

一個調用者(Invoker,比方說一個遙控器的插槽),可以用不同的請求當參數。

從外面來看,其他物件並不知道誰是接收者,進行了什麼。只知道如果呼叫 execute方法,請求的目的就能達到!

 

定義一下命令模式的類別圖(半成品)

image

 

後記:

咦?喂喂,前面都懂了!可是定義中還有:「使用不同的請求、佇列、或者日誌,參數化其他物件。命令模式也支援可復原的作業。」

這麼多字,到底是什麼意思??

這次將命令模式分成兩篇,前面先分享命令模式的基礎(簡單命令模式),一旦有了足夠的基礎,後續的擴充就輕易多了!

其實命令模式還沒完,下一篇將進一步改造簡單命令模式,分享所謂的Meta Command Pattern,將加入Undo方法後續會介紹,可以建立命令的巨集,以便一次執行多個指令,也可以複原作業。

可以先讓我自己想想,命令如何改造成集合的模式吧!