- 動機:在物件導向開發過程中,我們常常需要兩個或多個物件一起合作,最常見的情況是:一個物件A會命令或要求另外一個物件B去做特定的工作C。舉例來說:我們需要設計一個虛擬遙控器RemoteController,這個RemoteController上面有許多按鈕,不同的按鈕按下去,就會去完成不同的工作:例如Button1會打開客廳的燈;Button2會打開廚房的燈;Button3會開車庫門;Button4會打開電視機。試想在這種情境下,我們會怎麼去設計RemoteController?最直覺的方式是:我們會直接讓RemoteController物件中擁有所有需要操作的物件:客廳燈(Light in living room)、廚房燈(Light in kitchen)、車庫門(Garage Door) 以及電視機(TV),然後針對不同的Button去定義呼叫不同操作物件的特定方法:例如Button1會去呼叫lightInLivingRoom.on()、Button2會去呼叫lightInKitchen.on()、Button3則去呼叫garageDoor.open()、Button4呼叫tv.on()。乍看之下,這樣的設計似乎很合理,功能跑起來也都正常,但是,軟體開發的過程中,唯一不變的定律就是事情永遠在變。我們的RemoteController使用者添購了新的CD Player,他希望RemoteController可以新增一個功能就是去播放CD。於是乎,設計人員必須改寫RemoteController以便讓它能擁有新的成員:CD Player,同時針對播放CD這個功能,去設計相對應的按鈕。事情還沒結束,使用者換了他們家的電視機,從傳統的TV升級到新的LCD,所以,Button4不管用了,因為RemoteController不認識LCD。噩夢接二連三的到來:每當使用者有新的需求,或是更換了家具,設計人員就得從新改寫一次RemoteController。有沒有比較彈性的作法呢?
- 解決方式:站在巨人的肩膀上,可以幫助我們看到更開闊的視野。感謝前人的聰明才智,他們已經針對這樣的狀況想出漂亮的解決方式:Command Pattern。在先前的設計中,我們讓RemoteController跟它欲呼叫的物件(各種家具)完全綁在一起,套句IT的術語:就是所謂的Tightly Couple。這樣的設計,雖然直覺,卻也造成未來的難以維護與難以擴充。所以說,若我們能將RemoteController以及各種家具分開(decouple),事情是不是就比較好處理?這也是Command Pattern希望做出來的架構:complete decoupling between the invoker and receiver。所謂的Invoker就是呼叫特定方法(下命令)的物件(在前例中,就是RemoteController);而Receiver就是接收命令並實際去做事的物件(也就是例子中的傢俱)。而因為Decoupling的緣故,Invoker只需要下命令,而不需要了解Receiver到底長什麼模樣並且如何去完成命令。下面是Command Pattern的Class Diagram與Sequence Diagram:
- Command Pattern定義:在Invoker與Receiver之間,多增加一層Command。Invoker所需的Request(或是Command)均被封裝為Command物件,而Invoker並不直接操作Receiver物件,而是操作Command物件。
- 效果:
- Command Pattern將”引發命令的物件(Invoker)”與”知道如何執行的物件(Receiver)”隔離開來。
- Command可和一般物件一樣使用與擴充。
- 容易新增新的Command類別,而不必去更改現有的類別
- 後續延伸思考之Undo機制的建立:在Command新增undo()→在ConcreteCommand中定義undo()→在Invoker中建立一個member: undoCommand的機制以儲存先前執行的command。→在Invoker中新增undo method,並在其中呼叫undoCommand.undo()。
- 後續延伸思考之MacroCommand:可將個別Command物件組合成複合命令,形成MacroCommand。
- 後續延伸思考之Command Pattern在Struts Framework中的應用:
Struts Framework Role Command Pattern Role Web Container Client RequestProcessor Invoker Action Command Model Receiver - Sample Code:
RemoteController.java
import java.util.*;
public class RemoteController {
private Map commands;
public RemoteController() {
commands = new HashMap();
}
public void addCommand(String commName,
ICommand command) {
commands.put(commName, command);
}
public void request(String commName) {
ICommand command = (ICommand) commands.get(commName);
command.execute();
}
}
ICommand.java
public interface ICommand {
public void execute();
}
OpenGarageDoorCommand.java
public class OpenGarageDoorCommand implements ICommand {
private GarageDoor door;
public OpenGarageDoorCommand (GarageDoor door) {
this. door = door;
}
public void execute() {
door.open();
}
}
CDPlayerOnCommand.java
public class CDPlayerOnCommand implements ICommand {
private CDPlayer player;
public CDPlayerOnCommand(CDPlayer player) {
this.player = player;
}
public void execute() {
player.on();
player.setCd(“ABBA”);
player.setVolume(10);
}
}
Client.java
public class Client {
public static void main(String[] args) {
RemoteController controller = new RemoteController ();
GarageDoor door = new GarageDoor();
CDPlayer player = new CDPlayer();
OpenGarageDoorCommand command1 = new OpenGarageDoorCommand();
CDPlayerOnCommand command2 = new CDPlayerOnCommand();
controller.addCommand("OpenGarageDoor", command1);
controller.addCommand("CDPlayerOn", command2);
//simulate random request
String[] req = {"OpenGarageDoor", "CDPlayerOn"};
while(true) {
int i = (int) (Math.random() * 10) % 2;
controller.request(req[i]);
}
}
}
Thursday, March 30, 2006
Design Pattern Reading - Command Pattern
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment