Design Pattern - Command Pattern

定義:

Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations

粗分為兩部分:

  1. 把request封裝成物件。
  2. 實踐queue、日誌、以及支援復原功能。
    先來介紹的是沒有undo功能的Simple Command Pattern。

Simple Command Pattern

初步了解:
老方法,用Headfirst Design Pattern的範例熟悉一下Command Pattern。
這一章HeadFirst書上並沒有使用UML將範例畫出來,於是我稍作整理,以下是範例的UML:
2014-02-13_095001.jpg
圖中有幾個主要角色:
Invoker:調用者,負責調用client端需要的命令,並執行之。
Command << interface >>:透過這個介面Invoker可以調用各種實作這個介面的Command執行之。
Reciver:接收者,reciver接收到命令後執行相符合的動作。
Client:使用者,發出命令者。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SimpleRemoteControl {
Command slot;

public SimpleRemoteControl() {}

public void setCommand(Command command) {
slot = command;
}

public void buttonWasPressed() {
slot.execute();
}
}

Command:
命令接收者(reciver)做出實際行動,Class名稱就是它要reciver做的事情。例如:

1
2
3
4
5
6
7
8
9
10
11
12
//LightOffCommand.java
public class LightOffCommand implements Command {
Light light;

public LightOffCommand(Light light) {
this.light = light;
}

public void execute() {
light.off();
}
}

可以很清楚的知道,這個命令是要Light這個reciver去做開燈這件事。

從這個Simple Command pattern中可以觀察到幾個特性:

  1. 原本需由Client端直接面對各種Reciver不同的API,現在只要setCommand,然後執行即可。讓Client端的程式碼更具有可讀性。
  2. 要設成Command的行為,必須要有一定的相似性,通常是輸入的變數都是同一類型。如開燈與開電視機他們都不需要輸入參數。
  3. 使用Command的行為盡量不要回傳值,或者回傳值即是需要的結果。簡化Client程式碼。

##Meta Command Pattern
什麼是meta command pattern?
剛剛的simple command pattern,invoker使用setCommand()方法,一次可以設定一個command對吧。
現在我們把command存在陣列中,最後再使用executeAll()方法一次執行所有儲存的命令。
來做個比較便一目了然:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// SimpleCommandInvoker.java
public class SimpleCommandInvoker {

Command command;
public SimpleCommandInvoker() {
}

public void setCommand(Command command){
this.command = command;
}

public void execute(){
command.execute();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// MetaCommandInvoker.java
public class MetaCommandInvoker {

List<Command> commands = new ArrayList<Command>();
//把command儲存在List中,由executeAll一一執行。

public MetaCommandInvoker() {
// TODO Auto-generated constructor stub
}

public void setCommand(Command command){
commands.add(command);
}

public void removeCommand(Command command){
commands.remove(command);
}

public void executeAll(){
for(Command c:commands){
c.execute();
}
}

}

Undo與NoCommand

這部分先行跳過,擇日補上,如果對復原有需要的朋友,請參閱Head First Design Pattern一書。

延伸思考:

  1. 如果我們把Command用Abstract class或是Concrete Class實作一些方法,而非interface會怎麼樣?
  1. Command Pattern or Stragery Pattern
    一個情境,在登入時,能選擇google登入、facebook登入或者使用網站本身資料庫來登入,這種情形下,用Command Pattern比較好還是用Stragery Pattern比較好呢?

  2. Command pattern與Facade Pattern
    同樣都是在實際執行的物件和Client中間加上一個物件來降低藕合,差別在於facade必須為所有的method命名,例如開電視。而Command要使用哪一個才調用並執行,可以看得出Command Pattern適用於大量且不一定會使用的指令,而facade是讓你很明顯的看到,我這個系統有這些功能,扮演著介面的角色。

參考資源:

Head First Design Pattern
搞笑談軟工 重新整理Command Pattern
Command與Stragery Pattern的差異
Rico 技術農場 Command Pattern

評論