(注:大部分翻譯,整理自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));