Command
命令模式介绍:
Command
命令模式是一种对象行为型模式,它主要解决的问题是:在软件构建过程中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”的问题。如下图:
有时我们必须向某对象提交请求,但并不知道关于被请求的操作或请求的接受者的任何信息,此时无法抵御变化的紧耦合是不合适的。如:需要对行为进行“记录、撤销
/
重做、事务”等处理。我们所要做的是将依赖关系转化,将紧耦合变为松耦合。则上图的形式转化为如下形式:
Command
模式通过将请求本身变成一个对象来使行为请求者可向未指定的应用对象提出请求。
GoF
《设计模式》中说道:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
Command
命令模式结构:
定义场景:
现在来看一个场景:对于
notepad
大家都很熟悉,在我们使用
notepad
打开一个文档之后,往往做一些操作,如;输入字符
(Write)
、删除前一个字符(
Delete
)、撤销刚才的操作(
UnDo
)。现在我们就用
Console
程序模拟这个过程。
代码实现与结构分析:
在实现代码前先说明实现
Command
模式需要烤炉的一些问题:
1、
一个命令对象应达到何种智能程度:命令对象的能力可大可小。这样就出现了两个极端。一是:它仅确定一个接收者和执行该请求的动作;一是:它自己实现所有功能,根本不需要额外的接收者对象。我给他们起了便于我方便记忆的名字,第一种叫
OperationCommand
,第二种叫
ObjectCommand
。当然只是为了便于记忆和理解,如有不理解,在代码实现与结构分析最后我会再谈谈我的想法,如有不妥,还请多提意见,一会在下面的代码中会分别对这两种情况进行示范。
2、
支持取消和重做:为了达到这个目的
ConcreteCommand
类中要存储额外的状态信息。也就是上图中
ConcreteCommand
的
state
属性。
3、
避免取消操作中过程中的错误积累:由于命令重复的执行、取消执行和重执行的过程可能会积累错误,以致一个应用的状态最终偏离初始值。这就有必要在
Command
中存入更多的信息以保证这些对象可被精确的复原。
下面来看看代码上的实现:首先,我先作一个
OperationCommand
的例子,先做一个请求的接收者
Document
(也就是结构图中的
Receiver
)
class
Document
{
public
string strContent;
public Document()
{
strContent = "";
}
}
在这个程序中我们还要定义一个抽象类Command,对于OperationCommand类型来说,它仅确定一个接收者和执行该请求的动作。所以,在抽象类Command中,只声明一个Excute的方法。这个方法在其子类中进行实现。(当然这个Command还可以定义成接口)
abstract
class
Command
{
public Command()
{ }
public
abstract
void Excute();
}
接下来,就要实现各种操作(结构图中的ConcreteCommand),代码如下
//
写操作
class
WriteCommand : Command
{
Document doc;
ArrayList ObjectState;
public WriteCommand(Document doc, ArrayList state)
{
this.doc = doc;
ObjectState = state;
}
public
override
void Excute()
{
doc.strContent += Console.ReadLine();
ObjectState.Add(doc.strContent);
}
}
//
删除操作
class
DeleteCommand : Command
{
Document doc;
ArrayList ObjectState;
public DeleteCommand(Document doc, ArrayList state)
{
this.doc = doc;
ObjectState = state;
}
public
override
void Excute()
{
doc.strContent = doc.strContent.Substring(0, doc.strContent.Length - 1);
ObjectState.Add(doc.strContent);
}
}
//
撤销操作
class
UnDoCommand : Command
{
Document doc;
ArrayList ObjectState;
public UnDoCommand(Document doc, ArrayList state)
{
this.doc = doc;
ObjectState = state;
}
public
override
void Excute()
{
doc.strContent = (string)ObjectState[ObjectState.Count - 2];
ObjectState.Add(doc.strContent);
}
}
实现了各种操作后,编写一个客户代码进行测试
class
Program
{
static
void
Main(string[] args)
{
Document doc = newDocument();
Console.WriteLine("Please Input next operation:");
string strOperation = Console.ReadLine();
Command com = null;
ArrayList ObjectState = newArrayList();//Record state
while (strOperation != "Exit")
{
switch (strOperation.ToLower())
{
case
"write":
com = newWriteCommand(doc, ObjectState);
com.Excute();
Console.WriteLine("Write Operation:" + doc.strContent);
break;
case
"del":
com = newDeleteCommand(doc, ObjectState);
com.Excute();
Console.WriteLine("Delete Operation:" + doc.strContent);
break;
case
"undo":
com = newUnDoCommand(doc, ObjectState);
com.Excute();
Console.WriteLine("UnDo Operation:" + doc.strContent);
break;
default:
Console.WriteLine("Wrong Operation:");
break;
}
Console.WriteLine("Please Input next operation:");
strOperation = Console.ReadLine();
}
}
}
测试结果:
Please Input next operation:
write
k
Write Operation:k
Please Input next operation:
write
i
Write Operation:ki
Please Input next operation:
write
d
Write Operation:kid
Please Input next operation:
write
d
Write Operation:kidd
Please Input next operation:
del
Delete Operation:kid
Please Input next operation:
undo
UnDo Operation:kidd
Please Input next operation:
下面再来实现以下ObjectCommand的例子,首先还是编写一个已存在的请求接收者Document(结构图中的Receiver)
class
Document
{
public
string strContent;
public Document()
{
strContent = "";
}
}
接下来实现抽象类Command(也可以使用接口),对于ObjectCommand类型来说,
它自己实现所有功能,根本不需要额外的接收者对象,所以在
Command
中声明了所有的操作
abstract
class
Command
{
public Command()
{ }
public
abstract
void Write();
public
abstract
void Delete();
public
abstract
void UnDo();
}
有了Command,就可以实现具体的操作类型DocumentCommand(结构图中的ConcreteCommand)
class
DocumentCommand : Command
{
private
Document doc;
private
ArrayList ObjectState = newArrayList();//Record State
public DocumentCommand(Document doc)
{
this.doc = doc;
}
public
override
void Write()
{
Console.WriteLine("Please input an character:");
string strRead = Console.ReadLine();
doc.strContent += strRead;
ObjectState.Add(doc.strContent);
}
public
override
void Delete()
{
doc.strContent = doc.strContent.Substring(0, doc.strContent.Length - 1);
ObjectState.Add(doc.strContent);
}
public
override
void UnDo()
{
doc.strContent = (string)ObjectState[ObjectState.Count - 2];
ObjectState.Add(doc.strContent);
}
}
接下来就用一个客户端代码作一下测试
class
Program
{
static
void
Main(string[] args)
{
Document doc = newDocument();
DocumentCommand com = newDocumentCommand(doc);
Console.WriteLine("Please Input next operation:");
string strOperation = Console.ReadLine();
while (strOperation != "Exit")
{
switch (strOperation.ToLower())
{
case
"write":
com.Write();
Console.WriteLine("Write Operation:" + doc.strContent);
break;
case
"del":
com.Delete();
Console.WriteLine("Delete Operation:" + doc.strContent);
break;
case
"undo":
com.UnDo();
Console.WriteLine("UnDo Operation:" + doc.strContent);
break;
default:
Console.WriteLine("Wrong Operation:");
break;
}
Console.WriteLine("Please Input next operation:");
strOperation = Console.ReadLine();
}
}
}
测试结果如下:
Please Input next operation:
write
Please input an character:
k
Write Operation:k
Please Input next operation:
write
Please input an character:
i
Write Operation:ki
Please Input next operation:
write
Please input an character:
d
Write Operation:kid
Please Input next operation:
write
Please input an character:
d
Write Operation:kidd
Please Input next operation:
del
Delete Operation:kid
Please Input next operation:
undo
UnDo Operation:kidd
Please Input next operation:
这两个程序中需要有几点说明:
1、
对于OperationCommand,我的理解是它所实现的Command只是某一个操作对于某一个接收者,所以我给它取名为OperationCommand。对于ObjectCommand,是实现这样一种对象,它实现了请求接收者的所有操作,所以取名为ObjectCommand
2、
在代码实例中,我对状态的保存处理相对简单,但这是因为利用了String对象的特点,当String对象被修改时,系统会重新分配一块内存。不修改原内存上的内容。如果是要保存其他的引用类型应当注意使用深拷贝,否则,所保存的状态对象都指向一个内存地址,随着状态的改变,保存不了原有的状态。
3、
在对象状态的保存上,我们可以使用Prototype模式。
Command
模式的几个要点:
1、
Command
模式的根本目的在于将“行为请求者”与“行为实现者”解耦,在面向对象语言中,常见的实现手段是“将行为抽象为对象”。
2、
实现
Command
接口的具体命令对象
ConcreteCommand
有时候根据需要可能会保存一些额外的状态信息。
3、
通过使用
Composite
模式,可以将多个“命名”封装为一个“复合命令”
MacroCommand
。
4、
Command
模式与
C#
中的
Delegate
有些类似。但两者定义行为接口的规范有所区别:
Command
以面向对象中的“接口
-
实现”类定义行为接口规范,更严格,更符合抽象原则:
Delegate
以函数签名来定义行为接口规范,更灵活,但抽象能力比较弱