|
以下文章出自www.。本人僅作中文翻譯,未作任何其它修改。
EMF概述 文檔原出處:http:///emf/docs.php?doc=references/overview/EMF.html 最后修改時(shí)間: 2005年6月16日
本文為EMF及其代碼生成模式提供了一個(gè)基本的概述。要了解EMF所有功能的詳細(xì)描述,請參見《Eclipse Modeling Framework》(Addison Wesley, 2003),或者框架自身的Javadoc文檔。 概論 EMF是一個(gè)Java框架與代碼生成機(jī)制,用來構(gòu)建基于結(jié)構(gòu)化模型的工具或其它應(yīng)用系統(tǒng)。它們可以帶給你面向?qū)ο蠼5乃枷耄?/SPAN>EMF幫助你快速地將你的模型轉(zhuǎn)變?yōu)楦咝У?、正確的、以及易用的定制Java代碼,只需要一個(gè)很低的入門成本,EMF就可以為你提供這些益處。 那么,當(dāng)我說“模型”時(shí)到底意味著什么?當(dāng)談?wù)撃P蜁r(shí),我們一般都會(huì)想到類圖、協(xié)作圖、狀態(tài)類,等等。UML為這些圖定義了標(biāo)準(zhǔn)的符號(hào)。聯(lián)合使用各種UML圖,可以詳細(xì)說明一個(gè)應(yīng)用系統(tǒng)的完整模型。這個(gè)模型可能純粹只用作文檔,或者通過適當(dāng)?shù)墓ぞ撸梢员挥脕碜鳛檩斎雰?nèi)容生成一部分或全部應(yīng)用系統(tǒng)代碼。 要能夠做到這些的建模工作一般都需要昂貴的面向?qū)ο蟮姆治雠c設(shè)計(jì)工具(OOA/D),你可能對我們的結(jié)論有疑問,但是,EMF提供了一個(gè)低成本的入口。我們說這個(gè)的理由是一個(gè)EMF模型只需要你擁有在UML中建模所需知識(shí)的一小部分即可,主要是簡單的類的定義,以及它們的屬性與關(guān)系,針對這些,不需要使用一個(gè)全尺寸的圖形化建模工具。 EMF使用XMI作為模型定義的規(guī)范形式,你有多種方法可以得到這種格式的模型: · 直接使用XML或文本編輯器來創(chuàng)建XMI文檔。 · 從建模工具,如Rose,中導(dǎo)出XMI文檔。 · 帶有模型屬性的注解Java接口。 · 描述模型序列化格式的XML Schema。 第一種方法最直接,但一般只對XML高手有吸引力。若你已經(jīng)使用了全尺寸的建模工具,則第二種方法是最可取的。第三種方法只需要有一個(gè)基本的Java開發(fā)環(huán)境就可以低成本地?fù)碛?/SPAN>EMF帶來的好處、以及它的代碼生成能力。在創(chuàng)建一個(gè)需要讀寫特定的XML文件格式的應(yīng)用系統(tǒng)時(shí),最后一種方法最適合。 一旦你指定一個(gè)EMF模型,EMF生成器就可以創(chuàng)建一個(gè)一致的Java實(shí)現(xiàn)類的集合,你可以編輯生成的類來添加方法與實(shí)例變量,只要需要還可以重新從模型中生成代碼:你添加的部分在重新生成過程中都將被保留。若你添加的代碼依賴于你在模型中修改的某些東西,你還需要更新代碼來反映這些改變,其它情況下,你的代碼是完全不受模型修改與重新生成的影響的。 另外,通過以下方法,就可以簡單地提高你的生產(chǎn)力:使用EMF提供的幾個(gè)其它的益處,如模型變動(dòng)通知、持久化支持(包括默認(rèn)的XMI、以及基于Schema的XML序列化),模型校驗(yàn)框架,以及非常有效的用來操縱EMF對象的反射API。最重要的是,EMF提供了與其它基于EMF的工具或應(yīng)用進(jìn)行互操作的基礎(chǔ)。 EMF包括兩個(gè)基本的框架,core框架與EMF.Edit。core框架通過為模型創(chuàng)建實(shí)現(xiàn)類,提供基本的代碼生成與運(yùn)行時(shí)支持。EMF.Edit基于core構(gòu)建并進(jìn)行了擴(kuò)展,添加了對生成適配器類的支持,可以支持視圖以及基于命令的(可以undo的)模型編輯操作。下面的章節(jié)描述core框架的主要功能。EMF.Edit將在另一篇文章中進(jìn)行描述“The EMF.Edit Framework Overview”。指南“Generatin an EMF Model”詳細(xì)介紹了如何運(yùn)行EMF與EMF.Edit生成器。
EMF與OMG MOF的關(guān)系 如果你已經(jīng)熟悉OMG的MOF,你肯定會(huì)困惑于EMF倒底與MOF有什么關(guān)系。實(shí)際上,EMF就是從作為MOF規(guī)范的一個(gè)實(shí)現(xiàn)開始的,通過實(shí)現(xiàn)大量使用EMF的工具積累的經(jīng)驗(yàn),我們又對它進(jìn)行了發(fā)展。EMF可以被看作對于MOF的部分核心API的一個(gè)高效的Java實(shí)現(xiàn)。然而,為避免任何混淆,與MOF核心元模型類似的部分在EMF中稱為Ecore。 在MOF2.0計(jì)劃中,MOF模型的一個(gè)類似子集,稱為(EMOF,Essential MOF),也被分離了出來。在Ecore與EMOF間只存在微小的,大部分是命名上的區(qū)別;無論如何,EMF都可以透明地讀寫EMOF的序列化存儲(chǔ)。
定義一個(gè)EMF模型 為了有助于描述EMF,我們假定擁有一個(gè)簡單的、只包含一個(gè)類的模型,如下圖:
模型中展示了一個(gè)擁有兩個(gè)屬性的類:String類型的title,int類型的pages。 我們的如上圖這么簡單的模型定義,可以通過幾種不同的方式提供給EMF代碼生成器。 UML 若你擁有與EMF一起工作的建模工具,你可以簡單地畫出如上圖所示的模型。 XMI 另外,我們可以直接用XMI文檔來描述這個(gè)模型,就像下面所示: <ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www./XMI" xmlns:xsi="http://www./2001/XMLSchema-instance" xmlns:ecore="http://www./emf/2002/Ecore" name="library "nsURI="http:///library.ecore" nsPrefix="library"> <eClassifiers xsi:type="ecore:EClass" name="Book"> <eStructuralFeatures xsi:type="ecore:EAttribute" name="title" eType="ecore:EDataType http://www./emf/2002/Ecore#//EString"/> <eStructuralFeatures xsi:type="ecore:EAttribute" name="pages" eType="ecore:EDataType http://www./emf/2002/Ecore#//EInt"/> </eClassifiers> </ecore:EPackage>
XMI文檔包含了類圖中的所有信息.。在圖中的每個(gè)類與屬性都在XMI文檔中有一個(gè)對應(yīng)的類或?qū)傩远x。 Annotated Java 如果,你沒有圖形化建模工具,也沒有興趣手工輸入所有的XMI語句,這是第三個(gè)描述你的模型的選項(xiàng)。因?yàn)?/SPAN>EMF生成器是一個(gè)可以并入代碼的生成器,通過提供部分Java接口(將模型信息通過注解提供),生成器可以將接口作為生成模型的元數(shù)據(jù),并將其它的代碼生成到最終實(shí)現(xiàn)代碼中。 我們可以這樣定義Book類: /** * @model */ public interface Book { ** * @model */ String getTitle();
/** * @model */ int getPages(); } 通過這種方法,用Java接口中標(biāo)準(zhǔn)的get方法來標(biāo)注屬性與引用的形式,我們提供了模型的所有信息。@model標(biāo)簽用來向代碼生成器表示那些接口,以及接口的那些部分對應(yīng)到模型元素,并需要進(jìn)行相應(yīng)的代碼生成。 對于我們的簡單示例,所有模型信息都確實(shí)通過對接口進(jìn)行Java反省操作而提供,所以不再需要任何其它附加的模型信息。在一般情況下,還可以在@model標(biāo)簽后添加模型元素的附加詳細(xì)信息。針對此示例,若我們希望pages屬性是只讀的(這意味著不生成setter方法),我們就可以向注解中加入信息:
/** * @model changeable="false" */ int getPages(); 因?yàn)橹挥信c默認(rèn)值不一致的信息才需要被明確指定,所以注解可以保持簡潔明了。
XML Schema 有時(shí)候,你可以通過XML Schema描述一個(gè)模型的實(shí)例序列化后看起來應(yīng)該怎樣來定義一個(gè)模型。這對于編寫一個(gè)使用XML來整合現(xiàn)存的應(yīng)用系統(tǒng)或遵循某個(gè)標(biāo)準(zhǔn)的的應(yīng)用系統(tǒng)是很有幫助的。以下就是與我們的簡單模型等值的Schema文檔: <xsd:schema targetNamespace="http:///library.ecore" xmlns="http:///library.ecore" lns:xsd="http://www./2001/XMLSchema"> <xsd:complexType name="Book"> <xsd:sequence> <xsd:element name="title" type="xsd:string"/> <xsd:element name="pages" type="xsd:integer"/> </xsd:sequence> </xsd:complexType> </xsd:schema> 這種方法與另外三種有些不同,主要因?yàn)?/SPAN>EMF在最后的使用中,必然會(huì)加上某種序列化的約束,來確保與schema的一致性。作為結(jié)果,從Schema中創(chuàng)建的模型看起來就與使用其它方法生成的模型不一樣。這些區(qū)別的細(xì)節(jié)將在概述后的章節(jié)中討論。 在本文的剩余部分,為保持簡潔明了,我們將統(tǒng)一使用UML圖。所有我們所闡述的建模要領(lǐng)都可以用注解Java接口或直接使用XMI來表示,其中大部分也都存在有等價(jià)的XML Schema。不管提供了什么信息,對于EMF的代碼生成來說都是一樣的。
生成Java實(shí)現(xiàn) 對于模型中的每個(gè)類,都將會(huì)生成一個(gè)Java接口以及對應(yīng)的實(shí)現(xiàn)類。在我們例子中,為Book生成的接口如下: public interface Book extends EObject { String getTitle(); void setTitle(String value);
int getPages(); void setPages(int value); } 每個(gè)生成的接口包含針對每個(gè)屬性的getter與setter方法。 Book接口擴(kuò)展了基礎(chǔ)接口EObject。EObject是在EMF中等價(jià)于java.lang.Object的對象,也就是說,它是所有EMF類的基類。EObject以及它的實(shí)現(xiàn)類EObjectImpl(我們將在稍后討論)提供了一些基礎(chǔ)的輕量級的實(shí)現(xiàn),來為Book引入EMF的通知與持久框架。在我們開始討論EObject到底往混合中帶來了什么之前,讓我們繼續(xù)來看一看EMF如何生成Book。 每個(gè)生成的實(shí)現(xiàn)類都包含定義在對應(yīng)的接口中的getter與setter方法的實(shí)現(xiàn),還加上其它一些EMF框架所需的方法。 類BookImpl將會(huì)包含有title與pages屬性的存取實(shí)現(xiàn)。pages屬性,如例子,擁有如下生成的實(shí)現(xiàn)代碼: public class BookImpl extends EObjectImpl implements Book { ... protected static final int PAGES_EDEFAULT = 0; protected int pages = PAGES_EDEFAULT;
public int getPages() { return pages; }
public void setPages(int newPages) { int oldPages = pages; pages = newPages; if (eNotificationRequired()) eNotify(new ENotificationImpl(this, Notification.SET, ..., oldPages, pages)); } ... }
生成的get方法提供了理想的效率。它簡單地返回表示屬性的一個(gè)實(shí)例變量。 set方法,雖然有點(diǎn)復(fù)雜,但還是相當(dāng)高效。在設(shè)置pages實(shí)例變量的值之外,set方法還需要向可能的觀察者發(fā)送改變通知,這些觀察者通過eNotity()來監(jiān)聽對象。在沒有觀察者時(shí)(例如,在一個(gè)批處理中),情況就可以得到優(yōu)化,通知對象的構(gòu)造、以及通知方法的調(diào)用,都由eNotificationRequired()方法來進(jìn)行監(jiān)控。eNotificationRequired()方法默認(rèn)的實(shí)現(xiàn)只是簡單地檢查是否存在與對象相關(guān)的觀察者(或適配器)。因此,若在沒有觀察者的情況下使用EMF對象,對eNotificationRequired的調(diào)用與一個(gè)null指針檢查相當(dāng),它可以在JIT編譯器中進(jìn)行Inline處理。 對于其它類型的屬性自動(dòng)生成的訪問方式,像String類型的title屬性,有一些不同,但基本與上面列出的代碼類似。 對于引用屬性生成的訪問器,特別是雙向引用,就變得有點(diǎn)復(fù)雜。 單向引用 讓我們用另一個(gè)與Book類有關(guān)聯(lián)關(guān)系的類Writer來擴(kuò)充示例模型。
在Book與Writer間的關(guān)聯(lián)關(guān)系是,在本例中,一個(gè)單向的引用。從Book存取Writer的引用(角色)名字為author。 在EMF生成器中運(yùn)行此模型,將會(huì)再生成一個(gè)新的接口Writer與實(shí)現(xiàn)類WriterImpl,并在Book接口中添加新的getter與setter方法。 Writer getAuthor(); void setAuthor(Writer value); 因?yàn)?/SPAN>author引用是單向的,所以setAuthor()方法看起來還是一個(gè)簡單的數(shù)據(jù)設(shè)置,與前面的setPages()類似: public void setAuthor(Writer newAuthor) { Writer oldAuthor = author; author = newAuthor; if(eNotificationRequired()) eNotify(new ENotificationImpl(this, ...)); } 其中唯一的區(qū)別就是我們用一個(gè)對象指針代替了簡單數(shù)據(jù)類型的字段。 因?yàn)槲覀冊谔幚硪粋€(gè)對象引用,所以,getAuther()方法會(huì)復(fù)雜一點(diǎn)。因?yàn)閷τ谝恍╊愋偷囊?,包?/SPAN>author類型,需要處理這種可能性:即被引用的對象(在本例中是Writer)可能被持久化在與源對象(在本例中是Book)不同的資源(文檔)中。因?yàn)?/SPAN>EMF的持久化框架使用懶惰加裁模式,一個(gè)對象指針(在本例中是author)在某一時(shí)間點(diǎn)可能只是對象的一個(gè)代理,而不是真正的引用此對象。綜上所述,getAuthor()方法如下所示: public Writer getAuthor() { if (author != null && author.eIsProxy()) { Writer oldAuthor = author; author = (Writer)eResolveProxy((InternalEObject)author); if (author != oldAuthor) { if (eNotificationRequired()) eNotify(new ENotificationImpl(this, Notification.RESOLVE, ...)); } } return author; } 代替簡單地返回author實(shí)例變量,我們首先調(diào)用了從框架中繼承來的方法eIsProxy()來檢查引用是否是一個(gè)代理,若是,再調(diào)用eResolveProxy()來處理它。后者將會(huì)調(diào)用EcoreUtil.resolve(),一個(gè)靜態(tài)工具方法來試圖加載目標(biāo)對象的文檔,并使用代理URI來構(gòu)建此對象。若成功,它將返回被解析的對象,若沒有成功載入文檔,則它還將再次返回代理。 雙向引用 現(xiàn)在我們理解了代理解析對某些類型的引用的getter方式所起的影響,現(xiàn)在我們再來看一看雙向關(guān)聯(lián)關(guān)系如何影響setter方式。將單向關(guān)聯(lián)改為如下:
現(xiàn)在關(guān)聯(lián)是雙向的了,通過不帶箭頭的關(guān)聯(lián)線來進(jìn)行表示。從Write訪問Book的角色名為books。 當(dāng)我們?yōu)槟P椭匦律纱a時(shí),getAuthor()方法不會(huì)有改變,但setAuthor()將變?yōu)槿缦滤荆?/SPAN> public void setAuthor(Writer newAuthor) { if (newAuthor != author) { NotificationChain msgs = null; if (author != null) msgs = ((InternalEObject)author).eInverseRemove(this, ..., msgs); if (newAuthor != null) msgs = ((InternalEObject)newAuthor).eInverseAdd(this, ..., msgs); msgs = basicSetAuthor(newAuthor, msgs); if (msgs != null) msgs.dispatch(); } else if (eNotificationRequired()) eNotify(new ENotificationImpl(this, ...)); // send "touch" notification } 我們可以看到,當(dāng)設(shè)置一個(gè)雙向引用,如author,時(shí),引用的另一端必須也同時(shí)被設(shè)置(通過調(diào)用eInverseAdd())。而且,我們也需要為另一端移除原先的author(通過調(diào)用eInverseRemove()),因?yàn)樵谀P椭校?/SPAN>author引用是單一的(也就是說,一本書只能有一個(gè)作者),Book不能擁有超過一個(gè)Writer引用。最后,我們通過調(diào)用另一個(gè)生成的方法basicSetAuthor()來設(shè)置新的author引用。此方法如下: public NotificationChain basicSetAuthor(Writer newAuthor, NotificationChain msgs) { Writer oldAuthor = author; author = newAuthor; if (eNotificationRequired()) { ENotificationImpl notification = new ENotificationImpl(this, ...); if (msgs == null) msgs = notification; else msgs.add(notification); } return msgs; } 這個(gè)方法與單向引用的set方法非常類似,除非msgs參數(shù)不為空,將會(huì)把notification加入到其中,代替原來直接觸發(fā)此通知消息的做法。因?yàn)樵陔p向引用的set操作中發(fā)生的正向的/反向的添加、以及移除操作,會(huì)有四個(gè)(在本例中是三個(gè))不同的通知消息被生成。NotificationChain被用來集中所有這些單個(gè)的消息,直到所有狀態(tài)改變都完成后再來觸發(fā)它們。隊(duì)列中的消息通過調(diào)用msgs.dispatch()進(jìn)行發(fā)送。 多值引用 可以注意到在示例中books關(guān)聯(lián)(從Writer到Book)是多值關(guān)聯(lián)(0..*)。換句話說,一個(gè)作者可能寫有多本書。EMF中的多值引用(上界大于1的引用)使用集合API來處理,所以在接口中只生成了getter方法: public interface Writer extends EObject { ... EList getBooks(); } 注意到getBooks()返回一個(gè)替代java.util.List的EList。實(shí)際上,它們倆基本相同。EList是java.util.List在EMF中的子類,并在API中加入了兩個(gè)move方法。除此之外,從客戶端視角,你可以認(rèn)為它就是一個(gè)JavaList。如,向books關(guān)聯(lián)中添加一本書,只需簡單地調(diào)用: aWriter.getBooks().add(aBook); 或者,通過迭代器你可以如下所示做其它的事情: for (Iterator iter = aWriter.getBooks().iterator(); iter.hasNext(); ) { Book book = (Book)iter.next(); ... } 如上面所示,從客房端視角,操縱多值引用沒有任何特殊之處。然而,因?yàn)?/SPAN>books引用是雙向引用的一部分(它是Book.author的另一端),我們還是要做如同在setAuthor()方法中所示的所有相對的握手處理。從WriterImpl中的getBooks()方法的實(shí)現(xiàn)代碼可看到如何處理多值引用的情況: public EList getBooks() { if (books == null) { books = new EObjectWithInverseResolvingEList(Book.class, this, LibraryPackage.WRITER__BOOKS, LibraryPackage.BOOK__AUTHOR); } return books; } getBooks()方法返回一個(gè)特殊的實(shí)現(xiàn)類EObjectWithInverseResolvingEList,通過向它提供為在添加或移除操作時(shí)順利完成相對的握手處理所需的信息來構(gòu)造它的實(shí)例。EMF實(shí)際上提供了20種不同的特殊的EList實(shí)現(xiàn),來高效地實(shí)現(xiàn)所有類型的多值屬性。對于單向關(guān)聯(lián)關(guān)系(也就是說,沒有反向)我們可以使用EObjectResolvingElist。若引用操作不需要代理解析,我們可以使用EObjectWithInverseEList或者EObjectEList等等。 所以對于我們的例子,實(shí)現(xiàn)對bookd引用的list對象使用參數(shù)LibraryPackage.BOOK_AUTHOR來進(jìn)行創(chuàng)建(一個(gè)自動(dòng)生成的靜態(tài)int常量,表示相對的特性)。此參數(shù)在調(diào)用add()方法中對Book的eInverseAdd()方法進(jìn)行調(diào)用時(shí)使用,類似于在Book的setAuthor()期間對Writer的eInverseAdd()調(diào)用的方式。下面是在BookImpl中eInverseAdd() 方法的實(shí)現(xiàn): public NotificationChain eInverseAdd(InternalEObject otherEnd, int featureID, Class baseClass, NotificationChain msgs) { if (featureID >= 0) { switch (eDerivedStructuralFeatureID(featureID, baseClass)) { case LibraryPackage.BOOK__AUTHOR: if (author != null) msgs = ((InternalEObject)author).eInverseRemove(this, .., msgs); return basicSetAuthor((Writer)otherEnd, msgs); default: ... } } ... } 它首先調(diào)用eInverseRemove()方法來移除任何原先的作者(如同Book的setAuthor()方法),然后,它調(diào)用basicSetAuthor()來實(shí)際設(shè)置引用。雖然在我們的例子中,只有一個(gè)雙向引用,在eInverseAdd()中使用了switch結(jié)構(gòu)可以對Book類的所有雙向引用進(jìn)行處理。 容器引用(復(fù)合聚合) 讓我們再增加一個(gè)新類,Library,把它作為Book的容器。
容器引用通過在Library這端使用黑心菱形的箭頭線來表示。此關(guān)聯(lián)關(guān)系表示一個(gè)Library聚合,可以有0或更多本書。值聚合(包容)關(guān)聯(lián)關(guān)系是特別重要的因?yàn)樗硎玖艘粋€(gè)目標(biāo)實(shí)例的父實(shí)例,或者稱為擁有者,它也指明了在持久化處理時(shí)對象的物理位置。 容器從多個(gè)方面影響代碼生成。首先,因?yàn)楸话莸膶ο蟊WC與它的容器處于同一資源中,就不再需要代理解析。因而,在LibraryImpl中生成的get方法將使用一個(gè)不需要解析的EList實(shí)現(xiàn)類: public EList getBooks() { if (books == null) { books = new EObjectContainmentEList(Book.class, this, ...); } return books; } 因?yàn)椴恍枰瓿纱斫馕?,一個(gè)EObjectContainmentEList可以非常高效地實(shí)現(xiàn)contains()操作(就是說,與普通的線性增長的耗時(shí),可在一個(gè)常量時(shí)間內(nèi)完成操作)。這是非常重要的,因?yàn)樵?/SPAN>EMF引用列表中,不允許進(jìn)行復(fù)制項(xiàng)目的操作,所以在add()操作中會(huì)調(diào)用contains()方法。 因?yàn)橐粋€(gè)對象只能有一個(gè)容器,添加一個(gè)對象到容器關(guān)聯(lián)中意味著必須將它從現(xiàn)在所處的容器中移出。例如,添加一本book到Library的books列表中將包括將此book從其它Library的books列表中移除的操作。它與那些相對端的重?cái)?shù)是1的雙向關(guān)聯(lián)關(guān)系沒有任何不同。讓我們假定,若Writer類對于Book也擁有一個(gè)容器關(guān)聯(lián),稱為ownedBooks。這時(shí),一個(gè)給定的處于某個(gè)Writer的ownedBooks列表中的book實(shí)例,當(dāng)它被加到一個(gè)Library的books引用時(shí),它也將被從Writer的列表中移除。 要高效地實(shí)現(xiàn)此種操作,基類EObjectImol擁有一個(gè)EObject類型的實(shí)例變量(eContainer),用來存儲(chǔ)包容它的容器。作為結(jié)果,一個(gè)容器引用隱含地一定是雙向引用。要從Book訪問Library,你可以寫如下代碼: EObject container = book.eContainer(); if (container instanceof Library) library = (Library)container; 若你想要避免向下造型,你可以將關(guān)聯(lián)明確地改為雙向:
并讓EMF來為你生成一個(gè)良好的類型安全的get方法: public Library getLibrary() { if (eContainerFeatureID != LibraryPackage.BOOK__LIBRARY) return null; return (Library)eContainer; } 注意到,在明確的get方法中使用從EObjectImpl中繼承的eContainer變量,來代替像前面例子中一樣生成一個(gè)實(shí)例變量。 枚舉屬性 到目前為止,我們已經(jīng)看了EMF如何處理簡單屬性,以及各種類型的引用。另一種公共的屬性類型是枚舉。枚舉屬性使用Java類型安全的enum模式來實(shí)現(xiàn)。 我們增加一個(gè)枚舉屬性,category,到Book類:
重新生成實(shí)現(xiàn)類,Book接口現(xiàn)在包括針對category的getter與setter。 BookCategory getCategory(); void setCategory(BookCategory value); 在生成的接口中,category方法使用了類型安全的枚舉類—BookCategory。它為枚舉值定義了靜態(tài)常量,以及其它的方法,如下: public final class BookCategory extends AbstractEnumerator { public static final int MYSTERY = 0; public static final int SCIENCE_FICTION = 1; public static final int BIOGRAPHY = 2;
public static final BookCategory MYSTERY_LITERAL = new BookCategory(MYSTERY, "Mystery"); public static final BookCategory SCIENCE_FICTION_LITERAL = new BookCategory(SCIENCE_FICTION, "ScienceFiction"); public static final BookCategory BIOGRAPHY_LITERAL = new BookCategory(BIOGRAPHY, "Biography");
public static final List VALUES = Collections.unmodifiableList(...));
public static BookCategory get(String name) { ... }
public static BookCategory get(int value) { ... }
private BookCategory(int value, String name) { super(value, name); } } 以下略…,因?yàn)樵?/SPAN>JDK5.0中,已提供了類似的Enum實(shí)現(xiàn)。要了解上面的代碼,請參見相關(guān)的文檔。
工廠與包 在模型接口與實(shí)現(xiàn)類之外,EMF還至少生成兩個(gè)接口(以及它們的實(shí)現(xiàn)類):一個(gè)工廠與一個(gè)包。 工廠,如同名字的意思,用來創(chuàng)建模型中類的實(shí)例,而包則提供一些靜態(tài)變量(如,生成的方法所使用的特性常量)以及便利方法來存取你模型的元數(shù)據(jù)。 下面是book示例中的工廠接口: public interface LibraryFactory extends EFactory { LibraryFactory eINSTANCE = new LibraryFactoryImpl(); Book createBook(); Writer createWriter(); Library createLibrary();
LibraryPackage getLibraryPackage(); } 如上所示,生成的工廠為每模型中定義的類提供一個(gè)工廠方法(create),以及訪問模型包的方法,一個(gè)指向自身的靜態(tài)常量單值引用。 LibraryPackage接口提供了對模型元數(shù)據(jù)進(jìn)行方便的訪問: public interface LibraryPackage extends EPackage { ... LibraryPackage eINSTANCE = LibraryPackageImpl.init();
static final int BOOK = 0; static final int BOOK__TITLE = 0; static final int BOOK__PAGES = 1; static final int BOOK__CATEGORY = 2; static final int BOOK__AUTHOR = 3; ...
static final int WRITER = 1; static final int WRITER__NAME = 0; ...
EClass getBook(); EAttribute getBook_Title(); EAttribute getBook_Pages(); EAttribute getBook_Category(); EReference getBook_Author();
... } 如上所示,元數(shù)據(jù)通過兩種形式提供:int型常量,以及Ecore元對象。int常量提供了傳遞元信息的高效手段。你可以注意到生成的方法在實(shí)現(xiàn)中使用到了這些常量。稍后,當(dāng)我們察看如何實(shí)現(xiàn)EMF適配器時(shí),你將看到這些常量還為處理消息時(shí),判斷什么發(fā)生了改變時(shí),來提供高效的手段。還有,與工廠類似,生成的包接口,也提供了一個(gè)指向自身的單值引用。 生成擁有超類的類 讓我們在Book模型中來創(chuàng)建一個(gè)子類,SchoolBook,如下:
EMF代碼生成器會(huì)如你所愿地處理單一繼承,生成的接口擴(kuò)展了超類接口。 public interface SchoolBook extends Book 實(shí)現(xiàn)類也擴(kuò)展對應(yīng)的超實(shí)現(xiàn)類。 public class SchoolBookImpl extends BookImpl implements SchoolBook 在Java中,支持接口的多重繼承,但每個(gè)EMF實(shí)現(xiàn)類只能擴(kuò)展其中一個(gè)基類的實(shí)現(xiàn)類。因此,若模型中有多重繼承,我們需要決定將使用哪個(gè)類作為基類,其它的都只被當(dāng)成接口的合并,并在繼承后的實(shí)現(xiàn)類中提供所有的接口實(shí)現(xiàn)。 考慮如下圖所示的模型:
我們讓SchoolBook繼承兩個(gè)類:Book以及Asset。我們標(biāo)志Book類作為實(shí)現(xiàn)類的基類。若我們重新生成代碼,接口SchoolBook將會(huì)擴(kuò)展兩個(gè)接口: public interface SchoolBook extends Book, Asset 實(shí)現(xiàn)類也與前面相同,只是現(xiàn)在包括了從接口合并進(jìn)來的方法getValue()與方法setValue(): public class SchoolBookImpl extends BookImpl implements SchoolBook { public float getValue() { ... }
public void setValue(float newValue) { ... }
... } 定制生成的實(shí)現(xiàn)類 你可以向生成的Java類中添加行衛(wèi)(方法或?qū)嵗兞浚?,而不用?dān)心一旦模型發(fā)生變動(dòng)后重新生成代碼會(huì)搞丟你加入的東西。例如,我們向類Book加入一個(gè)方法,isRecommended()。只要在Java接口Book中簡單地加入新方法的聲明即可。 public interface Book ... { boolean isRecommended(); ... } 以及它們在實(shí)現(xiàn)類中的實(shí)現(xiàn): public boolean isRecommended() { return getAuthor().getName().equals("William Shakespeare"); } EMF生成器不會(huì)擦去這些修改,因?yàn)樗皇且粋€(gè)自動(dòng)生成的方法。每個(gè)EMF生成的方法都包括一個(gè)包含@generated標(biāo)簽的Javadoc注解,如下: /** * ... * @generated */ public String getTitle() { return title; } 不管怎樣重新生成代碼,EMF都不會(huì)去碰所有不包含此標(biāo)簽的方法(如isRecommended()方法)。實(shí)際上,若我們想要修改一個(gè)自動(dòng)生成的方法,我們可以從它的注解中移除@generated標(biāo)簽。 /** * ... * @generated // (removed) */ public String getTitle() { // our custom implementation ... } 現(xiàn)在,因?yàn)闆]有@generated標(biāo)簽,getTitle()方法被認(rèn)為了用戶的代碼;若我們重新生成代碼,生成器將會(huì)檢測到?jīng)_突,并簡單地放棄為此方法生成代碼。 實(shí)際上,在放棄一個(gè)生成的方法前,生成器首先檢查,在文件中是否存在另一個(gè)相同名字的,但加上Gen的,自動(dòng)生成的方法。若它找到一個(gè),則將會(huì)把生成的代碼重定向到這個(gè)方法中。例如,若我們想擴(kuò)展生成的getTitle()方法實(shí)現(xiàn),來代替完全地放棄它,我們可以簡單地改一下方法名字: /** * ... * @generated */ public String getTitleGen() { return title; } 并加入我們自己的覆蓋方法: public String getTitle() { String result = getTitleGen(); if (result == null) result = ... return result; } 現(xiàn)在我們重新生成代碼,生成器將會(huì)檢測到與我們自己的getTitle()有沖突,但因?yàn)樵陬愔写嬖趲?/SPAN>@generated標(biāo)簽的getTitleGen()方法,它將會(huì)重定向新生成的代碼到此方法中,而不是簡單地放棄生成新的代碼。 EMF模型上的操作 除了屬性與引用,你可以向模型中的類添加操作。若你這么做,則EMF生成器將會(huì)在接口中生成方法的聲明,在實(shí)現(xiàn)類中生成一個(gè)方法框架。EMF不對行衛(wèi)建模,所以實(shí)現(xiàn)要由用戶自己寫Java代碼來提供。 可以像上面說的,移除@generated標(biāo)簽,然后加入自己的代碼。另處,也可以將Java代碼包括在模型中間。在Rose上, |
|
|