以下文章出自www.eclipse.org。本人僅作中文翻譯,未作任何其它修改。
EMF概述
文檔原出處:http://eclipse.org/emf/docs.php?doc=references/overview/EMF.html
最后修改時(shí)間: 2005年6月16日
本文為EMF及其代碼生成模式提供了一個(gè)基本的概述。要了解EMF所有功能的詳細描述,請參見(jiàn)《Eclipse Modeling Framework》(Addison Wesley, 2003),或者框架自身的Javadoc文檔。
概論
EMF是一個(gè)Java框架與代碼生成機制,用來(lái)構建基于結構化模型的工具或其它應用系統。它們可以帶給你面向對象建模的思想,EMF幫助你快速地將你的模型轉變?yōu)楦咝У?、正確的、以及易用的定制Java代碼,只需要一個(gè)很低的入門(mén)成本,EMF就可以為你提供這些益處。
那么,當我說(shuō)“模型”時(shí)到底意味著(zhù)什么?當談?wù)撃P蜁r(shí),我們一般都會(huì )想到類(lèi)圖、協(xié)作圖、狀態(tài)類(lèi),等等。UML為這些圖定義了標準的符號。聯(lián)合使用各種UML圖,可以詳細說(shuō)明一個(gè)應用系統的完整模型。這個(gè)模型可能純粹只用作文檔,或者通過(guò)適當的工具,它可以被用來(lái)作為輸入內容生成一部分或全部應用系統代碼。
要能夠做到這些的建模工作一般都需要昂貴的面向對象的分析與設計工具(OOA/D),你可能對我們的結論有疑問(wèn),但是,EMF提供了一個(gè)低成本的入口。我們說(shuō)這個(gè)的理由是一個(gè)EMF模型只需要你擁有在UML中建模所需知識的一小部分即可,主要是簡(jiǎn)單的類(lèi)的定義,以及它們的屬性與關(guān)系,針對這些,不需要使用一個(gè)全尺寸的圖形化建模工具。
EMF使用XMI作為模型定義的規范形式,你有多種方法可以得到這種格式的模型:
· 直接使用XML或文本編輯器來(lái)創(chuàng )建XMI文檔。
· 從建模工具,如Rose,中導出XMI文檔。
· 帶有模型屬性的注解Java接口。
· 描述模型序列化格式的XML Schema。
第一種方法最直接,但一般只對XML高手有吸引力。若你已經(jīng)使用了全尺寸的建模工具,則第二種方法是最可取的。第三種方法只需要有一個(gè)基本的Java開(kāi)發(fā)環(huán)境就可以低成本地擁有EMF帶來(lái)的好處、以及它的代碼生成能力。在創(chuàng )建一個(gè)需要讀寫(xiě)特定的XML文件格式的應用系統時(shí),最后一種方法最適合。
一旦你指定一個(gè)EMF模型,EMF生成器就可以創(chuàng )建一個(gè)一致的Java實(shí)現類(lèi)的集合,你可以編輯生成的類(lèi)來(lái)添加方法與實(shí)例變量,只要需要還可以重新從模型中生成代碼:你添加的部分在重新生成過(guò)程中都將被保留。若你添加的代碼依賴(lài)于你在模型中修改的某些東西,你還需要更新代碼來(lái)反映這些改變,其它情況下,你的代碼是完全不受模型修改與重新生成的影響的。
另外,通過(guò)以下方法,就可以簡(jiǎn)單地提高你的生產(chǎn)力:使用EMF提供的幾個(gè)其它的益處,如模型變動(dòng)通知、持久化支持(包括默認的XMI、以及基于Schema的XML序列化),模型校驗框架,以及非常有效的用來(lái)操縱EMF對象的反射API。最重要的是,EMF提供了與其它基于EMF的工具或應用進(jìn)行互操作的基礎。
EMF包括兩個(gè)基本的框架,core框架與EMF.Edit。core框架通過(guò)為模型創(chuàng )建實(shí)現類(lèi),提供基本的代碼生成與運行時(shí)支持。EMF.Edit基于core構建并進(jìn)行了擴展,添加了對生成適配器類(lèi)的支持,可以支持視圖以及基于命令的(可以undo的)模型編輯操作。下面的章節描述core框架的主要功能。EMF.Edit將在另一篇文章中進(jìn)行描述“The EMF.Edit Framework Overview”。指南“Generatin an EMF Model”詳細介紹了如何運行EMF與EMF.Edit生成器。
EMF與OMG MOF的關(guān)系
如果你已經(jīng)熟悉OMG的MOF,你肯定會(huì )困惑于EMF倒底與MOF有什么關(guān)系。實(shí)際上,EMF就是從作為MOF規范的一個(gè)實(shí)現開(kāi)始的,通過(guò)實(shí)現大量使用EMF的工具積累的經(jīng)驗,我們又對它進(jìn)行了發(fā)展。EMF可以被看作對于MOF的部分核心API的一個(gè)高效的Java實(shí)現。然而,為避免任何混淆,與MOF核心元模型類(lèi)似的部分在EMF中稱(chēng)為Ecore。
在MOF2.0計劃中,MOF模型的一個(gè)類(lèi)似子集,稱(chēng)為(EMOF,Essential MOF),也被分離了出來(lái)。在Ecore與EMOF間只存在微小的,大部分是命名上的區別;無(wú)論如何,EMF都可以透明地讀寫(xiě)EMOF的序列化存儲。
定義一個(gè)EMF模型
為了有助于描述EMF,我們假定擁有一個(gè)簡(jiǎn)單的、只包含一個(gè)類(lèi)的模型,如下圖:

模型中展示了一個(gè)擁有兩個(gè)屬性的類(lèi):String類(lèi)型的title,int類(lèi)型的pages。
我們的如上圖這么簡(jiǎn)單的模型定義,可以通過(guò)幾種不同的方式提供給EMF代碼生成器。
UML
若你擁有與EMF一起工作的建模工具,你可以簡(jiǎn)單地畫(huà)出如上圖所示的模型。
XMI
另外,我們可以直接用XMI文檔來(lái)描述這個(gè)模型,就像下面所示:
<ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ecore="http://www.eclipse.org/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.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="pages" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"/>
</eClassifiers>
</ecore:EPackage>
XMI文檔包含了類(lèi)圖中的所有信息.。在圖中的每個(gè)類(lèi)與屬性都在XMI文檔中有一個(gè)對應的類(lèi)或屬性定義。
Annotated Java
如果,你沒(méi)有圖形化建模工具,也沒(méi)有興趣手工輸入所有的XMI語(yǔ)句,這是第三個(gè)描述你的模型的選項。因為EMF生成器是一個(gè)可以并入代碼的生成器,通過(guò)提供部分Java接口(將模型信息通過(guò)注解提供),生成器可以將接口作為生成模型的元數據,并將其它的代碼生成到最終實(shí)現代碼中。
我們可以這樣定義Book類(lèi):
/**
* @model
*/
public interface Book
{
**
* @model
*/
String getTitle();
/**
* @model
*/
int getPages();
}
通過(guò)這種方法,用Java接口中標準的get方法來(lái)標注屬性與引用的形式,我們提供了模型的所有信息。@model標簽用來(lái)向代碼生成器表示那些接口,以及接口的那些部分對應到模型元素,并需要進(jìn)行相應的代碼生成。
對于我們的簡(jiǎn)單示例,所有模型信息都確實(shí)通過(guò)對接口進(jìn)行Java反省操作而提供,所以不再需要任何其它附加的模型信息。在一般情況下,還可以在@model標簽后添加模型元素的附加詳細信息。針對此示例,若我們希望pages屬性是只讀的(這意味著(zhù)不生成setter方法),我們就可以向注解中加入信息:
/**
* @model changeable="false"
*/
int getPages();
因為只有與默認值不一致的信息才需要被明確指定,所以注解可以保持簡(jiǎn)潔明了。
XML Schema
有時(shí)候,你可以通過(guò)XML Schema描述一個(gè)模型的實(shí)例序列化后看起來(lái)應該怎樣來(lái)定義一個(gè)模型。這對于編寫(xiě)一個(gè)使用XML來(lái)整合現存的應用系統或遵循某個(gè)標準的的應用系統是很有幫助的。以下就是與我們的簡(jiǎn)單模型等值的Schema文檔:
<xsd:schema targetNamespace="http:///library.ecore" xmlns="http:///library.ecore" lns:xsd="http://www.w3.org/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>
這種方法與另外三種有些不同,主要因為EMF在最后的使用中,必然會(huì )加上某種序列化的約束,來(lái)確保與schema的一致性。作為結果,從Schema中創(chuàng )建的模型看起來(lái)就與使用其它方法生成的模型不一樣。這些區別的細節將在概述后的章節中討論。
在本文的剩余部分,為保持簡(jiǎn)潔明了,我們將統一使用UML圖。所有我們所闡述的建模要領(lǐng)都可以用注解Java接口或直接使用XMI來(lái)表示,其中大部分也都存在有等價(jià)的XML Schema。不管提供了什么信息,對于EMF的代碼生成來(lái)說(shuō)都是一樣的。
生成Java實(shí)現
對于模型中的每個(gè)類(lèi),都將會(huì )生成一個(gè)Java接口以及對應的實(shí)現類(lèi)。在我們例子中,為Book生成的接口如下:
public interface Book extends EObject
{
String getTitle();
void setTitle(String value);
int getPages();
void setPages(int value);
}
每個(gè)生成的接口包含針對每個(gè)屬性的getter與setter方法。
Book接口擴展了基礎接口EObject。EObject是在EMF中等價(jià)于java.lang.Object的對象,也就是說(shuō),它是所有EMF類(lèi)的基類(lèi)。EObject以及它的實(shí)現類(lèi)EObjectImpl(我們將在稍后討論)提供了一些基礎的輕量級的實(shí)現,來(lái)為Book引入EMF的通知與持久框架。在我們開(kāi)始討論EObject到底往混合中帶來(lái)了什么之前,讓我們繼續來(lái)看一看EMF如何生成Book。
每個(gè)生成的實(shí)現類(lèi)都包含定義在對應的接口中的getter與setter方法的實(shí)現,還加上其它一些EMF框架所需的方法。
類(lèi)BookImpl將會(huì )包含有title與pages屬性的存取實(shí)現。pages屬性,如例子,擁有如下生成的實(shí)現代碼:
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方法提供了理想的效率。它簡(jiǎn)單地返回表示屬性的一個(gè)實(shí)例變量。
set方法,雖然有點(diǎn)復雜,但還是相當高效。在設置pages實(shí)例變量的值之外,set方法還需要向可能的觀(guān)察者發(fā)送改變通知,這些觀(guān)察者通過(guò)eNotity()來(lái)監聽(tīng)對象。在沒(méi)有觀(guān)察者時(shí)(例如,在一個(gè)批處理中),情況就可以得到優(yōu)化,通知對象的構造、以及通知方法的調用,都由eNotificationRequired()方法來(lái)進(jìn)行監控。eNotificationRequired()方法默認的實(shí)現只是簡(jiǎn)單地檢查是否存在與對象相關(guān)的觀(guān)察者(或適配器)。因此,若在沒(méi)有觀(guān)察者的情況下使用EMF對象,對eNotificationRequired的調用與一個(gè)null指針檢查相當,它可以在JIT編譯器中進(jìn)行Inline處理。
對于其它類(lèi)型的屬性自動(dòng)生成的訪(fǎng)問(wèn)方式,像String類(lèi)型的title屬性,有一些不同,但基本與上面列出的代碼類(lèi)似。
對于引用屬性生成的訪(fǎng)問(wèn)器,特別是雙向引用,就變得有點(diǎn)復雜。
單向引用
讓我們用另一個(gè)與Book類(lèi)有關(guān)聯(lián)關(guān)系的類(lèi)Writer來(lái)擴充示例模型。

在Book與Writer間的關(guān)聯(lián)關(guān)系是,在本例中,一個(gè)單向的引用。從Book存取Writer的引用(角色)名字為author。
在EMF生成器中運行此模型,將會(huì )再生成一個(gè)新的接口Writer與實(shí)現類(lèi)WriterImpl,并在Book接口中添加新的getter與setter方法。
Writer getAuthor();
void setAuthor(Writer value);
因為author引用是單向的,所以setAuthor()方法看起來(lái)還是一個(gè)簡(jiǎn)單的數據設置,與前面的setPages()類(lèi)似:
public void setAuthor(Writer newAuthor)
{
Writer oldAuthor = author;
author = newAuthor;
if(eNotificationRequired())
eNotify(new ENotificationImpl(this, ...));
}
其中唯一的區別就是我們用一個(gè)對象指針代替了簡(jiǎn)單數據類(lèi)型的字段。
因為我們在處理一個(gè)對象引用,所以,getAuther()方法會(huì )復雜一點(diǎn)。因為對于一些類(lèi)型的引用,包括author類(lèi)型,需要處理這種可能性:即被引用的對象(在本例中是Writer)可能被持久化在與源對象(在本例中是Book)不同的資源(文檔)中。因為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;
}
代替簡(jiǎn)單地返回author實(shí)例變量,我們首先調用了從框架中繼承來(lái)的方法eIsProxy()來(lái)檢查引用是否是一個(gè)代理,若是,再調用eResolveProxy()來(lái)處理它。后者將會(huì )調用EcoreUtil.resolve(),一個(gè)靜態(tài)工具方法來(lái)試圖加載目標對象的文檔,并使用代理URI來(lái)構建此對象。若成功,它將返回被解析的對象,若沒(méi)有成功載入文檔,則它還將再次返回代理。
雙向引用
現在我們理解了代理解析對某些類(lèi)型的引用的getter方式所起的影響,現在我們再來(lái)看一看雙向關(guān)聯(lián)關(guān)系如何影響setter方式。將單向關(guān)聯(lián)改為如下:

現在關(guān)聯(lián)是雙向的了,通過(guò)不帶箭頭的關(guān)聯(lián)線(xiàn)來(lái)進(jìn)行表示。從Write訪(fǎng)問(wèn)Book的角色名為books。
當我們?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
}
我們可以看到,當設置一個(gè)雙向引用,如author,時(shí),引用的另一端必須也同時(shí)被設置(通過(guò)調用eInverseAdd())。而且,我們也需要為另一端移除原先的author(通過(guò)調用eInverseRemove()),因為在模型中,author引用是單一的(也就是說(shuō),一本書(shū)只能有一個(gè)作者),Book不能擁有超過(guò)一個(gè)Writer引用。最后,我們通過(guò)調用另一個(gè)生成的方法basicSetAuthor()來(lái)設置新的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方法非常類(lèi)似,除非msgs參數不為空,將會(huì )把notification加入到其中,代替原來(lái)直接觸發(fā)此通知消息的做法。因為在雙向引用的set操作中發(fā)生的正向的/反向的添加、以及移除操作,會(huì )有四個(gè)(在本例中是三個(gè))不同的通知消息被生成。NotificationChain被用來(lái)集中所有這些單個(gè)的消息,直到所有狀態(tài)改變都完成后再來(lái)觸發(fā)它們。隊列中的消息通過(guò)調用msgs.dispatch()進(jìn)行發(fā)送。
多值引用
可以注意到在示例中books關(guān)聯(lián)(從Writer到Book)是多值關(guān)聯(lián)(0..*)。換句話(huà)說(shuō),一個(gè)作者可能寫(xiě)有多本書(shū)。EMF中的多值引用(上界大于1的引用)使用集合API來(lái)處理,所以在接口中只生成了getter方法:
public interface Writer extends EObject
{
...
EList getBooks();
}
注意到getBooks()返回一個(gè)替代java.util.List的EList。實(shí)際上,它們倆基本相同。EList是java.util.List在EMF中的子類(lèi),并在API中加入了兩個(gè)move方法。除此之外,從客戶(hù)端視角,你可以認為它就是一個(gè)JavaList。如,向books關(guān)聯(lián)中添加一本書(shū),只需簡(jiǎn)單地調用:
aWriter.getBooks().add(aBook);
或者,通過(guò)迭代器你可以如下所示做其它的事情:
for (Iterator iter = aWriter.getBooks().iterator(); iter.hasNext(); )
{
Book book = (Book)iter.next();
...
}
如上面所示,從客房端視角,操縱多值引用沒(méi)有任何特殊之處。然而,因為books引用是雙向引用的一部分(它是Book.author的另一端),我們還是要做如同在setAuthor()方法中所示的所有相對的握手處理。從WriterImpl中的getBooks()方法的實(shí)現代碼可看到如何處理多值引用的情況:
public EList getBooks()
{
if (books == null)
{
books = new EObjectWithInverseResolvingEList(Book.class, this, LibraryPackage.WRITER__BOOKS, LibraryPackage.BOOK__AUTHOR);
}
return books;
}
getBooks()方法返回一個(gè)特殊的實(shí)現類(lèi)EObjectWithInverseResolvingEList,通過(guò)向它提供為在添加或移除操作時(shí)順利完成相對的握手處理所需的信息來(lái)構造它的實(shí)例。EMF實(shí)際上提供了20種不同的特殊的EList實(shí)現,來(lái)高效地實(shí)現所有類(lèi)型的多值屬性。對于單向關(guān)聯(lián)關(guān)系(也就是說(shuō),沒(méi)有反向)我們可以使用EObjectResolvingElist。若引用操作不需要代理解析,我們可以使用EObjectWithInverseEList或者EObjectEList等等。
所以對于我們的例子,實(shí)現對bookd引用的list對象使用參數LibraryPackage.BOOK_AUTHOR來(lái)進(jìn)行創(chuàng )建(一個(gè)自動(dòng)生成的靜態(tài)int常量,表示相對的特性)。此參數在調用add()方法中對Book的eInverseAdd()方法進(jìn)行調用時(shí)使用,類(lèi)似于在Book的setAuthor()期間對Writer的eInverseAdd()調用的方式。下面是在BookImpl中eInverseAdd() 方法的實(shí)現:
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:
...
}
}
...
}
它首先調用eInverseRemove()方法來(lái)移除任何原先的作者(如同Book的setAuthor()方法),然后,它調用basicSetAuthor()來(lái)實(shí)際設置引用。雖然在我們的例子中,只有一個(gè)雙向引用,在eInverseAdd()中使用了switch結構可以對Book類(lèi)的所有雙向引用進(jìn)行處理。
容器引用(復合聚合)
讓我們再增加一個(gè)新類(lèi),Library,把它作為Book的容器。

容器引用通過(guò)在Library這端使用黑心菱形的箭頭線(xiàn)來(lái)表示。此關(guān)聯(lián)關(guān)系表示一個(gè)Library聚合,可以有0或更多本書(shū)。值聚合(包容)關(guān)聯(lián)關(guān)系是特別重要的因為它表示了一個(gè)目標實(shí)例的父實(shí)例,或者稱(chēng)為擁有者,它也指明了在持久化處理時(shí)對象的物理位置。
容器從多個(gè)方面影響代碼生成。首先,因為被包容的對象保證與它的容器處于同一資源中,就不再需要代理解析。因而,在LibraryImpl中生成的get方法將使用一個(gè)不需要解析的EList實(shí)現類(lèi):
public EList getBooks()
{
if (books == null)
{
books = new EObjectContainmentEList(Book.class, this, ...);
}
return books;
}
因為不需要完成代理解析,一個(gè)EObjectContainmentEList可以非常高效地實(shí)現contains()操作(就是說(shuō),與普通的線(xiàn)性增長(cháng)的耗時(shí),可在一個(gè)常量時(shí)間內完成操作)。這是非常重要的,因為在EMF引用列表中,不允許進(jìn)行復制項目的操作,所以在add()操作中會(huì )調用contains()方法。
因為一個(gè)對象只能有一個(gè)容器,添加一個(gè)對象到容器關(guān)聯(lián)中意味著(zhù)必須將它從現在所處的容器中移出。例如,添加一本book到Library的books列表中將包括將此book從其它Library的books列表中移除的操作。它與那些相對端的重數是1的雙向關(guān)聯(lián)關(guān)系沒(méi)有任何不同。讓我們假定,若Writer類(lèi)對于Book也擁有一個(gè)容器關(guān)聯(lián),稱(chēng)為ownedBooks。這時(shí),一個(gè)給定的處于某個(gè)Writer的ownedBooks列表中的book實(shí)例,當它被加到一個(gè)Library的books引用時(shí),它也將被從Writer的列表中移除。
要高效地實(shí)現此種操作,基類(lèi)EObjectImol擁有一個(gè)EObject類(lèi)型的實(shí)例變量(eContainer),用來(lái)存儲包容它的容器。作為結果,一個(gè)容器引用隱含地一定是雙向引用。要從Book訪(fǎng)問(wèn)Library,你可以寫(xiě)如下代碼:
EObject container = book.eContainer();
if (container instanceof Library)
library = (Library)container;
若你想要避免向下造型,你可以將關(guān)聯(lián)明確地改為雙向:

并讓EMF來(lái)為你生成一個(gè)良好的類(lèi)型安全的get方法:
public Library getLibrary()
{
if (eContainerFeatureID != LibraryPackage.BOOK__LIBRARY) return null;
return (Library)eContainer;
}
注意到,在明確的get方法中使用從EObjectImpl中繼承的eContainer變量,來(lái)代替像前面例子中一樣生成一個(gè)實(shí)例變量。
枚舉屬性
到目前為止,我們已經(jīng)看了EMF如何處理簡(jiǎn)單屬性,以及各種類(lèi)型的引用。另一種公共的屬性類(lèi)型是枚舉。枚舉屬性使用Java類(lèi)型安全的enum模式來(lái)實(shí)現。
我們增加一個(gè)枚舉屬性,category,到Book類(lèi):

重新生成實(shí)現類(lèi),Book接口現在包括針對category的getter與setter。
BookCategory getCategory();
void setCategory(BookCategory value);
在生成的接口中,category方法使用了類(lèi)型安全的枚舉類(lèi)—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);
}
}
以下略…,因為在JDK5.0中,已提供了類(lèi)似的Enum實(shí)現。要了解上面的代碼,請參見(jiàn)相關(guān)的文檔。
工廠(chǎng)與包
在模型接口與實(shí)現類(lèi)之外,EMF還至少生成兩個(gè)接口(以及它們的實(shí)現類(lèi)):一個(gè)工廠(chǎng)與一個(gè)包。
工廠(chǎng),如同名字的意思,用來(lái)創(chuàng )建模型中類(lèi)的實(shí)例,而包則提供一些靜態(tài)變量(如,生成的方法所使用的特性常量)以及便利方法來(lái)存取你模型的元數據。
下面是book示例中的工廠(chǎng)接口:
public interface LibraryFactory extends EFactory
{
LibraryFactory eINSTANCE = new LibraryFactoryImpl();
Book createBook();
Writer createWriter();
Library createLibrary();
LibraryPackage getLibraryPackage();
}
如上所示,生成的工廠(chǎng)為每模型中定義的類(lèi)提供一個(gè)工廠(chǎng)方法(create),以及訪(fǎng)問(wèn)模型包的方法,一個(gè)指向自身的靜態(tài)常量單值引用。
LibraryPackage接口提供了對模型元數據進(jìn)行方便的訪(fǎng)問(wè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();
...
}
如上所示,元數據通過(guò)兩種形式提供:int型常量,以及Ecore元對象。int常量提供了傳遞元信息的高效手段。你可以注意到生成的方法在實(shí)現中使用到了這些常量。稍后,當我們察看如何實(shí)現EMF適配器時(shí),你將看到這些常量還為處理消息時(shí),判斷什么發(fā)生了改變時(shí),來(lái)提供高效的手段。還有,與工廠(chǎng)類(lèi)似,生成的包接口,也提供了一個(gè)指向自身的單值引用。
生成擁有超類(lèi)的類(lèi)
讓我們在Book模型中來(lái)創(chuàng )建一個(gè)子類(lèi),SchoolBook,如下:

EMF代碼生成器會(huì )如你所愿地處理單一繼承,生成的接口擴展了超類(lèi)接口。
public interface SchoolBook extends Book
實(shí)現類(lèi)也擴展對應的超實(shí)現類(lèi)。
public class SchoolBookImpl extends BookImpl implements SchoolBook
在Java中,支持接口的多重繼承,但每個(gè)EMF實(shí)現類(lèi)只能擴展其中一個(gè)基類(lèi)的實(shí)現類(lèi)。因此,若模型中有多重繼承,我們需要決定將使用哪個(gè)類(lèi)作為基類(lèi),其它的都只被當成接口的合并,并在繼承后的實(shí)現類(lèi)中提供所有的接口實(shí)現。
考慮如下圖所示的模型:

我們讓SchoolBook繼承兩個(gè)類(lèi):Book以及Asset。我們標志Book類(lèi)作為實(shí)現類(lèi)的基類(lèi)。若我們重新生成代碼,接口SchoolBook將會(huì )擴展兩個(gè)接口:
public interface SchoolBook extends Book, Asset
實(shí)現類(lèi)也與前面相同,只是現在包括了從接口合并進(jìn)來(lái)的方法getValue()與方法setValue():
public class SchoolBookImpl extends BookImpl implements SchoolBook
{
public float getValue()
{
...
}
public void setValue(float newValue)
{
...
}
...
}
定制生成的實(shí)現類(lèi)
你可以向生成的Java類(lèi)中添加行衛(方法或實(shí)例變量),而不用擔心一旦模型發(fā)生變動(dòng)后重新生成代碼會(huì )搞丟你加入的東西。例如,我們向類(lèi)Book加入一個(gè)方法,isRecommended()。只要在Java接口Book中簡(jiǎn)單地加入新方法的聲明即可。
public interface Book ...
{
boolean isRecommended();
...
}
以及它們在實(shí)現類(lèi)中的實(shí)現:
public boolean isRecommended()
{
return getAuthor().getName().equals("William Shakespeare");
}
EMF生成器不會(huì )擦去這些修改,因為它不是一個(gè)自動(dòng)生成的方法。每個(gè)EMF生成的方法都包括一個(gè)包含@generated標簽的Javadoc注解,如下:
/**
* ...
* @generated
*/
public String getTitle()
{
return title;
}
不管怎樣重新生成代碼,EMF都不會(huì )去碰所有不包含此標簽的方法(如isRecommended()方法)。實(shí)際上,若我們想要修改一個(gè)自動(dòng)生成的方法,我們可以從它的注解中移除@generated標簽。
/**
* ...
* @generated // (removed)
*/
public String getTitle()
{
// our custom implementation ...
}
現在,因為沒(méi)有@generated標簽,getTitle()方法被認為了用戶(hù)的代碼;若我們重新生成代碼,生成器將會(huì )檢測到?jīng)_突,并簡(jiǎn)單地放棄為此方法生成代碼。
實(shí)際上,在放棄一個(gè)生成的方法前,生成器首先檢查,在文件中是否存在另一個(gè)相同名字的,但加上Gen的,自動(dòng)生成的方法。若它找到一個(gè),則將會(huì )把生成的代碼重定向到這個(gè)方法中。例如,若我們想擴展生成的getTitle()方法實(shí)現,來(lái)代替完全地放棄它,我們可以簡(jiǎn)單地改一下方法名字:
/**
* ...
* @generated
*/
public String getTitleGen()
{
return title;
}
并加入我們自己的覆蓋方法:
public String getTitle()
{
String result = getTitleGen();
if (result == null)
result = ...
return result;
}
現在我們重新生成代碼,生成器將會(huì )檢測到與我們自己的getTitle()有沖突,但因為在類(lèi)中存在帶@generated標簽的getTitleGen()方法,它將會(huì )重定向新生成的代碼到此方法中,而不是簡(jiǎn)單地放棄生成新的代碼。
EMF模型上的操作
除了屬性與引用,你可以向模型中的類(lèi)添加操作。若你這么做,則EMF生成器將會(huì )在接口中生成方法的聲明,在實(shí)現類(lèi)中生成一個(gè)方法框架。EMF不對行衛建模,所以實(shí)現要由用戶(hù)自己寫(xiě)Java代碼來(lái)提供。
可以像上面說(shuō)的,移除@generated標簽,然后加入自己的代碼。另處,也可以將Java代碼包括在模型中間。在Rose上,
聯(lián)系客服