第一次寫關於設計模式的隨筆,最近在使用C#做一個WinForm的項目,其中要求需要支持撤銷和恢復功能,想到了以前看過Command模式支持撤銷和恢復操作,就在項目中使用了。對命令模式理解的不夠深,各位看客請指正。
Gof23種設計模式中的Command模式,其意圖是這么描述的“將一個請求封裝為一個對象,從而是你可以用不同的請求對客戶進行參數化;對請求排隊或記錄請求日志,以及支持可撤銷的操作”;另外個人的理解就是可以將調用者和接受者解耦出來。下圖為Command的類圖:
首先理解將調用者和接受者解耦出來:Invoker中只需要持有一個Command接口的引用即可,但是具體是其哪一個子類,Invoker並不知道。而真正執行動作的接受者,Invoker並不用知道,這樣就講調用者和接收者解耦出來。另外理解實現撤銷和恢復操作。程序需要將所有操作過的命令對象存儲起來,存儲的每一個命令對象保存了其當前狀態和上一個狀態,因此可用來做撤銷和恢復操作。下面用代碼進行說明,本文使用C#語言實現的。
首先,我們定義一個Command的接口。定義如下:
interface Command { void execute(); void undo(); }
其中僅僅聲明了兩個函數,其如果在c++的實現的話,使用純虛類。
我們要實現的具體功能為,可以為一個TextBox控件實先撤銷和恢復功能。因此,我們實先一個TextChangedCommand的類,定義如下:
1 public class TextChangedCommand: Command 2 { 3 private TextBox ctrl; 4 private String newStr; 5 private String oldStr; 6 7 public TextChangedCommand(TextBox ctrl, String newStr, String oldStr) 8 { 9 this.ctrl = ctrl; 10 this.newStr = newStr; 11 this.oldStr = oldStr; 12 } 13 14 public void execute() 15 { 16 this.ctrl.Text = newStr; 17 this.ctrl.SelectionStart = this.ctrl.Text.Length; 18 } 19 20 public void undo() 21 { 22 this.ctrl.Text = oldStr; 23 this.ctrl.SelectionStart = this.ctrl.Text.Length; 24 } 25 }
其中有三個成員變量,分別為TextBox類型的ctrl,和String類型的newStr、oldStr。這里的ctrl就可以理解為類圖中的Invoker,而newStr和oldStr即為當前狀態和上一個狀態。
由於我們需要實現其撤銷和恢復功能,因此這里創建兩個棧對象,分別用來存儲已經執行了的命令對象和撤銷后的命令對象。即在撤銷動作中,從棧中彈出一個Command對象,然后執行對象的undo操作,執行完成后將其存儲到恢復的棧中,在點擊恢復時,用從恢復棧彈出一個Command對象,執行其execute動作。兩個棧的定義如下:
Stack<Command> undoStack = new Stack<Command>(); Stack<Command> redoStack = new Stack<Command>();
下面實現類圖中的Client部分,其主要功能是創建一個具體的Command對象,同時為為其設置接收者。對於一個TextBox來說,在其Text熟悉變化的時候,將其上一個狀態和當前狀態保存下來。具體試下如下:
private void textBox1_TextChanged(object sender, EventArgs e) {
if(flag){ TextChangedCommand com = new TextChangedCommand((TextBox)textBox1, ((TextBox)textBox1).Text, oldStr); undoStack.Push(com); oldStr = ((TextBox)textBox1).Text;
} }
這個函數是控件textBox1的Text屬性變化時的回調函數。在其中創建了Command基類的實力,並將其存到棧對象undoStack中。下面來做恢復和撤銷操作的代碼:
撤銷操作:
private void button1_Click(object sender, EventArgs e) //撤銷 { if (undoStack.Count == 0) return;
flag = false;
Command com = undoStack.Pop(); com.undo(); redoStack.Push(com); }
恢復操作:
private void button2_Click(object sender, EventArgs e) //恢復 { if (redoStack.Count == 0) return;
flag = false;
Command com = redoStack.Pop(); com.execute(); undoStack.Push(com); }
我將兩個操作分別放到兩個button的操作里面,一個用於撤銷,另外一個用於恢復。由於我們在進行撤銷和恢復時,在TextChangedCommand中是對TextBox控件進行賦值,因此也會觸發textBox1控件的Text屬性變化的回調函數,我這這里使用了一個標志flag,如果是在撤銷和恢復的動作中,則不做Command對象的壓棧動作。到這里,已經完全可以實現了TextBox的撤銷和恢復功能,當然在我自己的項目中,出了TextBox控件為,還有ComboBox、DateTimePicker、CheckBox控件都實現了撤銷和恢復功能,但是ComboBox控件的回調函數需要使用SelectIndexChanged作為回調,不要使用SelectTextChanged,因為當Index變化時也會觸發Text屬性的變化。
這里簡單介紹一下在撤銷恢復時,不觸發textBox1的Text的屬性回調函數的另外一種方法。我在這里將其稱之為原子操作。可以將textBox1的回到函數textBox1_TextChanged作為一個參數,傳入到Command對象中。在對TextBox的Text屬性變化前先做這個動作:ctrl.TextChanged -=動作,賦值完成后,再做Ctrl.TextChanged +=動作。下面用一個函數簡單模擬一下:
void setTextBox(TextBox ctrl, String str, EventHandler eventhandler)
{
ctrl.TextChanged -= eventhandler;
ctrl.Text = str;
ctrl.TextChanged += eventhandler;
}
附上源碼:http://files.cnblogs.com/files/youyipin/test_Charp.zip