用模型驅動Eclipse開發 ( by quqi99 )
EMF(Eclipse Modeling Framework)是一個模型架構和代碼生成工具,它可以用來建構以結構化數據為基礎的工具或者應用。作為MDA和Eclipse的結合體,它發展迅速,IBM的大部分工具產品如RSA等都將建立在它的基礎之上。
樣例
我們將構建一個簡單的手機庫管工具,它只維護種類信息,并不存儲數量庫位等。通過該工具,我們可以添加,刪除,修改主機及配件;維護主機,配件的功能;并且可以通過拖放將主機和相關配件組成一種配置。
建立模型
EMF通過JET和JMERGE來實現支持MDA,它可以從annotated Java, UML, or XML Schema三種模型生成Eclipse plug-in代碼。我們將用Rational Rose建立UML模型來作為系統模型。
將模型導入Eclipse中
建立EMF工程,在Wizard中會提示選擇初始化的模型內容。從Rose中讀取類模型以后,將生成了一個工程,其中包含兩個文件:.ecore和.genmodel。
Ecore文件代表我們的模型本身,你可以通過修改它來改變模型。比如改變屬性的類型,類之間的關系等。你也完全可以通過手動的方式建立ecore文件來創建整個模型。我們可以看到,在ecore模型中,所有的類被轉換為EClass;聚合關系變成了一個EClass包含其它EClass作為EReference,例如主機、配置、配件都作為庫存的EReference。在這里,我們需要修改EReference的Containment屬性,缺省的值是false。Containment屬性是在持久化時的一個重要屬性,它表明存儲時數據的包含關系,如果全部保留為fasle,將不會有任何信息被存儲到文件中,除了頂級節點:庫存。應該以誰創建,誰包含為原則。比如庫存可以創建主機、配件、配置,那么這些庫存的EReference的Containment屬性都應該設為true;而配置并不負責創建主機和配件,只是從庫存中現有的主機和配件拖放過來的,那么它下面的主機,配件EReference的Containment屬性都應該為false。
以XMI存儲為例,修改后存儲的格式應該為:
<?xml version="1.0" encoding="UTF-8"?>
<mobiles:庫存 xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:mobiles="http:///mobiles.ecore"> <配置 配件="http://@配件.0" 主機="http://@主機.0"/> <配件> <功能/> </配件> <主機> <功能/> </主機> </mobiles:庫存> |
Genmodel文件主要維護著一些與生成代碼相關的設置,比如說,某個屬性可不可以修改。大部分的屬性都不用修改,我們要決定的仍然是EReference的屬性:Children,Create Child,Notify。
Notify是表明它應不應該將在這個包含節點中有關自身的改變通知給其它的相同節點。比如某個主機V3在庫存中創建之后被拖到某個配置里,這樣在整個樹上就存在兩個V3:一個是V3本身,另一個是它的一個引用。如果將配置中主機的Notify屬性設為true后,改變配置中引用的V3價格,它就會把這個改變通知給V3自身,實現同步。通常情況下應將所有的EReference的Notify設為true。
按照下表做出設置:
包含節點 EReference Children Create Child Notify
庫存 主機 配件 配置 True True True 主機 功能 True True True 主機 配件 True False True 配件 功能 True True True 配置 主機 配件 True False True |
生成代碼并運行
用EMF generator(缺省的Eidtor)打開genmodel文件,原來的工程中會生成Model代碼,兩個新的工程Edit和Editor工程會被生成, Edit工程包含了一些方便編輯Model的代碼,Eidtor工程中大都是UI部分的代碼,不在本文范圍之內。
.mobiles文件生成后,將會用Mobiles Model Editor打開。現在就可以在庫存中添加主機配件等,并將其拖到某個配置中去。這里所謂的Mobiles在缺省情況下與你的Rose文件的名字相同,在Editor工程的Plugin.xml的editor擴展中可以看到生成的后綴。
EMF是從作為MOF規范在Eclipse的一個實現開始的,隨后通過大量的運用在工具的實現,EMF成為一個有效的MOF API的一個核心子集的Java實現。EMF中的元數據被稱為Ecore。
灰色的類代表抽象類,所有的類都繼承自Eobject。EPackage包含關于模型類(EClass)和數據類型(EDataType)的信息。EClass描述一個建模的類,并且指定屬性和參考以描述實例的數據。EAttribute描述簡單數據,它由一個EDataType來指定。EReference描述一個類之間的關聯;它的類型是一個Eclass。EFactory包含創建模型元素的方法。更多的關于Ecore的描述請參考Eclipse官方網站。
包和工廠
前面說過EPackage包含所有關于EClass和EDataType的信息,參看生成的代碼mobiles. MobilesPackage,可以看到Eclass的feature ID聲明:
int 主機 = 0;
int 配置 = 1;
feature ID僅僅是對所有的類元素(不包含類型信息)的一個int類型的編號,有了它可以使你很快的區分出是哪個類的哪個屬性。例如在某個類的屬性值發生改變后,它的監聽者會收到一個通知,通知里就包含了改變了的屬性的feature ID,這時你就可以簡單的通過一個switch方法來實現分派。
MobilesPackage中另外的一些是關于類型的,例如:
EClass get主機();
EAttribute get主機_Name();
在某些情況下這些元數據是非常重要的,比如在決定某個屬性的編輯框類型的時候。當然你也可以直接調用某個類型的create方法來創建對象:
適配器
EMF最重要的一個特性就是它對Adapter的定義。在EMF中Adapter有兩個功能,第一個是類似于Observer的功能,它可以監聽目標對象的變化然后做出相應的反映;另一個是通過它可以使目標對象不用繼承某個接口或者父類來實現其特有的功能。這一節我們會結合Model Plug_in的代碼來探討Adapter的這兩個功能。
Observer
通常的Observer模式需要至少兩個類,監聽者和目標對象。EMF中,監聽者稱為Adapter,目標對象稱為Notifier。每個Notifier都擁有一個eAdapters列表,維護著所有監聽者的類型,一旦Notifier發生變化,它會遍歷eAdapters列表將變化通知給列表中的每一個Adapter。所以假如某個Adapter想要監聽這個Notifier,只需要將自己添加到Notifier的eAdapters中。
EMF中所有的對象都直接或間接繼承自Eobject,如同java中所有的類都繼承自Object一樣。而Eobject類已經實現了Notifier接口,所以所有的EMF的類都可以是Notifier。
Adapter需要自己來實現AdapterImpl,需要實現的方法主要有兩個,isAdapterForType和notifyChanged。IsAdapterForType是用來判斷這個Adapter能不能監聽某個Notifier,notifyChanged包含著一旦Notifier發生了變化這個Adapter所要做出的所有相應。假設我們需要一個Adapter,它來監聽主機的變化,一旦任何關于主機的增刪改操作產生就將改變記錄在本地文件中。
Notification類是由Notifier發出來的,它包含著發出者getNotifier(),改變之前的舊值getOldValue()和改變后的新值getNewValue()。象如下示例這樣使用這個Adapter:
主機 machine= MobilesFactory. EINSTANCE. create主機();
主機Adapter adapter = new主機Adapter();
machine.eAdatpters().add(adapter);
machine.setName(“V3”);
你要自己實例化Adapter,并且把它添加到Notifier的eAdaters里去。假如在其它的某個地方你有同樣的需求,但你不確定“主機Adapter”是否已經被添加到“主機”的eAdatpters里了,你就需要遍歷現有的adapter,來判斷是否已被添加。然后決定創不創建新的“主機Adapter”。事實上EMF提供了AdapterFactory來簡化你的工作。可以參照生成的代碼mobiles.util. MobilesAdapterFactory.java。它只是一個框架,用來根據Notifier的類型生成相應的Adatpter并將其注冊到Notifier的eAdapters列表中去。它還借用了MobilesSwitch類的分派功能。
MobilesSwitch只做了一件事情,那就是根據Notifier的類型調用不同的case*方法。例如,如果Notifier是“主機”,它會去調用case主機()方法。MobilesAdapterFactory中則實現了case主機()方法,調用create主機Adapter()來創建“主機Adapter”。
繼續上面的例子,我們實現一個AdapterFactory:
public class MobilesAdapterFactoryImpl extends MobilesAdapterFactory{
public static MobilesAdapterFactoryImpl INSTANCE = new MobilesAdapterFactoryImpl (); public Adapter create主機Adapter() { return new主機Adapter(); } } |
用的時候就很簡單:
主機 machine= MobilesFactory. EINSTANCE. create主機();
MobilesAdapterFactoryImpl.INSTANCE.adapt(machine, 主機Adapter.class); machine.setName(“V3”); |
factory會遍歷machine的eAdapters,如果沒有“主機Adapter”則創建一個添加進去,否則返回已有的。
行為擴展
我們把日志功能提取出來,成為一個類:
public interface Logger{
public void log(); } |
其實我們希望的是“主機”也能有日志功能,可以在自己改變之后將變化記錄下來。以往我們要做的就是讓“主機”繼承Logger,然后實現log()方法記錄下自己這種特定類型的信息。但有的時候通過繼承來擴展行為并不是那么舒服,比如繼承的關系是死的,你不能在你不需要的時候去掉這些擴展行為。通過Adapter你可以隨心所欲的控制這些擴展行為。
public class 主機Adapter extends AdapterImpl implements Logger
public boolean isAdapterForType(Object type) {
|
用的時候同上邊一樣:
主機 machine= MobilesFactory. EINSTANCE. create主機();
MobilesAdapterFactoryImpl.INSTANCE.adapt(machine, 主機Adapter.class); machine.setName(“V3”); |
這個時候任何“主機”的變化都會引起“主機Adapter”執行擴展了的log()方法,這一點與AOP很像。
Model Class
我們現在把目光關注到Model Plug_in中最重要的部分。首先是Model的接口,例如接口“主機”,它的定義非常簡單,就是些屬性的get,set方法。get方法很簡單,而set方法則比較有趣:
public void setName(String newName) {
String oldName = name; name = newName; if (eNotificationRequired()) eNotify(new ENotificationImpl(this, Notification.SET, MobilesPackage.主機__NAME, oldName, name)); } |
它不僅僅將類變量賦予了新值,還在需要Notify的時候發出了一個Notification。如同上一節所講的,這個Notification將包含發出者,改變的方式,改變的屬性標識(feature ID)、舊值和新值。
隨后是一些e開頭的方法,他們都是用作反射的。例如eGet,根據feature ID返回相應的屬性值;eSet,根據feature ID設置相應的屬性值;eUnset,根據feature ID設置相應的屬性值為缺省值;eIsSet,根據feature ID判斷相應的屬性是否被設置過;
限于篇幅的關系,EMF中還有很多高級的功能沒有在本文中介紹,比如持久化,Command pattern等;Edit工程里更是包含了集萬千寵愛于一身的ItemProvider,它們都非常重要,希望以后有機會能與讀者共同探討
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
