借助EMF的幫助,不用親自編寫一行代碼就可以生成一個完整的應用程序,你是不是對EMF 有些感興趣了?不過生成的應用程序看起來都像是從同一個模子裡出來的,即一個多頁編輯 器,一個大綱視圖和屬性頁,這當然無法滿足所有人的需求。不用耽心,只要了解EMF的機制 ,按照我們的要求修改這個應用程序並不是一件很困難的事情。
首先大概的看一下EMF為我們生成了哪些東西吧。按照前文的操作,EMF應該一共生成了四 個插件項目:com.my.shop、 com.my.shop.edit、com.my.shop.editor和com.my.shop.tests ,其中最後一個項目是方便我們編寫單元測試的框架代碼,這裡我們先不管它,暫時把注意 力集中在前三個項目上。
第一個項目是模型部分,主要包含你定義的ecore模型裡各類型(EClass,在ecore元模型 裡類型稱為EClass,屬性稱為 EAttribute)對應的java接口和缺省的實現類代碼,例如 Product.java和ProductImpl.java,它們分別被放置在 com.my.shop和com.my.shop.impl包 裡;一個工廠類(ShopFactory)使用工廠模式創建新的模型實例;一個 Package類 (ShopPackage)維護關於元模型的信息,提供了一堆靜態成員變量;此外還生成了 ShopAdapterFactory和 ShopSwitch這兩個類,它們是可選的,它們倆的作用這裡賣個關子暫 時先不說。
第二個項目是.edit部分,這裡面包含了一系列ItemProvider類,用來為在jface的各種查 看器(Viewer)裡顯示這些模型對象提供便利,以CategoryItemProvider為例,它實現了 IStructuredItemContentProvider、 ITreeItemContentProvider和IItemLabelProvider這些 接口,注意把這些接口名字中的"Item"去掉就是 jface裡需要的Provider,可以把這些帶 有"Item"字樣的Provider想象成對jface相應Provider的包裝。有了這些 Provider,在應用 程序裡使用jface的TreeViewer、TableViewer等查看器就很方便了。(前面講GEF的一個帖子 裡曾利用 EMF構造模型,當時使用的就僅僅是模型部分,因為並未用到jface查看器。所以視 你的應用程序而定,可以只生成模型部分來用,.edit部分依賴模型部分,而反之不然。)
第三個項目是編輯器,這個部分依賴.edit部分,主要包含了幾個UI方面的類。EMF為我們 生成的這個編輯器有兩個用途:一是讓我們可以不用從零開始,而是在這個編輯器的基礎上 進行修改得到自己的編輯器;二是通過這些代碼展示怎樣在應用程序裡使用前兩個項目裡的 那些類,編輯器包含那麼多Tab正是為了演示各種查看器的用法。下面來說一下怎樣定制生成 的應用程序。
一、修改ecore模型和genmodel模型
在ecore模型和genmodel模型裡我們可以通過修改一些屬性改變所生成的代碼,例如希望 新創建的類別和產品的名稱不是空字符串而是" (Unnamed)",就可以在類圖裡修改 NamedElement類的name屬性的"Default Value Literal"屬性(沒錯,屬性的屬性。見圖1) 。修改ecore模型後,必須更新genmodel模型,方法是在Package Explorer裡右鍵單擊 shop.genmodel文件,在彈出菜單裡選擇"Reload...",這樣genmodel會從修改後的ecore模型 裡獲得修改過的信息。之後,再次從genmodel模型生成一遍代碼,這樣得到的程序運行後, 類別和產品的名稱缺省就是"(Unnamed)"了。在 genmodel模型裡則可以定制更多屬性,例如 所生成的每個項目的id、生成類所在的包名(本例中為com.my)、類名前綴(本例中為Shop )、是否生成圖標、標准屬性頁裡各屬性的分類等等。
圖1 在EclipseUML類圖裡修改屬性的缺省值
有人對 ecore模型和genmodel模型各自的用途搞不清楚,其實因為JET是直接通過 genmodel生成代碼的,所以像是否生成圖標這些信息放在 genmodel裡是比較合適的,而 ecore模型裡定義的是各個類型以及之間的關系,所以像屬性缺省值這樣的信息是應該放在 ecore裡沒錯的。
二、直接修改生成的代碼
前面的方式是讓EMF生成我們想要的程序,好處是非常直觀和方便,缺點是只有有限的信 息可被定制,而一個復雜的應用程序是不太可能僅僅通過填寫一些屬性就被生成出來的,所 以我們需要代碼級的定制。有一個事實你需要知道:無論對ecore還是genmodel模型定制,產 生的結果最終都要反映到生成的代碼。讓我們對比一下修改名稱缺省值前後的代碼(因為類 圖裡NamedElement被定義為抽象類,所以這段代碼的位置在 CategoryImpl.java和 ProductImpl.java裡),這是之前生成的代碼:
/**
* The default value of the '{@link #getName() <em>Name</em>}' attribute.
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @see #getName()
* @generated
* @ordered
*/
protected static final String NAME_EDEFAULT = null;
下面是定制之後的代碼:
/**
* The default value of the '{@link #getName() <em>Name</em>}' attribute.
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @see #getName()
* @generated
* @ordered
*/
protected static final String NAME_EDEFAULT = "(Unnamed)";
可以看到只是NAME_EDEFAULT的值發生了變化。也就是說,如果我們不修改ecore模型,而 是手工修改這段代碼也能達到同樣的目的。對於熟悉 EMF的開發人員來講,修改代碼很多時 候甚至比修改ecore/genmodel模型更加快速(因為省去了reload genmodel和生成代碼這兩步 ),但一定要記住,在修改代碼的同時修改這部分代碼前的javadoc。因為在每次重新生成代 碼的時候,JET通過原有代碼前的javadoc判斷是否覆蓋這段代碼,如果javadoc裡包含一 行"@generated"則覆蓋,否則保持這部分代碼不變。為了讓我們手工的修改不在以後的生成 中消失(這可是很大的損失),可以直接刪除@generated這一行,更好的辦法是將其改為一 個統一的字符串,例如 "@generated NOT",以便將來通過javadoc搜索找到自己都在哪些地 方做了手工修改。
下面再通過一個例子演示怎樣修改EMF生成的代碼。假設現在有個需求是讓所有價格大於 等於100元的產品自動打95折,而小於5元的商品一律按5元算,那麼我們需要修改 ProductImpl的getPrice()方法,修改前的代碼如下:
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated
*/
public double getPrice() {
return price;
}
這是修改後的代碼,再次提醒一定別忘記修改@generated那行注釋:
/**
* <!-- begin-user-doc -->
* 所有價格大於等於100元的產品自動打95折,而小於5元的商品一律按5元算。
* <!-- end-user-doc -->
* @generated NOT
*/
public double getPrice() {
if (price >= 100)
return price * 0.95;
if (price < 5)
return 5d;
return price;
}
至於使用哪種方式進行定制,我個人建議能通過修改ecore/genmodel模型解決的問題就不 要直接修改代碼,盡量減少手工修改的地方有利於保持思路清晰,其實在實際使用中大部分 定制還是需要修改代碼才能實現的。
本文配套源碼