第一次写关于设计模式的随笔,最近在使用C#做一个WinForm的项目,其中要求需要支持撤销和恢复功能,想到了以前看过Command模式支持撤销和恢复操作,就在项目中使用了。对命令模式理解的不够深,各位看客请指正。
Gof23种设计模式中的Command模式,其意图是这么描述的“将一个请求封装为一个对象,从而是你可以用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作”;另外个人的理解就是可以将调用者和接受者解耦出来。下图为Comand的类图:
。首先理解将调用者和接受者解耦出来: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);
}
恢复操作: