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