EMF(Eclipse Modeling Framework)是一个模型架构和代码生成工具,它可以用来建构以结构化数据为基础的工具或者应用。作为MDA和Eclipse的结合体,它发展迅速,IBM的大部分工具产品如RSA等都将建立在它的基础之上。
样例
我们将构建一个简单的手机库管工具,它只维护种类信息,并不存储数量库位等。通过该工具,我们可以添加,删除,修改主机及配件;维护主机,配件的功能;并且可以通过拖放将主机和相关配件组成一种配置。
建立模型
EMF通过JET和JMERGE来实现支持MDA,它可以从annotated Java, UML, or XML Schema三种模型生成Eclipse
plug-in代码。我们将用Rational Rose建立UML模型来作为系统模型。
图1:简单的手机库管工具的UML模型表示
将模型导入Eclipse中
建立EMF工程,在Wizard中会提示选择初始化的模型内容。从Rose中读取类模型以后,将生成了一个工程,其中包含两个文件:.ecore和.genmodel。
Ecore文件代表我们的模型本身,你可以通过修改它来改变模型。比如改变属性的类型,类之间的关系等。你也完全可以通过手动的方式建立ecore文件来创建整个模型。我们可以看到,在ecore模型中,所有的类被转换为EClass;聚合关系变成了一个EClass包含其它EClass作为EReference,例如主机、配置、配件都作为库存的EReference。在这里,我们需要修改EReference的Containment属性,缺省的值是false。Containment属性是在持久化时的一个重要属性,它表明存储时数据的包含关系,如果全部保留为fasle,将不会有任何信息被存储到文件中,除了顶级节点:库存。应该以谁创建,谁包含为原则。比如库存可以创建主机、配件、配置,那么这些库存的EReference的Containment属性都应该设为true;而配置并不负责创建主机和配件,只是从库存中现有的主机和配件拖放过来的,那么它下面的主机,配件EReference的Containment属性都应该为false。
以XMI存储为例,修改后存储的格式应该为:
<?xml version="1.0"
encoding="UTF-8"?> <mobiles:库存 xmi:version="2.0"
xmlns:xmi="http://www.omg.org/XMI" xmlns:mobiles="http:///mobiles.ecore">
<配置 配件="//@配件.0" 主机="//@主机.0"/>
<配件> <功能/> </配件> <主机>
<功能/> </主机> </mobiles:库存> |
Genmodel文件主要维护着一些与生成代码相关的设置,比如说,某个属性可不可以修改。大部分的属性都不用修改,我们要决定的仍然是EReference的属性:Children,Create
Child,Notify。
Notify是表明它应不应该将在这个包含节点中有关自身的改变通知给其它的相同节点。比如某个主机V3在库存中创建之后被拖到某个配置里,这样在整个树上就存在两个V3:一个是V3本身,另一个是它的一个引用。如果将配置中主机的Notify属性设为true后,改变配置中引用的V3价格,它就会把这个改变通知给V3自身,实现同步。通常情况下应将所有的EReference的Notify设为true。
按照下表做出设置:
包含节点 EReference Children
Create Child Notify
库存 主机
配件
配置 True True True
主机 功能 True True True
主机 配件 True False True
配件 功能 True True True
配置 主机
配件 True False True |
生成代码并运行
用EMF generator(缺省的Eidtor)打开genmodel文件,原来的工程中会生成Model代码,两个新的工程Edit和Editor工程会被生成,
Edit工程包含了一些方便编辑Model的代码,Eidtor工程中大都是UI部分的代码,不在本文范围之内。
.mobiles文件生成后,将会用Mobiles Model Editor打开。现在就可以在库存中添加主机配件等,并将其拖到某个配置中去。这里所谓的Mobiles在缺省情况下与你的Rose文件的名字相同,在Editor工程的Plugin.xml的editor扩展中可以看到生成的后缀。
Model Plug-in
EMF是从作为MOF规范在Eclipse的一个实现开始的,随后通过大量的运用在工具的实现,EMF成为一个有效的MOF
API的一个核心子集的Java实现。EMF中的元数据被称为Ecore。
灰色的类代表抽象类,所有的类都继承自Eobject。EPackage包含关于模型类(EClass)和数据类型(EDataType)的信息。EClass描述一个建模的类,并且指定属性和参考以描述实例的数据。EAttribute描述简单数据,它由一个EDataType来指定。EReference描述一个类之间的关联;它的类型是一个Eclass。EFactory包含创建模型元素的方法。更多的关于Ecore的描述请参考Eclipse官方网站。
包和工厂
前面说过EPackage包含所有关于EClass和EDataType的信息,参看生成的代码mobiles. MobilesPackage,可以看到Eclass的feature
ID声明:
int 主机 = 0;
int 配置 = 1;
feature ID仅仅是对所有的类元素(不包含类型信息)的一个int类型的编号,有了它可以使你很快的区分出是哪个类的哪个属性。例如在某个类的属性值发生改变后,它的监听者会收到一个通知,通知里就包含了改变了的属性的feature
ID,这时你就可以简单的通过一个switch方法来实现分派。
MobilesPackage中另外的一些是关于类型的,例如:
EClass get主机();
EAttribute get主机_Name();
在某些情况下这些元数据是非常重要的,比如在决定某个属性的编辑框类型的时候。当然你也可以直接调用某个类型的create方法来创建对象:
适配器
EMF最重要的一个特性就是它对Adapter的定义。在EMF中Adapter有两个功能,第一个是类似于Observer的功能,它可以监听目标对象的变化然后做出相应的反映;另一个是通过它可以使目标对象不用继承某个接口或者父类来实现其特有的功能。这一节我们会结合Model
Plug_in的代码来探讨Adapter的这两个功能。
Observer
通常的Observer模式需要至少两个类,监听者和目标对象。EMF中,监听者称为Adapter,目标对象称为Notifier。每个Notifier都拥有一个eAdapters列表,维护着所有监听者的类型,一旦Notifier发生变化,它会遍历eAdapters列表将变化通知给列表中的每一个Adapter。所以假如某个Adapter想要监听这个Notifier,只需要将自己添加到Notifier的eAdapters中。
EMF中所有的对象都直接或间接继承自Eobject,如同java中所有的类都继承自Object一样。而Eobject类已经实现了Notifier接口,所以所有的EMF的类都可以是Notifier。
Adapter需要自己来实现AdapterImpl,需要实现的方法主要有两个,isAdapterForType和notifyChanged。IsAdapterForType是用来判断这个Adapter能不能监听某个Notifier,notifyChanged包含着一旦Notifier发生了变化这个Adapter所要做出的所有相应。假设我们需要一个Adapter,它来监听主机的变化,一旦任何关于主机的增删改操作产生就将改变记录在本地文件中。
Notification类是由Notifier发出来的,它包含着发出者getNotifier(),改变之前的旧值getOldValue()和改变后的新值getNewValue()。象如下示例这样使用这个Adapter:
主机 machine= MobilesFactory. EINSTANCE. create主机();
主机Adapter adapter = new主机Adapter();
machine.eAdatpters().add(adapter);
machine.setName(“V3”);
你要自己实例化Adapter,并且把它添加到Notifier的eAdaters里去。假如在其它的某个地方你有同样的需求,但你不确定“主机Adapter”是否已经被添加到“主机”的eAdatpters里了,你就需要遍历现有的adapter,来判断是否已被添加。然后决定创不创建新的“主机Adapter”。事实上EMF提供了AdapterFactory来简化你的工作。可以参照生成的代码mobiles.util.
MobilesAdapterFactory.java。它只是一个框架,用来根据Notifier的类型生成相应的Adatpter并将其注册到Notifier的eAdapters列表中去。它还借用了MobilesSwitch类的分派功能。
MobilesSwitch只做了一件事情,那就是根据Notifier的类型调用不同的case*方法。例如,如果Notifier是“主机”,它会去调用case主机()方法。MobilesAdapterFactory中则实现了case主机()方法,调用create主机Adapter()来创建“主机Adapter”。
继续上面的例子,我们实现一个AdapterFactory:
public class MobilesAdapterFactoryImpl
extends MobilesAdapterFactory{
public static MobilesAdapterFactoryImpl
INSTANCE = new MobilesAdapterFactoryImpl ();
public Adapter create主机Adapter() {
return new主机Adapter();
}
} |
用的时候就很简单:
主机 machine= MobilesFactory.
EINSTANCE. create主机();
MobilesAdapterFactoryImpl.INSTANCE.adapt(machine, 主机Adapter.class);
machine.setName(“V3”); |
factory会遍历machine的eAdapters,如果没有“主机Adapter”则创建一个添加进去,否则返回已有的。
行为扩展
我们把日志功能提取出来,成为一个类:
public interface Logger{
public void log();
} |
其实我们希望的是“主机”也能有日志功能,可以在自己改变之后将变化记录下来。以往我们要做的就是让“主机”继承Logger,然后实现log()方法记录下自己这种特定类型的信息。但有的时候通过继承来扩展行为并不是那么舒服,比如继承的关系是死的,你不能在你不需要的时候去掉这些扩展行为。通过Adapter你可以随心所欲的控制这些扩展行为。
public
class 主机Adapter extends AdapterImpl implements Logger
{
public void notifyChanged(Notification notification) {
if (notification.getNotifier() instanceof 主机)
log();
}
public boolean isAdapterForType(Object type)
{
return type == 主机.class;
}
public void log(){
//log the name, band,year,changed property name and its old
value and new value
}
} |
用的时候同上边一样:
主机 machine= MobilesFactory.
EINSTANCE. create主机();
MobilesAdapterFactoryImpl.INSTANCE.adapt(machine, 主机Adapter.class);
machine.setName(“V3”); |
这个时候任何“主机”的变化都会引起“主机Adapter”执行扩展了的log()方法,这一点与AOP很像。
Model Class
我们现在把目光关注到Model Plug_in中最重要的部分。首先是Model的接口,例如接口“主机”,它的定义非常简单,就是些属性的get,set方法。get方法很简单,而set方法则比较有趣:
public void setName(String
newName) {
String oldName = name;
name = newName;
if (eNotificationRequired())
eNotify(new ENotificationImpl(this,
Notification.SET,
MobilesPackage.主机__NAME,
oldName, name));
} |
它不仅仅将类变量赋予了新值,还在需要Notify的时候发出了一个Notification。如同上一节所讲的,这个Notification将包含发出者,改变的方式,改变的属性标识(feature
ID)、旧值和新值。
随后是一些e开头的方法,他们都是用作反射的。例如eGet,根据feature ID返回相应的属性值;eSet,根据feature
ID设置相应的属性值;eUnset,根据feature ID设置相应的属性值为缺省值;eIsSet,根据feature ID判断相应的属性是否被设置过;
限于篇幅的关系,EMF中还有很多高级的功能没有在本文中介绍,比如持久化,Command pattern等;Edit工程里更是包含了集万千宠爱于一身的ItemProvider,它们都非常重要,希望以后有机会能与读者共同探讨。 |