圖1 示例模型
為了節約篇幅和時間,我就不較長的描述構造EMF項目的步驟了,這裡主要把使用EMF與非EMF模型的差別做一個說明。圖1是例子中使用的模型,其中Dimension和Point是兩個外部java類型,由于EMF并不了解它們,是以定義為datatype類型。
使用兩個Plugins
為了讓模型與編輯器更好的分離,可以讓EMF模型單獨位于一個Plugin中(名為SubjectModel),而讓編輯器Plugin (SubjectEditor)依賴于它。這樣做的另一個好處是,當修改模型後,如果你願意,可以很容易的删除以前生成的代碼,然後全部重新生成。
EditPart中的修改
在以前我們的EditPart是實作java.beans.PropertyChangeListener接口的,當模型改用EMF實作後, EditPart應改為實作org.eclipse.emf.common.notify.Adapter接口,因為EMF的每個模型對象都是 Notifier,它維護了一個Adapter清單,可以把Adapter作為監聽器加入到模型的這個清單中。
實作Adapter接口時須要實作getTarget()和setTarget()方法,target代表發出消息的那個模型對象。我的實作方式是在EditPart裡維護一個Notifier類型的target變量,這兩個方法分别傳回和設定該變量即可。
還要實作isAdapterForType()方法,該方法傳回一個布爾值,表示這個Adapter是否應響應指定類型的消息,我的實作一律為"return type.equals(getModel().getClass());"。
另外,propertyChanged()方法的名稱應改為notifyChanged()方法,其實作的功能和以前是一樣的,但代碼有所不同,下面是NodePart中的實作,看一下就應該明白了:

public void notifyChanged(Notification notification) {

int featureId = notification.getFeatureID(ModelPackage.class);

switch (featureId) {

case ModelPackage.NODE__LOCATION:

case ModelPackage.NODE__SIZE:

refreshVisuals();

break;

case ModelPackage.NODE__INCOMING_CONNECTIONS:

refreshTargetConnections();


case ModelPackage.NODE__OUTGOING_CONNECTIONS:

refreshSourceConnections();


}

}

還有active()/deactive()方法中的内容需要修改,作用還是把EditPart自己作為Adapter(不是 PropertyChangeListener了)加入模型的監聽器清單,下面是SubjectPart的實作,其中eAdapters()得到監聽器列 表:

public void activate() {

super.activate();

((Subject)getModel().eAdapters()).add(this);


可以看到,我們對EditPart所做的修改實際是在兩種消息機制之間的轉換,如果你對以前的那套機制很熟悉的話,這裡了解起來不應該有任何困難。
ElementFactory的修改
這個類的作用是根據template建立新的模型對象執行個體,以前的實作都是"new XXX()"這樣,用了EMF以後應改為"ModelFactory.eINSTANCE.createXXX()",EMF裡的每個模型對象執行個體都應該是使用工廠建立的。

public Object getNewObject() {

if (template.equals(Diagram.class))

return ModelFactory.eINSTANCE.createDiagram();

else if (template.equals(Subject.class))

return ModelFactory.eINSTANCE.createSubject();

else if (template.equals(Attribute.class))

return ModelFactory.eINSTANCE.createAttribute();

else if (template.equals(Connection.class))

return ModelFactory.eINSTANCE.createConnection();

return null;


使用自定義CreationFactory代替SimpleFactory
在原先的PaletteFactory裡定義CreationEntry時都是指定SimpleFactory作為工廠,這個類是使用 Class.newInstance()建立新的對象執行個體,而用EMF作為模型後,建立執行個體的工作應該交給ModelFactory來完成,是以必須定義 自己的CreationFactory。(注意,示例代碼裡沒有包含這個修改。)
處理自定義資料類型
我們的Node類裡有兩個非标準資料類型:Point和Dimension,要讓EMF能夠正确的将它們儲存,必須提供序列化和反序列化它們的方 法。在EMF為我們生成的代碼裡,找到ModelFactoryImpl類,這裡有形如convertXXXToString()和 createXXXFromString()的幾個方法,分别用來序列化和反序列化這種外部資料類型。我們要把它的預設實作改為自己的方式,下面是我對 Point的實作方式:

public String convertPointToString(EDataType eDataType, Object instanceValue) {

Point p = (Point) instanceValue;

return p.x + "," + p.y;


public Point createPointFromString(EDataType eDataType, String initialValue) {

Point p = new Point();

String[] values = initialValue.split(",");

p.x = Integer.parseInt(values[0]);

p.y = Integer.parseInt(values[1]);

return p;


注意,修改後要将方法前面的@generated注釋删除,這樣在重新生成代碼時才不會被覆寫掉。要設定使用這些類型的變量的預設值會有點問題(例 如設定Node類的location屬性的預設值),在EMF自帶的Sample Ecore Model Editor裡設定它的defaultValueLiteral為"100,100"(這是我們通過convertPointToString()方法定 義的序列化形式)會報一個錯,但不管它就可以了,在生成的代碼裡會得到這個預設值。
儲存和載入模型
EMF通過Resource管理模型資料,幾個Resource放在一起稱為ResourceSet。前面說過,要想正常儲存模型,必須保證每個模 型對象都被包含在Resource裡,當然間接包含也是可以的。比如例子這個模型,Diagram是被包含在Resource裡的(建立新Diagram 時即被加入),而Diagram包含Subject,Subject包含Attribute,是以它們都在Resource裡。在圖1中可以看到, Diagram和Connection之間存在一對多的包含關系,這個關系的主要作用就是確定在儲存模型時不會出現 DanglingHREFException,因為如果沒有這個包含關系,則Connection對象不會被包含在任何Resource裡。
在删除一個對象的時候,一定要保證它不再包含在Resource裡,否則儲存後的檔案中會出現很多空元素。比較容易犯錯的地方是對 Connection的處理,在删除連接配接的時候,隻是從源節點和目标節點裡删除對這個連接配接的引用是不夠的,因為這樣隻是在界面上消除了兩個節點間的連接配接 線,而這個連接配接對象還是包含在Diagram裡的,是以還要調用從Diagram對象裡删除它才對,DeleteConnectionCommand中的 代碼如下:

public void execute() {

source.getOutgoingConnections().remove(connection);

target.getIncomingConnections().remove(connection);

connection.getDiagram().getConnections().remove(connection);


當然,建立連接配接時也不要忘記将連接配接添加在Diagram對象裡(代碼見CreateConnectionCommand)。儲存和載入模型的代碼請 看SubjectEditor的init()方法和doSave()方法,都是很标準的EMF通路資源的方法,以下是載入的代碼(如果是新建立的檔案,則 在Resource中建立Diagram對象):

public void init(IEditorSite site, IEditorInput input) throws PartInitException {

super.init(site, input);

IFile file = ((FileEditorInput) getEditorInput()).getFile();

URI fileURI = URI.createPlatformResourceURI(file.getFullPath().toString());

resource = new XMIResourceImpl(fileURI); //注意要區分XMIResource和XMLResource

try {

resource.load(null);

diagram = (Diagram) resource.getContents().get(0);

} catch (IOException e) {

diagram = ModelFactory.eINSTANCE.createDiagram();

resource.getContents().add(diagram);



雖然到目前為止我還沒有機會體會EMF在模型互動引用方面的優勢,但經過進一步的了解和在這個例子的應用,我對EMF的印象已有所改觀。據我目前所知,使用EMF模型作為GEF的模型部分至少有以下幾個好處:
隻需要定義一次模型,而不是類圖、設計文檔、Java代碼等等好幾處;
EMF為模型提供了完整的消息機制,不用我們手動實作了;
EMF提供了預設的模型持久化功能(xmi),并且允許修改持久化方式;
EMF的模型便于交叉引用,因為擁有足夠的元資訊,等等。
此外,EMF.Edit架構能夠為模型的編輯提供了很大的幫助,由于我現在對它還不熟悉,是以例子裡也沒有用到,今後我會修改這個例子以利用EMF.Edit。