本笔记[不]是从《面向模式的软件体系架构 卷1 模式系统》摘抄,而是《Pattern-Oriented Software Architecture vol.1 A system of patterns》原书[page 282-286]的翻译组成,主要内容为如何实现command processor模式!
---------------------------------------------------
[实现]
执行以下步骤可以实现此模式:
1 定义abstract command接口。abstract command类隐藏特定command的细节,定义执行command的抽象方法,同时定义了支持command processor提供的附加服务的必要方法,例如,名为‘getNameAndParameters’的方法为记录command的日志提供支持。
我们将TEDDI中的‘撤销’机制分为三种command类型,将他们定义成枚举类型,因为command类型有可能像第3步一样动态改变:
No change:不需要‘撤销’的command,光标移动就属于此类。
Normal:可被‘撤销’的command,在文本中替换单词就属于此类。
No undo:不能被‘撤销’的command,防止撤销之前执行的normal command。
如果要把我们的文本变成‘政治上正确‘的文本,把‘他’替换成‘他/她’,TEDDI将需要存储文档中所有相应的位置,以备撤销,对于在全文档这样的替换操作将导致潜在的大量存储,这也就促使我们将这种command归类为‘no undo’。
class AbstractCommand {
public:
enum CmdType { no_change, normal, no_undo } ;
virtual -Abstractcommand ( ) ;
virtual void doit () ;
virtual void undo ( ) ;
CmdType getType() const { return type;)
virtual String getName() const { return "NONAME";}
// gives name of command for selection
// in undo/redo menu
protected:
CmdType type;
Abstractcommand (CmdType t=no_change) : type (t ) { }
};
方法getName()可以用来在用户选择‘撤销’命令时显示最近执行过的command名称。
2 为应用程序支持的每种请求类型设计command组件。绑定supplier和command的方式有多种:将supplier硬编码到command中,或controller将supplier以command构造函数的参数形式传给它。对于第二种选项,多文档编辑器就是一个很好的例子,在多文档编辑器中,command会连接到一个特定的文档对象。
TEDDI中的‘删除‘command将表示文本的对象作为他第一个参数,需要删除的字符范围作为后面两个参数:
class DeleteCmd : public AbstractCommand {
public:
DeleteCmd(TEDD1_Text *t, int start, int end)
: ~bstractcornman(dn ormal) , mytext (t) ,
from (start) , to (end) {/*...*/}
virtual -DeleteCmd();
virtual void doit();
// delete characters in mytext
// between from and to and save them in delstr
virtual void undo ( ) ;
// insert delstr again at position from
String getName() const { return "DELETE" + delstr;}
protected:
TEDDI_Text *mytext;// plan for multiple text buffers
int from,to; // range of characters to delete
String delstr; // save deleted text for undo
};
在doit()的实现中,将调用TEDDI_Text中提供的deleteText()。
Command对象也许会要求用户提供更多的参数。TEDDI提供‘load text file’命令,例如,弹出一个输入装载文件名的对话框,在此情况下,事件处理系统应该将用户输入转给command,而不是controller。(Commands that require user interaction during their creation or execution therefore call for additional care.)需要与用户交互的command,在被创建或执行时需要特殊处理。事件处理系统(已经超出此模式的讨论范围)必须能处理这种情况。
可撤销的command可以使用Memento模式[GHJV95]在不破坏封装性的前提下存储supplier的状态以备撤销。
[284]
3 通过组合多个连续的command组成一个macro command,增加灵活性。可以应用Composite模式[GHJV95]来实现这种macro command组件。
在TEDDI中,实现macro command类,允许用户定义经常使用的的命令序列快捷方式:
class MacroCmd : public AbstractCommand {
public :
MacroCmd(String name, AbstractCommand *first)
: AbstractCommand ( first ->getType 0 ) ,
macroname(name) { / * . . . * / }
virtual -MacroCmd();
virtual void doit ( ) ;
// do every command in cmdlist
virtual void undo () ;
// undo all commands in cmdlist in reverse order
virtual void finish(); // delete commands in cmdlist
void add(Ab-stractcommand *next) {
cmdlist.append(next) ;
if (next - >getType ( ) == no-undo) type = no-undo;
/* ... * / }
String getName ( ) const { return macroname; }
protected:
String macroname;
OrderedCollection<AbstractCommand*> cmdlist;
} ;
MacroCmd的command类型依赖于添加到macro中的command,添加了no-undo类型的command将避免在整个macro command上做撤销操作,否则undo()将从反方向在cmdlist的每个普通command对象上做撤销操作,当然在此过程中会跳过类型为no-change的command。
4 实现controller组件。controller可以采用创建型模式(如:Abstract Factory和Prototype模式[GHJV95])创建command对象,当然,controller已经从supplier组件中解偶了,这种另外的解偶模式不是必须的。应用程序中的controller提供的菜单是Prototype模式应用的一个很好的例子,controller中包含了每个菜单项的command原型
,当用户选择菜单项时,controller将相应command原型的一个拷贝传递给command processor处理,如果这样的一个支持菜单的controller能被动态地用macro command对象配置,那就能很容易地实现用户自定义菜单。
[285]
TEDDI的用户交互是通过controller中的回调过程处理,在回调过程中将创建相应的command对象,并把它传递给command processor,TEDDI采用了theCP这个全局变量来引用唯一的一个command processor组件。
void TEDDI_control1er::deleteButtonPressed() {
AbstractCommand *delcmd =
new DeleteWordConunand(
this->getcursor(),// pass cursor position
this->getText()); //pass text
theCP ->perform (delcmd) ;
}
在程序一启动的时候,回调函数deleteButtonPressed()就被注册到了事件处理系统中。
5 实现command processor提供的附加服务访问点。用户可访问的附加服务通常用特定的command类实现;command processor提供完成服务功能的方法,也可以通过直接调用command processor中的方法来访问附加服务;但比如command日志一类的内部服务应该自动被command processor执行。
类UndoCommand可以访问TEDDI的撤销机制,此类同command processor的内部数据协作,因此被定义成command processor的友元(译者注:c++中的friend)。注意:类UndoCommand需分类为no-change,不能被command processor存储下来。
class UndoCommand : public AbstractCommand {
public:
UndoCommand ( )
: AbstractCommand(no_change) { }
virtual ~Undocommand ( ) ;
virtual void doit() { theCP->undo_lastcmd(); }
} ;
UndoCommand中的doit()请求command processor撤销最后执行的一次普通操作。类RedoCommand则提供了相反的功能,它的方法doit()将使command processor重新执行一次已经撤销的命令。
[286]
6 实现command processor组件。command processor接收从controller传递过来的command对象并管理他们,调用每个command对象的‘do’方法执行其命令。例如:用c++实现的command processor将负责删除不再使用的command对象。
可以应用singleton模式[GHJV95]确保只有一个command processor存在。
在TEDDI中,我们采用2个栈实现多层撤销/重做的功能,一个栈保存执行过的命令,另一个保存已经撤销的命令:
class Commandprocessor {
pub1ic :
CommandProcessor ( ) ;
virtual ~CommandProcessor();
virtual void do_cmd(AbstractCommand *cmd) {
// do cmd and push it on donestack
cmd->doit () ;
switch(cmd->getType()) {
case AbstractCommand::normal:
donestack .push (cmd) ; break;
case AbstractCommand::no_undo:
dones tack. make_empty ( ) ;
undonestack .make_empty () ;
// Fall through:
case AbstractCommand::no-change:
// take responsibility for command objects:
delete cmd;
break;
}
}
friend class UndoCommand; // special relationship
friend class Redocommand; // special relationship
private:
// this method is only used by Undocommand
virtual void undo_lastcmd() ;
// pop cmd from donestack,
// undo it, and push it on undonestack
// this method is only used by Redocommand
virtual void redo_lastundone() {
AbstractCommand *last = undonestack.pop();
if (last) this->do-cmd(1ast);
}
private:
Stack<AbstractCommand*> donestack,undonestack;
} ;