以下文章出自www.。本人仅作中文翻译,未作任何其它修改。
EMF概述 文档原出处:http:///emf/docs.php?doc=references/overview/EMF.html 最后修改时间: 2005年6月16日
本文为EMF及其代码生成模式提供了一个基本的概述。要了解EMF所有功能的详细描述,请参见《Eclipse Modeling Framework》(Addison Wesley, 2003),或者框架自身的Javadoc文档。 概论 EMF是一个Java框架与代码生成机制,用来构建基于结构化模型的工具或其它应用系统。它们可以带给你面向对象建模的思想,EMF帮助你快速地将你的模型转变为高效的、正确的、以及易用的定制Java代码,只需要一个很低的入门成本,EMF就可以为你提供这些益处。 那么,当我说“模型”时到底意味着什么?当谈论模型时,我们一般都会想到类图、协作图、状态类,等等。UML为这些图定义了标准的符号。联合使用各种UML图,可以详细说明一个应用系统的完整模型。这个模型可能纯粹只用作文档,或者通过适当的工具,它可以被用来作为输入内容生成一部分或全部应用系统代码。 要能够做到这些的建模工作一般都需要昂贵的面向对象的分析与设计工具(OOA/D),你可能对我们的结论有疑问,但是,EMF提供了一个低成本的入口。我们说这个的理由是一个EMF模型只需要你拥有在UML中建模所需知识的一小部分即可,主要是简单的类的定义,以及它们的属性与关系,针对这些,不需要使用一个全尺寸的图形化建模工具。 EMF使用XMI作为模型定义的规范形式,你有多种方法可以得到这种格式的模型: · 直接使用XML或文本编辑器来创建XMI文档。 · 从建模工具,如Rose,中导出XMI文档。 · 带有模型属性的注解Java接口。 · 描述模型序列化格式的XML Schema。 第一种方法最直接,但一般只对XML高手有吸引力。若你已经使用了全尺寸的建模工具,则第二种方法是最可取的。第三种方法只需要有一个基本的Java开发环境就可以低成本地拥有EMF带来的好处、以及它的代码生成能力。在创建一个需要读写特定的XML文件格式的应用系统时,最后一种方法最适合。 一旦你指定一个EMF模型,EMF生成器就可以创建一个一致的Java实现类的集合,你可以编辑生成的类来添加方法与实例变量,只要需要还可以重新从模型中生成代码:你添加的部分在重新生成过程中都将被保留。若你添加的代码依赖于你在模型中修改的某些东西,你还需要更新代码来反映这些改变,其它情况下,你的代码是完全不受模型修改与重新生成的影响的。 另外,通过以下方法,就可以简单地提高你的生产力:使用EMF提供的几个其它的益处,如模型变动通知、持久化支持(包括默认的XMI、以及基于Schema的XML序列化),模型校验框架,以及非常有效的用来操纵EMF对象的反射API。最重要的是,EMF提供了与其它基于EMF的工具或应用进行互操作的基础。 EMF包括两个基本的框架,core框架与EMF.Edit。core框架通过为模型创建实现类,提供基本的代码生成与运行时支持。EMF.Edit基于core构建并进行了扩展,添加了对生成适配器类的支持,可以支持视图以及基于命令的(可以undo的)模型编辑操作。下面的章节描述core框架的主要功能。EMF.Edit将在另一篇文章中进行描述“The EMF.Edit Framework Overview”。指南“Generatin an EMF Model”详细介绍了如何运行EMF与EMF.Edit生成器。
EMF与OMG MOF的关系 如果你已经熟悉OMG的MOF,你肯定会困惑于EMF倒底与MOF有什么关系。实际上,EMF就是从作为MOF规范的一个实现开始的,通过实现大量使用EMF的工具积累的经验,我们又对它进行了发展。EMF可以被看作对于MOF的部分核心API的一个高效的Java实现。然而,为避免任何混淆,与MOF核心元模型类似的部分在EMF中称为Ecore。 在MOF2.0计划中,MOF模型的一个类似子集,称为(EMOF,Essential MOF),也被分离了出来。在Ecore与EMOF间只存在微小的,大部分是命名上的区别;无论如何,EMF都可以透明地读写EMOF的序列化存储。
定义一个EMF模型 为了有助于描述EMF,我们假定拥有一个简单的、只包含一个类的模型,如下图: 模型中展示了一个拥有两个属性的类:String类型的title,int类型的pages。 我们的如上图这么简单的模型定义,可以通过几种不同的方式提供给EMF代码生成器。 UML 若你拥有与EMF一起工作的建模工具,你可以简单地画出如上图所示的模型。 XMI 另外,我们可以直接用XMI文档来描述这个模型,就像下面所示: <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文档包含了类图中的所有信息.。在图中的每个类与属性都在XMI文档中有一个对应的类或属性定义。 Annotated Java 如果,你没有图形化建模工具,也没有兴趣手工输入所有的XMI语句,这是第三个描述你的模型的选项。因为EMF生成器是一个可以并入代码的生成器,通过提供部分Java接口(将模型信息通过注解提供),生成器可以将接口作为生成模型的元数据,并将其它的代码生成到最终实现代码中。 我们可以这样定义Book类: /** * @model */ public interface Book { ** * @model */ String getTitle();
/** * @model */ int getPages(); } 通过这种方法,用Java接口中标准的get方法来标注属性与引用的形式,我们提供了模型的所有信息。@model标签用来向代码生成器表示那些接口,以及接口的那些部分对应到模型元素,并需要进行相应的代码生成。 对于我们的简单示例,所有模型信息都确实通过对接口进行Java反省操作而提供,所以不再需要任何其它附加的模型信息。在一般情况下,还可以在@model标签后添加模型元素的附加详细信息。针对此示例,若我们希望pages属性是只读的(这意味着不生成setter方法),我们就可以向注解中加入信息:
/** * @model changeable="false" */ int getPages(); 因为只有与默认值不一致的信息才需要被明确指定,所以注解可以保持简洁明了。
XML Schema 有时候,你可以通过XML Schema描述一个模型的实例序列化后看起来应该怎样来定义一个模型。这对于编写一个使用XML来整合现存的应用系统或遵循某个标准的的应用系统是很有帮助的。以下就是与我们的简单模型等值的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> 这种方法与另外三种有些不同,主要因为EMF在最后的使用中,必然会加上某种序列化的约束,来确保与schema的一致性。作为结果,从Schema中创建的模型看起来就与使用其它方法生成的模型不一样。这些区别的细节将在概述后的章节中讨论。 在本文的剩余部分,为保持简洁明了,我们将统一使用UML图。所有我们所阐述的建模要领都可以用注解Java接口或直接使用XMI来表示,其中大部分也都存在有等价的XML Schema。不管提供了什么信息,对于EMF的代码生成来说都是一样的。
生成Java实现 对于模型中的每个类,都将会生成一个Java接口以及对应的实现类。在我们例子中,为Book生成的接口如下: public interface Book extends EObject { String getTitle(); void setTitle(String value);
int getPages(); void setPages(int value); } 每个生成的接口包含针对每个属性的getter与setter方法。 Book接口扩展了基础接口EObject。EObject是在EMF中等价于java.lang.Object的对象,也就是说,它是所有EMF类的基类。EObject以及它的实现类EObjectImpl(我们将在稍后讨论)提供了一些基础的轻量级的实现,来为Book引入EMF的通知与持久框架。在我们开始讨论EObject到底往混合中带来了什么之前,让我们继续来看一看EMF如何生成Book。 每个生成的实现类都包含定义在对应的接口中的getter与setter方法的实现,还加上其它一些EMF框架所需的方法。 类BookImpl将会包含有title与pages属性的存取实现。pages属性,如例子,拥有如下生成的实现代码: 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方法提供了理想的效率。它简单地返回表示属性的一个实例变量。 set方法,虽然有点复杂,但还是相当高效。在设置pages实例变量的值之外,set方法还需要向可能的观察者发送改变通知,这些观察者通过eNotity()来监听对象。在没有观察者时(例如,在一个批处理中),情况就可以得到优化,通知对象的构造、以及通知方法的调用,都由eNotificationRequired()方法来进行监控。eNotificationRequired()方法默认的实现只是简单地检查是否存在与对象相关的观察者(或适配器)。因此,若在没有观察者的情况下使用EMF对象,对eNotificationRequired的调用与一个null指针检查相当,它可以在JIT编译器中进行Inline处理。 对于其它类型的属性自动生成的访问方式,像String类型的title属性,有一些不同,但基本与上面列出的代码类似。 对于引用属性生成的访问器,特别是双向引用,就变得有点复杂。 单向引用 让我们用另一个与Book类有关联关系的类Writer来扩充示例模型。 在Book与Writer间的关联关系是,在本例中,一个单向的引用。从Book存取Writer的引用(角色)名字为author。 在EMF生成器中运行此模型,将会再生成一个新的接口Writer与实现类WriterImpl,并在Book接口中添加新的getter与setter方法。 Writer getAuthor(); void setAuthor(Writer value); 因为author引用是单向的,所以setAuthor()方法看起来还是一个简单的数据设置,与前面的setPages()类似: public void setAuthor(Writer newAuthor) { Writer oldAuthor = author; author = newAuthor; if(eNotificationRequired()) eNotify(new ENotificationImpl(this, ...)); } 其中唯一的区别就是我们用一个对象指针代替了简单数据类型的字段。 因为我们在处理一个对象引用,所以,getAuther()方法会复杂一点。因为对于一些类型的引用,包括author类型,需要处理这种可能性:即被引用的对象(在本例中是Writer)可能被持久化在与源对象(在本例中是Book)不同的资源(文档)中。因为EMF的持久化框架使用懒惰加裁模式,一个对象指针(在本例中是author)在某一时间点可能只是对象的一个代理,而不是真正的引用此对象。综上所述,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实例变量,我们首先调用了从框架中继承来的方法eIsProxy()来检查引用是否是一个代理,若是,再调用eResolveProxy()来处理它。后者将会调用EcoreUtil.resolve(),一个静态工具方法来试图加载目标对象的文档,并使用代理URI来构建此对象。若成功,它将返回被解析的对象,若没有成功载入文档,则它还将再次返回代理。 双向引用 现在我们理解了代理解析对某些类型的引用的getter方式所起的影响,现在我们再来看一看双向关联关系如何影响setter方式。将单向关联改为如下: 现在关联是双向的了,通过不带箭头的关联线来进行表示。从Write访问Book的角色名为books。 当我们为模型重新生成代码时,getAuthor()方法不会有改变,但setAuthor()将变为如下所示: 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 } 我们可以看到,当设置一个双向引用,如author,时,引用的另一端必须也同时被设置(通过调用eInverseAdd())。而且,我们也需要为另一端移除原先的author(通过调用eInverseRemove()),因为在模型中,author引用是单一的(也就是说,一本书只能有一个作者),Book不能拥有超过一个Writer引用。最后,我们通过调用另一个生成的方法basicSetAuthor()来设置新的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; } 这个方法与单向引用的set方法非常类似,除非msgs参数不为空,将会把notification加入到其中,代替原来直接触发此通知消息的做法。因为在双向引用的set操作中发生的正向的/反向的添加、以及移除操作,会有四个(在本例中是三个)不同的通知消息被生成。NotificationChain被用来集中所有这些单个的消息,直到所有状态改变都完成后再来触发它们。队列中的消息通过调用msgs.dispatch()进行发送。 多值引用 可以注意到在示例中books关联(从Writer到Book)是多值关联(0..*)。换句话说,一个作者可能写有多本书。EMF中的多值引用(上界大于1的引用)使用集合API来处理,所以在接口中只生成了getter方法: public interface Writer extends EObject { ... EList getBooks(); } 注意到getBooks()返回一个替代java.util.List的EList。实际上,它们俩基本相同。EList是java.util.List在EMF中的子类,并在API中加入了两个move方法。除此之外,从客户端视角,你可以认为它就是一个JavaList。如,向books关联中添加一本书,只需简单地调用: aWriter.getBooks().add(aBook); 或者,通过迭代器你可以如下所示做其它的事情: for (Iterator iter = aWriter.getBooks().iterator(); iter.hasNext(); ) { Book book = (Book)iter.next(); ... } 如上面所示,从客房端视角,操纵多值引用没有任何特殊之处。然而,因为books引用是双向引用的一部分(它是Book.author的另一端),我们还是要做如同在setAuthor()方法中所示的所有相对的握手处理。从WriterImpl中的getBooks()方法的实现代码可看到如何处理多值引用的情况: public EList getBooks() { if (books == null) { books = new EObjectWithInverseResolvingEList(Book.class, this, LibraryPackage.WRITER__BOOKS, LibraryPackage.BOOK__AUTHOR); } return books; } getBooks()方法返回一个特殊的实现类EObjectWithInverseResolvingEList,通过向它提供为在添加或移除操作时顺利完成相对的握手处理所需的信息来构造它的实例。EMF实际上提供了20种不同的特殊的EList实现,来高效地实现所有类型的多值属性。对于单向关联关系(也就是说,没有反向)我们可以使用EObjectResolvingElist。若引用操作不需要代理解析,我们可以使用EObjectWithInverseEList或者EObjectEList等等。 所以对于我们的例子,实现对bookd引用的list对象使用参数LibraryPackage.BOOK_AUTHOR来进行创建(一个自动生成的静态int常量,表示相对的特性)。此参数在调用add()方法中对Book的eInverseAdd()方法进行调用时使用,类似于在Book的setAuthor()期间对Writer的eInverseAdd()调用的方式。下面是在BookImpl中eInverseAdd() 方法的实现: 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()方法来移除任何原先的作者(如同Book的setAuthor()方法),然后,它调用basicSetAuthor()来实际设置引用。虽然在我们的例子中,只有一个双向引用,在eInverseAdd()中使用了switch结构可以对Book类的所有双向引用进行处理。 容器引用(复合聚合) 让我们再增加一个新类,Library,把它作为Book的容器。 容器引用通过在Library这端使用黑心菱形的箭头线来表示。此关联关系表示一个Library聚合,可以有0或更多本书。值聚合(包容)关联关系是特别重要的因为它表示了一个目标实例的父实例,或者称为拥有者,它也指明了在持久化处理时对象的物理位置。 容器从多个方面影响代码生成。首先,因为被包容的对象保证与它的容器处于同一资源中,就不再需要代理解析。因而,在LibraryImpl中生成的get方法将使用一个不需要解析的EList实现类: public EList getBooks() { if (books == null) { books = new EObjectContainmentEList(Book.class, this, ...); } return books; } 因为不需要完成代理解析,一个EObjectContainmentEList可以非常高效地实现contains()操作(就是说,与普通的线性增长的耗时,可在一个常量时间内完成操作)。这是非常重要的,因为在EMF引用列表中,不允许进行复制项目的操作,所以在add()操作中会调用contains()方法。 因为一个对象只能有一个容器,添加一个对象到容器关联中意味着必须将它从现在所处的容器中移出。例如,添加一本book到Library的books列表中将包括将此book从其它Library的books列表中移除的操作。它与那些相对端的重数是1的双向关联关系没有任何不同。让我们假定,若Writer类对于Book也拥有一个容器关联,称为ownedBooks。这时,一个给定的处于某个Writer的ownedBooks列表中的book实例,当它被加到一个Library的books引用时,它也将被从Writer的列表中移除。 要高效地实现此种操作,基类EObjectImol拥有一个EObject类型的实例变量(eContainer),用来存储包容它的容器。作为结果,一个容器引用隐含地一定是双向引用。要从Book访问Library,你可以写如下代码: EObject container = book.eContainer(); if (container instanceof Library) library = (Library)container; 若你想要避免向下造型,你可以将关联明确地改为双向: 并让EMF来为你生成一个良好的类型安全的get方法: public Library getLibrary() { if (eContainerFeatureID != LibraryPackage.BOOK__LIBRARY) return null; return (Library)eContainer; } 注意到,在明确的get方法中使用从EObjectImpl中继承的eContainer变量,来代替像前面例子中一样生成一个实例变量。 枚举属性 到目前为止,我们已经看了EMF如何处理简单属性,以及各种类型的引用。另一种公共的属性类型是枚举。枚举属性使用Java类型安全的enum模式来实现。 我们增加一个枚举属性,category,到Book类: 重新生成实现类,Book接口现在包括针对category的getter与setter。 BookCategory getCategory(); void setCategory(BookCategory value); 在生成的接口中,category方法使用了类型安全的枚举类—BookCategory。它为枚举值定义了静态常量,以及其它的方法,如下: 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中,已提供了类似的Enum实现。要了解上面的代码,请参见相关的文档。
工厂与包 在模型接口与实现类之外,EMF还至少生成两个接口(以及它们的实现类):一个工厂与一个包。 工厂,如同名字的意思,用来创建模型中类的实例,而包则提供一些静态变量(如,生成的方法所使用的特性常量)以及便利方法来存取你模型的元数据。 下面是book示例中的工厂接口: public interface LibraryFactory extends EFactory { LibraryFactory eINSTANCE = new LibraryFactoryImpl(); Book createBook(); Writer createWriter(); Library createLibrary();
LibraryPackage getLibraryPackage(); } 如上所示,生成的工厂为每模型中定义的类提供一个工厂方法(create),以及访问模型包的方法,一个指向自身的静态常量单值引用。 LibraryPackage接口提供了对模型元数据进行方便的访问: 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();
... } 如上所示,元数据通过两种形式提供:int型常量,以及Ecore元对象。int常量提供了传递元信息的高效手段。你可以注意到生成的方法在实现中使用到了这些常量。稍后,当我们察看如何实现EMF适配器时,你将看到这些常量还为处理消息时,判断什么发生了改变时,来提供高效的手段。还有,与工厂类似,生成的包接口,也提供了一个指向自身的单值引用。 生成拥有超类的类 让我们在Book模型中来创建一个子类,SchoolBook,如下: EMF代码生成器会如你所愿地处理单一继承,生成的接口扩展了超类接口。 public interface SchoolBook extends Book 实现类也扩展对应的超实现类。 public class SchoolBookImpl extends BookImpl implements SchoolBook 在Java中,支持接口的多重继承,但每个EMF实现类只能扩展其中一个基类的实现类。因此,若模型中有多重继承,我们需要决定将使用哪个类作为基类,其它的都只被当成接口的合并,并在继承后的实现类中提供所有的接口实现。 考虑如下图所示的模型: 我们让SchoolBook继承两个类:Book以及Asset。我们标志Book类作为实现类的基类。若我们重新生成代码,接口SchoolBook将会扩展两个接口: public interface SchoolBook extends Book, Asset 实现类也与前面相同,只是现在包括了从接口合并进来的方法getValue()与方法setValue(): public class SchoolBookImpl extends BookImpl implements SchoolBook { public float getValue() { ... }
public void setValue(float newValue) { ... }
... } 定制生成的实现类 你可以向生成的Java类中添加行卫(方法或实例变量),而不用担心一旦模型发生变动后重新生成代码会搞丢你加入的东西。例如,我们向类Book加入一个方法,isRecommended()。只要在Java接口Book中简单地加入新方法的声明即可。 public interface Book ... { boolean isRecommended(); ... } 以及它们在实现类中的实现: public boolean isRecommended() { return getAuthor().getName().equals("William Shakespeare"); } EMF生成器不会擦去这些修改,因为它不是一个自动生成的方法。每个EMF生成的方法都包括一个包含@generated标签的Javadoc注解,如下: /** * ... * @generated */ public String getTitle() { return title; } 不管怎样重新生成代码,EMF都不会去碰所有不包含此标签的方法(如isRecommended()方法)。实际上,若我们想要修改一个自动生成的方法,我们可以从它的注解中移除@generated标签。 /** * ... * @generated // (removed) */ public String getTitle() { // our custom implementation ... } 现在,因为没有@generated标签,getTitle()方法被认为了用户的代码;若我们重新生成代码,生成器将会检测到冲突,并简单地放弃为此方法生成代码。 实际上,在放弃一个生成的方法前,生成器首先检查,在文件中是否存在另一个相同名字的,但加上Gen的,自动生成的方法。若它找到一个,则将会把生成的代码重定向到这个方法中。例如,若我们想扩展生成的getTitle()方法实现,来代替完全地放弃它,我们可以简单地改一下方法名字: /** * ... * @generated */ public String getTitleGen() { return title; } 并加入我们自己的覆盖方法: public String getTitle() { String result = getTitleGen(); if (result == null) result = ... return result; } 现在我们重新生成代码,生成器将会检测到与我们自己的getTitle()有冲突,但因为在类中存在带@generated标签的getTitleGen()方法,它将会重定向新生成的代码到此方法中,而不是简单地放弃生成新的代码。 EMF模型上的操作 除了属性与引用,你可以向模型中的类添加操作。若你这么做,则EMF生成器将会在接口中生成方法的声明,在实现类中生成一个方法框架。EMF不对行卫建模,所以实现要由用户自己写Java代码来提供。 可以像上面说的,移除@generated标签,然后加入自己的代码。另处,也可以将Java代码包括在模型中间。在Rose上, |
|