(注:大部分翻译,整理自Eclipse Modeling Framework: A Developer's Guide一书的第3.3, 3.4节的内容)
EMF的Command框架分为两个部分,Common Command Framework和EMF.Edit的Commands。Common Command Framework定义了基本的Command接口并提供了一些基本的类,例如CommandStack, CompoundCommand等。这个Command框架是非常通用的,并不依赖于EMF.Edit,甚至也不依赖于EMF,是完全独立的一个框架。而EMF.Edit的Commands,则是专门用来编辑EObjects的。
Command接口是所有Command必须实现的基本接口。包括了execute(),undo(), redo()等方法。Command在执行先会先进行测试,执行canExecute()判断其能否被执行。canUndo()被用来检查一个Command是否能被undo(),它返回false也就意味着redo()和undo()没有被实现。Command接口的getResult()和getAffectedObjects()方法是可选的,但是有时候非常有用。getResult()可以用来返回一个执行的结果对象,而getAffertedObjects()方法则可以用来返回在执行的过程中被修改过的对象。通常getAffertedObjects()可以和getResult返回相同的对象,但也并不总是如此。
AbstractCommand提供对Command接口的缺省实现。唯一重要的实现是canExecute()方法,将控制流转交给另一个钩子方法prepare()。然而prepare()只被执行一次,而不论canExecute()被执行多少次,这个特性在某些情况下可以避免大量的计算被重复执行。
public boolean canExecute() {
if (!isPrepared) {
isExecutable = prepare();
isPrepared = true;
}
return isExecutable;
}
CommandStack接口定义了一个Command栈,使用后进先出的方式保存所有执行过的Command,使得undo()和redo()能够被方便的实现。BasicCommandStack是CommandStack接口的一个简单实现。
CompoundCommand是一个很有用的类。可以让你通过基本的Command对象来组合更为复杂的高层次的Command。它的execute()方法会依次调用每一个成员Command;而canExecute()等测试方法在当所有成员Command的canExecute()方法返回为true是方返回true。一个有用的技巧是使用appendAndExecute()来加入并立即执行一个Command;使用这个方法可以记录一个命令执行的序列,并一次性的进行undo()。
EMF.Edit Commands在Common Command Framework的基础上,执行专门正对于EObject的命令。它定义了如下的几个基本Command:
1. SetCommand:用来为EObject的attribute或者reference设置值。
2. AddCommand:用来为EObject的multiplicity为many的feature添加一个或者多个值。
3. RemoveCommand:则是用来做和AddCommand相反的事情。
4. MoveCommand:用来移动对象在multiplicity为many的feature中的位置。
5. ReplaceCommand:用来替换一个multiplicity为many的feature中的对象。
6. CopyCommand:执行EObject对象的深度拷贝(Clone).
除了CopyCommand之外,其他的Command均为简单命令,而CopyCommand命令则是由CreateCopyCommand和InitializeCopyCommand这两个命令组合而成。
除了上述的基本命令之外,EMF.Edit基于基本命令的,执行High Level编辑功能的命令:
1. CreateChildCommand。
2. CutToClipboardCommand。
3. CopyToClipboardCommand。
4. PasteFromClipboardCommand。
5. DragAndDropCommand。
AbstractOverrideableCommand作为EMF.Edit的Command的抽象基类,上面大部分的命令均继承于它,而AbstractOverrideableCommand则是继承于AbstractCommand。它提供了扩展机制来覆盖其自生的execute()方法:
public final void execute() {
if (overrideCommand != null)
overrideCommand.execute();
else
doExecute();
}
之所以这样做的,而不是直接使用继承来扩展Command原因,是因为你可以使用Common Command来实现模型相关而与EMF无关的Command,来扩展这些专门针对于EMF EObjects的命令。从上面的代码也可以看出,如果要用继承来覆盖已有的命令的话,应该覆盖doExecute()方法而不是execute()方法。
通常,Command都是由EditingDomain接口的createCommand(Class commandClass, CommandParameter commandParameter)来创建的,它接受通用的CommandParater参数,来创建Command。但是,为了方便起见,每一个EMF.Edit Command都提供了static的create()方法来创建相应的对象,而由它在来调用EditingDomain的createCommand()。
EditingDomain需要完成三种功能:
1. 创建commands,在AdapterFactoryEditingDomain中这是通过代理到一个Item Provider来实现的。
2. 管理Command Undo栈,通过CommandStack来实现。
3. 提供方法来访问EMF模型对象的ResourceSet,以及load或者save Resource。
创建一个对象,一般来说使用的是Command的静态create()方法。例如对于RemoveCommand来说,可以通过如下的代码来创建一个RemoveCommand:
Command cmd = RemoveCommand.create(editingDomain,
aPurchaseOrder,
poPackage.getPurchaseOrder_Items(),
aItem);
在EditingDomain的ResourceSet之间存在有双向的关联。因为一个对象知道其Resource,而Resource又知道其ResourceSet,因此,由一个对象可以得到其EditingDomain,而EditingDomain又提供了创建Command的方法,因此一个对象可以在任何地方都能够创建修改这个对象的Command了,如下例所示:
EditingDomain editingDomain = getEditingDomain(object);
editingDomain.getCommandStack().execute(
SetCommand.create(editingDomain, object, feature, value));