Java Tip: 實現Command模式
概述
有時需要向對象發送請求,但是不知道 "被請求的操作" 或 "請求的接受者" 的任何信息。在面向過程的程序設計語言中,這類通信是通過回調函數來完成的:在某個地方登記這個函數,然后在后面調用它。在面向對象程序中,command(命令)與回調函數等價,它封裝了回調函數。本文演示如何在Java中實現Command模式。
---------------------------------------------------------------------------
設計模式不但可以加速面向對象工程的設計進度,而且可以提高開發小組的產出以及軟件的質量。Commad模式是一種對象行為模式,它可以對發送者(sender)和接收者(receiver)完全解耦(decoupling)。("發送者" 是請求操作的對象,"接收者" 是接收請求并執行某操作的對象。有了 "解耦",發送者對接收者的接口一無所知。)這里,"請求"(request)這個術語指的是要被執行的命令。Command模式還讓我們可以對 "何時" 以及 "如何" 完成請求進行改變。因此,Command模式為我們提供了靈活性和可擴展性。
在象C這樣的程序設計語言中,函數指針常被用來消除龐大的switch語句。(參見 "Java Tip 30: Polymorphism and Java" 獲得更詳細的介紹)Java沒有函數指針,所以我們可以用Command模式來實現回調函數。在下面的第一個代碼示例TestCommand.java中,你將實際看到它是如何實現的。
一些開發者在其它語言中已經習慣于使用函數指針,因而他們往往禁不起誘惑,想以同樣的方法使用Reflection api的Method對象。例如,在 "Java Reflection" 一文中,Paul Tremblett介紹了如何使用Reflection而不是switch語句來實現事務處理(Transaction) (參見 "相關資源" 獲得Tremblett的文章和Sun的Reflection教程網址的鏈接)。我是不會為這樣的誘惑所動的。正如Sun所指出的:在有其它更貼近于Java程序設計語言的工具滿足使用要求的情況下,一般不提倡使用Reflect API。不使用Method對象,程序會更易于調試和維護。所以,你應該定義一個接口,并在類中實現它,以執行所需操作。
因此我建議,可以使用Command模式并結合Java的動態加載和綁定機制來實現函數指針。(關于Java的動態加載和綁定機制的詳細介紹,參見Gosling和Henry McGilton的"The Java Language Environment -- A White Paper",列于"相關資源"。)
遵循上面的建議,我們可以運用Command模式,利用它所提供的多態性來消除龐大的switch語句,從而設計出可擴展的系統。我們還可以利用Java獨有的動態加載和綁定機制來構筑動態的、并且可以動態擴展的系統。這一點在下面第二個代碼示例TestTransactionCommand.java中進行說明。
Command模式使請求本身成為一個對象。這個對象和其它對象一樣可以被存儲和四處傳遞。這種模式的關鍵在于一個Command接口:它聲明了一個接口,用于執行操作。最簡單的形式下,這個接口包含一個抽象的execute操作。每個具體的Command類把接收者作為一個實例變量進行存儲,從而指定了一對 "接收者" 和 "行為"。它為execute()方法提供不同的實現以進行請求調用。接收者知道如何執行請求。
如下的圖1表示了Switch--一個Command對象的集合體(aggregation)。它的接口中有flipUp()和flipDown()兩種操作。Switch被稱為 "調用者"(invoker),因為它調用command接口中的execute操作。
具體的command,如LightOnCommand,實現command接口的execute操作。它知道去調用合適的接收者對象的操作。這種情況下它充當了一個適配器(adapter)。通過 "適配器"這一術語, 我想說明:具體的Command對象是一個簡單的連接器,它連接具有不同接口的 "調用者" 和 "接收者"。
客戶實例化調用者,接收者以及具體的command對象。
圖2為時序圖,它表示對象間的相互作用。它說明了Command如何對調用者和接收者(以及它執行的請求)解耦。客戶用合適的接收者作為構造函數的參數來創建具體的command。然后,它將Command保存在調用者中。調用者回調具體的command,后者知道如何完成想要的Action()操作。
客戶(代碼中的主程序)創建具體的Command對象并設置它的接收者。作為一個調用者對象,Switch保存具體的Command對象。調用者通過對Command對象調用execute來發送請求。具體的Command對象對它的接收者進行操作調用,從而完成操作請求。
這里最關鍵的思想在于,具體的command用調用者來注冊自身,調用者進行回調,在接收者身上執行命令。
Command模式示例代碼
讓我們來看一個簡單的例子,它通過Command模式實現回調機制。
以Fan(風扇)和Light(燈)為例。我們的目標是設計一個Switch,它可以對任一個對象進行 "開" 和 "關" 的操作。Fan和Light具有不同的接口,這意味著Switch必須獨立于接收者接口,或者說,它不清楚接收者的接口。為了解決這一問題,每個Switch都需要合適的command作為參數。很明顯,連接到Light的Switch和連接到Fan的Switch具有不同的command。因此,Command類必須是抽象的,或者是個接口。
Switch的構造函數被調用時,它以一組合適的command作為參數。command作為Switch的私有變量保存下來。
調用flipUp()和flipDown()操作時,它們只是簡單地讓合適的command進行execute()操作。Switch對調用execute()后將發生些什么一無所知。
TestCommand.java
class Fan {
public void startRotate() {
System.out.println("Fan is rotating");
}
public void stopRotate() {
System.out.println("Fan is not rotating");
}
}
class Light {
public void turnOn( ) {
System.out.println("Light is on ");
}
public void turnOff( ) {
System.out.println("Light is off");
}
}
class Switch {
private Command UpCommand, DownCommand;
public Switch( Command Up, Command Down) {
UpCommand = Up; // concrete Command registers itself with the invoker
DownCommand = Down;
}
void flipUp( ) { // invoker calls back concrete Command, which executes the Command on the receiver
UpCommand . execute ( ) ;
}
void flipDown( ) {
DownCommand . execute ( );
}
}
class LightOnCommand implements Command {
private Light myLight;
public LightOnCommand ( Light L) {
myLight = L;
}
public void execute( ) {
myLight . turnOn( );
}
}
class LightOffCommand implements Command {
private Light myLight;
public LightOffCommand ( Light L) {
myLight = L;
}
public void execute( ) {
myLight . turnOff( );
}
}
class FanOnCommand implements Command {
private Fan myFan;
public FanOnCommand ( Fan F) {
myFan = F;
}
public void execute( ) {
myFan . startRotate( );
}
}
class FanOffCommand implements Command {
private Fan myFan;
public FanOffCommand ( Fan F) {
myFan = F;
}
public void execute( ) {
myFan . stopRotate( );
}
}
public class TestCommand {
public static void main(String[] args) {
Light testLight = new Light( );
LightOnCommand testLOC = new LightOnCommand(testLight);
LightOffCommand testLFC = new LightOffCommand(testLight);
Switch testSwitch = new Switch(testLOC,testLFC);
testSwitch.flipUp( );
testSwitch.flipDown( );
Fan testFan = new Fan( );
FanOnCommand foc = new FanOnCommand(testFan);
FanOffCommand ffc = new FanOffCommand(testFan);
Switch ts = new Switch( foc,ffc);
ts.flipUp( );
ts.flipDown( );
}
}
Command.java
public interface Command {
public abstract void execute ( );
}
在上面的示例代碼中,Command模式將 "調用操作的對象" (Switch)和 "知道如何執行操作的對象" (Light和Fan)完全分離開來。這帶來了很大的靈活性:發送請求的對象只需要知道如何發送;它不必知道如何完成請求。
Command模式實現Transaction
Command模式也被稱為action(動作)模式或transaction(事務)模式。假設有一個服務器,它接收并處理客戶通過TCP/IP socket連接發送的transaction。這些transaction包含一個命令,后跟零個或多個 參數。
一些設計者可能會使用switch語句,每個command對應一個case。在一個面向對象工程的設計中,代碼中如果使用switch語句,往往表示這是一個糟糕的設計。Command模式展現的是支持transaction的面向對象的方法,它可以用于解決這類設計問題。
在TestTransactionCommand.java程序的客戶代碼中,所有請求都被封裝在通用的TransactionCommand對象中。TransactionCommand對象由客戶創建并用CommandManager進行登記。等待的請求可以通過調用runCommands()在不同時期被執行,這帶來了很大的靈活性。而且我們還可以將多個command組裝成一個復合command。示例代碼中還有CommandArgument,CommandReceiver,CommandManager這些類,以及TransactionCommand的子類--AddCommand和SubtractCommand。下面是對這些類的介紹。
· CommandArgument是一個helper類,它保存命令的參數。如果是大量(或可變數量)的任何類型的參數,它可以被重寫,以簡化參數的傳遞工作。
· CommandReceiver實現所有的命令處理方法(command-processing method),它用Singleton模式來實現。
· CommandManager是調用者,和前面例子中的Switch相當。它在其私有myCommand變量中保存通用TransactionCommand對象。runCommands()被調用時,它調用合適的TransactionCommand對象的execute()。
Java中,可以根據一個包含類名的字符串查找類的定義。在TransactionCommand類的execute ()操作中,我先計算出類名,然后將它鏈接到運行系統中--也就是說,類是根據需要被即時載入的。這里所采用的命名方式是,在命令名后連接一個 "Command" 字符串作為transaction command子類名,這樣它就可以被動態載入。
注意,newInstance()返回的Class對象必須被轉換為合適的類型。這意味著新的類要么必須實現一個接口,要么繼承一個在編譯期就為程序所知道的現有的類。本例中我們是實現Command接口,所以不存在問題。
file://TestTransactionCommand.java
import java.util.*;
final class CommandReceiver {
private int[] c;
private CommandArgument a;
private CommandReceiver(){
c = new int[2];
}
private static CommandReceiver cr = new CommandReceiver();
public static CommandReceiver getHandle() {
return cr;
}
public void setCommandArgument(CommandArgument a) {
this.a = a;
}
public void methAdd() {
c = a.getArguments();
System.out.println("The result is " + (c[0]+c[1]));
}
public void methSubtract() {
c = a.getArguments();
System.out.println("The result is " + (c[0]-c[1]));
}
}
class CommandManager {
private Command myCommand;
public CommandManager(Command myCommand) {
this.myCommand = myCommand ;
}
public void runCommands( ) {
myCommand.execute();
}
}
class TransactionCommand implements Command {
private CommandReceiver commandreceiver;
private Vector commandnamelist,commandargumentlist;
private String commandname;
private CommandArgument commandargument;
private Command command;
public TransactionCommand () {
this(null,null);
}
public TransactionCommand ( Vector commandnamelist, Vector
commandargumentlist){
this.commandnamelist = commandnamelist;
this.commandargumentlist = commandargumentlist;
commandreceiver = CommandReceiver.getHandle();
}
public void execute( ) {
for (int i = 0; i < commandnamelist.size(); i++) {
commandname = (String)(commandnamelist.get(i));
commandargument = (CommandArgument)((commandargumentlist.get(i)));
commandreceiver.setCommandArgument(commandargument);
String classname = commandname + "Command";
try {
Class cls = Class.forName(classname);
command = (Command) cls.newInstance();
}
catch (Throwable e) {
System.err.println(e);
}
command.execute();
}
}
}
class AddCommand extends TransactionCommand {
private CommandReceiver cr;
public AddCommand () {
cr = CommandReceiver.getHandle();
}
public void execute( ) {
cr.methAdd();
}
}
class SubtractCommand extends TransactionCommand {
private CommandReceiver cr;
public SubtractCommand () {
cr = CommandReceiver.getHandle();
}
public void execute( ) {
cr.methSubtract();
}
}
class CommandArgument {
private int[] args;
CommandArgument() {
args = new int[2];
}
public int[] getArguments() {
return args;
}
public void setArgument(int i1, int i2) {
args[0] = i1; args[1] = i2;
}
}
public class TestTransactionCommand {
private Vector clist,alist;
public TestTransactionCommand() {
clist = new Vector();
alist = new Vector();
}
public void clearBuffer(Vector c, Vector a) {
clist.removeAll(c);
alist.removeAll(a);
}
public Vector getClist() {
return clist;
}
public Vector getAlist() {
return alist;
}
public static void main(String[] args) {
CommandArgument ca,ca2;
TestTransactionCommand t = new TestTransactionCommand();
ca = new CommandArgument();
ca.setArgument(2,8);
Vector myclist = t.getClist();
Vector myalist = t.getAlist();
myclist.addElement("Add"); myalist.addElement(ca);
TransactionCommand tc = new TransactionCommand(myclist,myalist);
CommandManager cm = new CommandManager(tc);
cm.runCommands();
t.clearBuffer(myclist,myalist);
ca2 = new CommandArgument();
ca2.setArgument(5,7);
myclist = t.getClist();
myalist = t.getAlist();
myclist.addElement("Subtract"); myalist.addElement(ca2);
myclist.addElement("Add"); myalist.addElement(ca2);
TransactionCommand tc2 = new TransactionCommand(myclist,myalist);
CommandManager cm2 = new CommandManager(tc2);
cm2.runCommands();
}
}
命令及其參數保存在列表中,并被封裝成通用TransactionCommand對象。通用TransactionCommand用CommandManager來注冊。任何時候,命令可以在CommandManager類中通過調用runCommands()接口來執行。
客戶代碼不依賴于任何具體的TransactionCommand子類,也就是說,我的設計是針對接口而不是實現。這帶來了靈活性:要想增加一個新的命令,只需要定義一個新的TransactionCommand子類,并在CommandReceiver類中提供新的命令處理方法的實現。僅此而已。
結論
Command模式具有以下優點:
1. command將 "進行操作請求" 的對象和 "知道如何執行操作" 的對象分離開來(即,解耦)。
2. command是個很棒的對象。它可以象任何其它對象一樣被使用和繼承。
3. 多個command可以被組裝成一個復合command。
4. 很容易增加新的command,因為不需要修改現有的類。
如果執行過的命令序列被保存在一個歷史列表中,就可以遍歷這個列表來提供undo和redo操作。要想實現這一功能,必須在Command接口中有一個unexecute()操作。這一練習留給讀者自己去完成。
---------------------------------------------------------------------------
相關資源
· Design Patterns by Gamma, Helm, Johnson, Vlissides, Addison-Wesley, 1994, ISBN 0-201-63361-2 http://www.bookbuyer.com/cgi-bin/getTitle.cgi?ISBN=0201633612
· Dr. Dobb's Journal, January 1998: "Java Reflection: Not just for tool developers," by Paul Tremblett http://www.ddj.com/articles/1998/9801/9801c/9801c.htm
· Sun's Reflection page
http://java.sun.com/docs/books/tutorial/reflect/index.html
· "The Java Language Environment -- A White Paper" (May 1996), by James Gosling and Henry McGilton covers details about Java's dynamic loading and binding mechanism
http://java.sun.com/docs/white/langenv/
For more on taking advantage of Java's unique feature of dynamic loading and binding mechanism to build a dynamic and dynamically-extensible system, see
http://java.sun.com/docs/white/langenv/
網頁名稱:JavaTip:實現Command模式(轉)-創新互聯
URL標題:http://vcdvsql.cn/article40/cdieho.html
成都網站建設公司_創新互聯,為您提供外貿建站、響應式網站、網站營銷、移動網站建設、網站收錄、面包屑導航
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯