EMF是基于两种元模型来构建的,分别是Ecore和Genmodel。


Ecore:主要用于定义模型结构的相关信息,并对外提供了一个根对象(犹如XML文件的根节点)用来代表要显示的模型,该对象的子节点为package(包),package的子节点为class(类),class的子节点为attribute(类的属性字段),这样整个树形结构就代表了所要表示的EMF模型结构。

Genmodel:主要包含有关代码生成的相关信息(如:文件生成路径及文件名),和一些控制参数用来控制代码怎样生成。

EMF采用模型驱动开发方法,在无需编码的情况下,我们可以基于模型使用EMF为我们生成以下几样东西。
1,与模型相对应的EMF实体类。
2,基于实体类的各种ItemProvider模型适配器类,通过该适配器类可以很好的将EMF模型绑定到Jface组件中去,稍后具体介绍。
3,editor-plugin项目,可理解为基于EMF应用的一个插件项目。
4,测试bundle。

这里3和4不做介绍,主要讲解1,2。然后自定义一个Jface组件,将EMF模型应用与该组件之上。

在开始之前,我们需要了解以下几个名词的含义,也是文中多处提到的几个词:
1实体类:就像Hibernate ORM为我们生成的实体类一样,EMF也会为我们生成这样的实体类,并且这些实体类含有更高级的特性(如:类属性状态发生变化时,实体类具有通知功能)

2适配器类:EMF的模型结构可能非常复杂(如可能含有多个实体类,并且类结构各不相同),这样当视图组件直接使用这些实体类的时候就变得非常困难(要判断出是哪一个实体类,针对该实体类怎样显示视图等等..),因此EMF针对这些实体类生成了一些适配器类,通过实现指定的接口,让适配器类来设计实体类的显示,而视图只需要选择合适的适配类便可。

3适配器工厂:上面讲到视图通过适配器类避免了与实体类的直接交互,然而视图又是如何得到合适的适配器类的呢?那便是通过适配器工厂类。

模型定义如下:


从模型定义来看Library便是Ecore所对外提供的用来表示模型的根对象。

首先来看EMF为我们生成的模型实体类(假设这些实体类分布在org.emf.demo这个bundle之中)
EMF会为我们生成3个package,分别是:
org.emf.demo.library:包含实体类的接口;用于创建实体类的工厂(libraryFactory);访问模型元数据的实用类(LibraryPackage)


org.emf.demo.library.impl:实体类接口的实现类;实体类工厂的实现类(LibraryFactryImpl);LibraryPackage的实现类(LibraryPackageImpl)
 org.emf.demo.library.util:针对该模型的适配器工厂类(LibraryAdapterFactory)
 注:libraryFactory和LibraryPackage都是单例模式的,可通过其成员变量eINSTANCE来得到对象的实例。在EMF中,所有的实体类接口全部继承EObject,换个角度,同Java领域的Object对象一样,EObject是EMF中所有类的基类。在实体类的定义中,我们强调过,除了具有一般JavaBean的特性之外,EMF实体类具有更高级的特性,因此其代码实现也较普通的javaBean更加复杂。
在EMF所生成的实体类的set方法中,除了要对成员变量进行赋值外,还要对其适配器类进行变更通知操作。以Book实体类的setPages方法为例:
public void setPages(int newPages){ int oldPages = pages; pages = newPages; if (eNotificationRequired()) eNotify(new ENotificationImpl(this, Notification.SET, ..., oldPages, pages)); } } eNotificationRequired()方法:判断该实体类是否含有适配器类。
 eNotify()方法:对适配器类进行变动通知操作;
 至于实体类状态发生变化时为什么要通知其适配器类,在以后的篇幅中会有介绍。在UML模型中,实体类之间的关系是多种多样的,单向引用,双向引用,聚合引用等等,为了表示这种关系的不同,EMF为我们生成的实体类代码也是不一样的。
单向引用(one-way reference):
 在单向引用中,EMF所生成的实体类set方法和无引用情况是相同的(成员变量赋值、适配器类通知)
 只是get方法的实现不同,以Book实体类的getWriter()方法为例:
public Writer getWriter() { if (Writer != null && Writer.eIsProxy()) { InternalEObject oldWriter = (InternalEObject)Writer; Writer = (Writer)eResolveProxy(oldWriter); if (Writer != oldWriter) { if (eNotificationRequired()) eNotify(new ENotificationImpl(this, Notification.RESOLVE, WebpagePackage.BOOK__Writer, oldWriter, Writer)); } } return Writer; } 当Writer和Book两个实体类分别存储与不同的资源文件中时,EMF是采用延迟加载模式来加载对象的,因此这里的Writer对象可能只是Writer的一个代理类,而不是真正的对象引用,所以在getWriter()方法里,首先要判断其是不是代理类(eIsProxy()方法),如果是则调用eResoleProxy()方法来加载目标对象,然后判断目标对象与代理对象是否相同(引用同一个对象),如果不同则Book的Writer属性状态便发生了变化,要对其适配器类进行通知。
双向引用(two-way reference):
 同单向引用相比,get方法没有变化,set方法的实现不同。以Book对象的setWriter()方法为例:
public void setWriter(Writer newWriter){ if (newWriter != Writer){ NotificationChain msgs = null; if (Writer != null) msgs = ((InternalEObject)Writer).eInverseRemove(this, ..., msgs); if (newWriter != null) msgs = ((InternalEObject)newWriter).eInverseAdd(this, ..., msgs); msgs = basicSetWriter(newWriter, msgs); if (msgs != null) msgs.dispatch(); }else if (eNotificationRequired()) eNotify(new ENotificationImpl(this, ...)); } } public NotificationChain basicSetWriter(Writer newWriter, NotificationChain msgs){ Writer oldWriter = Writer; Writer = newWriter; if (eNotificationRequired()){ ENotificationImpl notification = new ENotificationImpl(this, ...); if (msgs == null) msgs = notification; else msgs.add(notification); } return msgs; } 注:Book与Writer互为引用
 从Book角度看,Book是引用端,Writer是被引用端;
 相反从Writer的角度看,Writer是引用端,而Book是被引用端(^_^)
 在setWriter()方法中,除了执行引用端的basicSetWriter()方法外,还执行了被引用端(Writer端)的eInverseRemove和eInverseAdd方法,这样通过Book端的操作,实际上是影像了两端的结果。我们可以编写代码来进行测试:
LibraryFactory factory=LibraryFactory.eINSTANCE;//得到创建实体类的工厂类 Book book=factory.createBook();//创建Book对象 Writer writer=factory.createWriter();//创建Writer对象 writer.setName("zhangsan"); book.setTitle("emf demo"); book.setPage(123); book.setWriter(writer);//只是在book端执行了set操作 List<Book> books=writer.getBooks();//在writer端依然能够遍历到book for(Book b:books){ System.out.println(b.getTitle()); }

虽然我们只是在book端执行了setWriter方法,而没有在writer端执行添加book的方法,但是在writer端依然能够遍历到Book对象。

聚合引用:
所谓聚合引用,就是一个对象是另一个对象的容器;比如上面的Library和Book,Library便是Book的容器。
在EMF中,容器和它所包含的对象存储在同一资源文件中,因此在加载的时候不需要使用代理机制。
在聚合引用中,每个对象只能有一个容器,因此在将一个对象加入到某个容器之前,要将该对象从现在的容器中移除出去。为了实现该功能,EObjectImpl类中设计了一个成员变量eContainer用来表示实体类对象的容器,这样我们就可以通过book.eContainer()方法来得到Library对象。

注:EMF所生成的变量或方法都会含有@generated标注,当执行regenerate操作时EMF只会覆盖含有@generated标注的方法,而自己手工添加的方法不会被覆盖。

下一篇将会讲解EMF所生成的适配器类。