Ran Wei/MDE 系列
English
基于 Eclipse Epsilon 的模型驱动工程 — Ran Wei

构建与查看模型

用 Flexmi 的 XML 和 YAML 写法编写模型,按名称解析引用,排查载入问题,并查看模型图。

3 小时3 个时段8 个可运行示例Epsilon Playground

完成后你能够

  • 根据 Emfatic 元模型创建嵌套的 Flexmi 模型。
  • 用 XML 和 Epsilon 2.8 的 YAML 写法编写等价的小模型。
  • 用完全限定名解析引用,并解释简短名称为何可能含糊。
  • 识别模型载入警告,并区分警告与程序输出。
  • 使用 Playground 图示及简单的 Emfatic 标注查看模型。

开始之前

完成第 1、2 模块,或已了解 Ecore 的类、包含和引用。无需本地安装。

下载本模块全部示例(zip)

目录

学习计划

3 小时

时间为估计值,包含运行示例的时间。进度保存在当前浏览器,中英文版本共享。

时段 160 分钟
写出结构
时段 260 分钟
连接与排查
时段 360 分钟
查看与练习
1

Flexmi 与元模型

第 2 模块用 Emfatic 定义了一门组件与连接器语言。元模型(metamodel)列出它允许的元素种类(Architecture、Component、InPort、OutPort、Connector)、元素拥有的值以及元素之间的关系。本模块要用这门语言编写模型(model)。模型记录具体事物:名为 Alarm 的架构、带有 open 输出端口的 OrGate,以及通往另一个端口的连接器。

我们用 Flexmi 编写模型。它是 EMF 模型的一种简洁记法,有 XML 和 YAML 两种写法。Flexmi 借助元模型来解释文本,因此一份短小的文件就能创建许多相连的模型元素。之后,Epsilon 可以查询、验证或转换这个模型。这里我们用简短的 EOL 查询显示实际载入了什么;第 4 模块会系统讲解 EOL。

学习顺序是:读元模型、写模型、运行查询,然后查看模型图。每个示例下面的输出都由构建过程中的 Epsilon 2.8.0 实际运行得到。浏览器里的 Playground 也能运行示例,但它不会显示所有模型载入警告。第 5 节会故意制造一个这样的警告。

核心思想

Flexmi 文件是模型的一种具体语法(concrete syntax)。元素类型、特性和关系来自元模型;改变记法并不会改变这些概念。

2

XML 嵌套与元素

打开第一个示例,对照两个可见的文件。components.emf 声明了 Architecture、它的 components 和 connectors 包含引用,以及各个端口类。alarm.flexmi 以 <?nsuri components?> 开头,用来选择该元模型的命名空间。随后,<architecture name="Alarm"> 创建一个 Architecture 实例。

示例用 XML Flexmi 编写报警器在 Playground 中打开 ↗
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" type="DIGITAL"/>
    <inPort name="window" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" type="DIGITAL"/>
    <outPort name="sound" type="DIGITAL"/>
  </component>
  <component name="Siren">
    <inPort name="sound" type="DIGITAL"/>
  </component>
  <connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
  <connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
components.emf
@namespace(uri="components", prefix="components")
package components;

enum PortType {
  DIGITAL;
  ANALOG;
}

class Architecture {
  attr String[1] name;
  val Component[*]#architecture components;
  val Connector[*] connectors;
}

class Component {
  attr String[1] name;
  ref Architecture#components architecture;
  val Port[*] ports;
}

abstract class Port {
  attr String[1] name;
  attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}

class Connector {
  ref OutPort source;
  ref InPort target;
}
其他文件: inspect.eol
inspect.eol
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
以下输出由构建本页时的 Epsilon 2.8.0 实际运行得到。Playground 中的提示措辞可能有所不同,并且不显示关于模型的警告。
输出
Architecture: Alarm
Components: 3
Ports: 7
Connectors: 2

从外到内阅读 XML。每个 <component> 都嵌套在 <architecture> 中,因为架构包含组件。每个 <inPort> 或 <outPort> 都嵌套在其组件中,因为 Component.ports 是包含引用。两个 <connector> 也位于架构内。它们的 source 和 target 属性是普通引用,并非在连接器内部新建的端口。

此模型中的三个组件共包含七个端口;架构还包含两个连接器。查询打印的正是这些数量。运行查询,然后删除 Siren 元素和指向它的连接器。再次运行前,先预测各项数量。一次小小的文本修改就能改变程序所读到的对象关系。

Flexmi 根据元模型中的类和包含特性匹配 XML 标签。架构内的 <component> 会成为一个 Component,组件内的 <inPort> 会成为一个 InPort。这种匹配有意设计得比较宽松,因此不要把标签的拼写当作严格的解析检查。如果模型行为出乎意料,请查看图示,并查询实际类型与取值,不要假定所有拼写错误都会报错。Flexmi 文档解释了匹配规则。

3

按名称建立引用

包含关系让每个端口在模型树中都有位置,但连接器要连接不同分支的端口。Flexmi 用元素的标识符解析普通引用。如果没有专门的 ID 属性,它会在可用时使用 name。完全限定名(fully qualified name)从具名根元素出发,沿具名容器一直走到目标。

报警器中有两个名为 open 的端口:一个是 OrGate 的输出,另一个是 AndGate 的输入。单写 open 无法清楚地指明其中哪一个。因此,第一个连接器的 source 是 Alarm.OrGate.open,target 是 Alarm.AndGate.open。查询在打印端口名称时也打印所属组件,让两者的区别一目了然。

示例跨组件解析完整名称在 Playground 中打开 ↗
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" type="DIGITAL"/>
    <inPort name="window" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" type="DIGITAL"/>
    <outPort name="sound" type="DIGITAL"/>
  </component>
  <component name="Siren">
    <inPort name="sound" type="DIGITAL"/>
  </component>
  <connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
  <connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
其他文件: inspect.eol, components.emf
inspect.eol
for (c in Connector.all) {
  (c.source.eContainer().name + "." + c.source.name + " -> " +
   c.target.eContainer().name + "." + c.target.name).println();
}
components.emf
@namespace(uri="components", prefix="components")
package components;

enum PortType {
  DIGITAL;
  ANALOG;
}

class Architecture {
  attr String[1] name;
  val Component[*]#architecture components;
  val Connector[*] connectors;
}

class Component {
  attr String[1] name;
  ref Architecture#components architecture;
  val Port[*] ports;
}

abstract class Port {
  attr String[1] name;
  attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}

class Connector {
  ref OutPort source;
  ref InPort target;
}
以下输出由构建本页时的 Epsilon 2.8.0 实际运行得到。Playground 中的提示措辞可能有所不同,并且不显示关于模型的警告。
输出
OrGate.open -> AndGate.open
AndGate.sound -> Siren.sound

用同样的方法追踪第二个连接器:从 Alarm.AndGate.sound 到 Alarm.Siren.sound。这两个端口的短名称也相同。在 Epsilon 2.8.0 中,应使用从根元素开始的完整路径,以便可靠地解析引用。较新版的 Flexmi 文档介绍了后来加入的部分路径;本系列使用 2.8.0 构建并检查示例。

陷阱

如果重命名组件或端口,也要更新每一处引用它的路径。XML 嵌套负责创建元素,但连接器的 source 和 target 文本仍必须能解析到相应元素。

4

Epsilon 2.8 中的 YAML

Flexmi 也能读取 YAML。下一个示例用同一个元模型描述同一个报警器,运行的也是与 XML 示例相同的查询。比较实际输出:架构、组件、端口与连接器的数量完全一致。

示例用 Epsilon 2.8 的 YAML 写法描述同一个报警器在 Playground 中打开 ↗
alarm.flexmi
?nsuri: components
architecture:
- name: Alarm
- component:
  - name: OrGate
  - inPort: {name: door, type: DIGITAL}
  - inPort: {name: window, type: DIGITAL}
  - outPort: {name: open, type: DIGITAL}
- component:
  - name: AndGate
  - inPort: {name: open, type: DIGITAL}
  - inPort: {name: armed, type: DIGITAL}
  - outPort: {name: sound, type: DIGITAL}
- component:
  - name: Siren
  - inPort: {name: sound, type: DIGITAL}
- connector: {source: Alarm.OrGate.open, target: Alarm.AndGate.open}
- connector: {source: Alarm.AndGate.sound, target: Alarm.Siren.sound}
其他文件: inspect.eol, components.emf
inspect.eol
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
components.emf
@namespace(uri="components", prefix="components")
package components;

enum PortType {
  DIGITAL;
  ANALOG;
}

class Architecture {
  attr String[1] name;
  val Component[*]#architecture components;
  val Connector[*] connectors;
}

class Component {
  attr String[1] name;
  ref Architecture#components architecture;
  val Port[*] ports;
}

abstract class Port {
  attr String[1] name;
  attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}

class Connector {
  ref OutPort source;
  ref InPort target;
}
以下输出由构建本页时的 Epsilon 2.8.0 实际运行得到。Playground 中的提示措辞可能有所不同,并且不显示关于模型的警告。
输出
Architecture: Alarm
Components: 3
Ports: 7
Connectors: 2

请注意 2.8 的写法。?nsuri: components 选择元模型。architecture: 下面是一组条目,记录根元素的属性及其包含的元素。- component: 引入一个组件;其下缩进的条目设置组件的 name,并加入端口。连接器可以简写为 - connector: {source: ..., target: ...}。缩进很重要,因此要统一使用空格。

新版 Flexmi 文档展示的是 Epsilon 2.9 及以后修订过的 YAML 映射,使用 $nsuri。本系列的运行器采用 2.8,而实时 Playground 检查也确认了这些示例能按旧版写法运行。因此修改它们时,请遵循旧版 YAML 参考文档。把 2.9 的示例直接放进 2.8 环境,可能得不到预期结果。

对于这个小架构,XML 的树形结构很容易浏览。YAML 在大部分内容都是名称和值时也很方便。两者是同一抽象结构的不同具体语法;选择团队能够可靠阅读和维护的写法。

5

警告与符合性

下一个模型在第二个连接器的目标名称中犯了一处错误:写成 Alarm.Siren.sond,而正确写法是 Alarm.Siren.sound。模型中存在 Siren.sound 端口,却没有一个名为 sond 的元素。

示例无法解析的端口引用在 Playground 中打开 ↗
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" type="DIGITAL"/>
    <inPort name="window" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" type="DIGITAL"/>
    <outPort name="sound" type="DIGITAL"/>
  </component>
  <component name="Siren">
    <inPort name="sound" type="DIGITAL"/>
  </component>
  <connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
  <connector source="Alarm.AndGate.sound" target="Alarm.Siren.sond"/>
</architecture>
其他文件: inspect.eol, components.emf
inspect.eol
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
components.emf
@namespace(uri="components", prefix="components")
package components;

enum PortType {
  DIGITAL;
  ANALOG;
}

class Architecture {
  attr String[1] name;
  val Component[*]#architecture components;
  val Connector[*] connectors;
}

class Component {
  attr String[1] name;
  ref Architecture#components architecture;
  val Port[*] ports;
}

abstract class Port {
  attr String[1] name;
  attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}

class Connector {
  ref OutPort source;
  ref InPort target;
}
以下输出由构建本页时的 Epsilon 2.8.0 实际运行得到。Playground 中的提示措辞可能有所不同,并且不显示关于模型的警告。
输出
Model warning (line 17): Could not resolve target Alarm.Siren.sond for reference target (target)
Architecture: Alarm
Components: 3
Ports: 7
Connectors: 2

按顺序阅读实际输出。本地运行器先给出一条模型警告,指出行号和无法解析的 target,然后 EOL 查询打印了与有效报警器完全相同的数量。连接器元素本身仍然存在,因此查询数到了两个连接器,尽管第二个没有目标。仅仅统计元素,无法证明接线正确。

在 Playground 中,控制台只显示四行数量,不显示这条模型警告。切换到模型面板的图示:出错的连接器缺少指向 Siren.sound 的箭头。图示能暴露这条断开的关系。第 6 节会介绍怎样改变图示的表现形式。

其他错误需要其他检查方式。Flexmi 可能把拼错的标签模糊匹配到某个类或特性;如果某个 XML 属性在元模型的所有类中都没有声明,本系列的本地运行器会报告它,但 Playground 不会显示该警告。像 Component[+] 这样的数量约束虽然已声明,单纯运行查询仍不是完整的有效性检查,第 2 模块已经演示了这一点。对于重要的规则,应检查模型并明确编写验证。第 5 模块会用 EVL 表达输入是否连接、信号类型是否兼容等领域规则。

提示

排查问题时从模型本身开始:先核对元模型名称,再核对嵌套、取值以及每条非包含引用的路径。把图示与文本对照,并在相信程序看似合理的输出之前读完所有警告。

6

图示与语法

模型并不等同于它的 XML 或 YAML 文件。文件只是书写模型的方法。Playground 还能画出载入的模型。默认情况下,它显示对象图,其中有模型元素以及元素间的引用。Emfatic 标注(annotation)可以指定更有针对性的图形表现形式,而不改变模型的概念或 EOL 查询结果。

下面的示例在要画成具名节点的类上加入 @node(label="name"),在 Connector 上加入 @edge(source="source", target="target"),让连接器实例成为端口之间的边。Flexmi 文件与之前有效的报警器相同;变化的只有 Emfatic 中控制显示的标注。

示例选择图中的节点和边在 Playground 中打开 ↗
components.emf
@namespace(uri="components", prefix="components")
package components;

enum PortType {
  DIGITAL;
  ANALOG;
}

class Architecture {
  attr String[1] name;
  val Component[*]#architecture components;
  val Connector[*] connectors;
}

@node(label="name")
class Component {
  attr String[1] name;
  ref Architecture#components architecture;
  val Port[*] ports;
}

abstract class Port {
  attr String[1] name;
  attr PortType type;
}
@node(label="name")
class InPort extends Port {}
@node(label="name")
class OutPort extends Port {}

@edge(source="source", target="target")
class Connector {
  ref OutPort source;
  ref InPort target;
}
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" type="DIGITAL"/>
    <inPort name="window" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" type="DIGITAL"/>
    <outPort name="sound" type="DIGITAL"/>
  </component>
  <component name="Siren">
    <inPort name="sound" type="DIGITAL"/>
  </component>
  <connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
  <connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
其他文件: inspect.eol
inspect.eol
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
以下输出由构建本页时的 Epsilon 2.8.0 实际运行得到。Playground 中的提示措辞可能有所不同,并且不显示关于模型的警告。
输出
Architecture: Alarm
Components: 3
Ports: 7
Connectors: 2

在 Playground 中打开它,切换到模型面板的图示。找到两个连接器形成的箭头。然后切回文本,删掉 @edge 标注。连接器仍在模型中,EOL 数量也不变,但图示会采用另一种表现方式。这些标注是 Playground 的功能;本地运行器会读取元模型和模型,却不会通过输出检查图示外观。图形语法标注指南列出了可用选项。

元模型面板也有图示。它展示的是类与引用,而不是这个报警器里的具体组件与端口。请分清两个层次:元模型图解释这门语言;模型图展示这门语言的一个实例。XML、YAML 和图示则是模型抽象结构的不同具体视图。

7

练习

练习 1★★★概念4 分钟

类型还是实例? 在 XML 示例中,将 Component、OrGate、OutPort 和 Alarm.OrGate.open 分别归为元模型类型或模型元素。哪个模型元素是 OutPort 的实例?

查看解答

Component 和 OutPort 是元模型类型。OrGate 和 Alarm.OrGate.open 是模型元素。Alarm.OrGate.open 是 OutPort 的实例;OrGate 是 Component 的实例。

练习 2★★★概念4 分钟

阅读模型树。 在 XML 报警器中,谁包含 AndGate、它的 armed 输入,以及第二个连接器?连接器包含它所连接的任一端口吗?

查看解答

Alarm 包含 AndGate 和第二个连接器;AndGate 包含 armed。连接器只引用其源端口和目标端口,并不包含它们。

练习 3★★★概念4 分钟

两个名为 open 的端口。 写出第一个连接器的源端口和目标端口的完整路径。为什么这里单写 open 不合适?

查看解答

源端口是 Alarm.OrGate.open,目标端口是 Alarm.AndGate.open。两个端口的短名称都是 open,无法据此区分。在 Epsilon 2.8.0 中,完整路径既能区分它们,也能可靠地解析。

练习 4★★★编程6 分钟

用 XML 建立恒温器模型。 使用同一个元模型创建 Thermostat 架构。把 Sensor.reading 接到 Filter.raw,再把 Filter.smoothed 接到 Display.value。将四个端口都设为 ANALOG,运行数量查询。先预测输出的四行。

查看解答

这个架构有三个组件、四个端口和两个连接器。完整模型与实际输出如下:

参考解答用 XML 构建恒温器在 Playground 中打开 ↗
alarm.flexmi
<?nsuri components?>
<architecture name="Thermostat">
  <component name="Sensor">
    <outPort name="reading" type="ANALOG"/>
  </component>
  <component name="Filter">
    <inPort name="raw" type="ANALOG"/>
    <outPort name="smoothed" type="ANALOG"/>
  </component>
  <component name="Display">
    <inPort name="value" type="ANALOG"/>
  </component>
  <connector source="Thermostat.Sensor.reading" target="Thermostat.Filter.raw"/>
  <connector source="Thermostat.Filter.smoothed" target="Thermostat.Display.value"/>
</architecture>
其他文件: inspect.eol, components.emf
inspect.eol
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
components.emf
@namespace(uri="components", prefix="components")
package components;

enum PortType {
  DIGITAL;
  ANALOG;
}

class Architecture {
  attr String[1] name;
  val Component[*]#architecture components;
  val Connector[*] connectors;
}

class Component {
  attr String[1] name;
  ref Architecture#components architecture;
  val Port[*] ports;
}

abstract class Port {
  attr String[1] name;
  attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}

class Connector {
  ref OutPort source;
  ref InPort target;
}
以下输出由构建本页时的 Epsilon 2.8.0 实际运行得到。Playground 中的提示措辞可能有所不同,并且不显示关于模型的警告。
输出
Architecture: Thermostat
Components: 3
Ports: 4
Connectors: 2
练习 5★★★编程7 分钟

把恒温器转换为 YAML。 用 Epsilon 2.8 的 YAML 写法表达上一题的解答。保留相同的嵌套结构与完整的连接器路径。数量查询会变化吗?

查看解答

变化的只有具体语法;查询和各项数量都不变。?nsuri 行及序列条目采用旧版 2.8 映射:

参考解答用 YAML 构建恒温器在 Playground 中打开 ↗
alarm.flexmi
?nsuri: components
architecture:
- name: Thermostat
- component:
  - name: Sensor
  - outPort: {name: reading, type: ANALOG}
- component:
  - name: Filter
  - inPort: {name: raw, type: ANALOG}
  - outPort: {name: smoothed, type: ANALOG}
- component:
  - name: Display
  - inPort: {name: value, type: ANALOG}
- connector: {source: Thermostat.Sensor.reading, target: Thermostat.Filter.raw}
- connector: {source: Thermostat.Filter.smoothed, target: Thermostat.Display.value}
其他文件: inspect.eol, components.emf
inspect.eol
("Architecture: " + Architecture.all.first().name).println();
("Components: " + Component.all.size()).println();
("Ports: " + Port.all.size()).println();
("Connectors: " + Connector.all.size()).println();
components.emf
@namespace(uri="components", prefix="components")
package components;

enum PortType {
  DIGITAL;
  ANALOG;
}

class Architecture {
  attr String[1] name;
  val Component[*]#architecture components;
  val Connector[*] connectors;
}

class Component {
  attr String[1] name;
  ref Architecture#components architecture;
  val Port[*] ports;
}

abstract class Port {
  attr String[1] name;
  attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}

class Connector {
  ref OutPort source;
  ref InPort target;
}
以下输出由构建本页时的 Epsilon 2.8.0 实际运行得到。Playground 中的提示措辞可能有所不同,并且不显示关于模型的警告。
输出
Architecture: Thermostat
Components: 3
Ports: 4
Connectors: 2
练习 6★★★编程5 分钟

修复并查看。 打开“无法解析的端口引用”示例,在 Flexmi 文本中找到错误的目标,修复后重新运行。将实际输出与修复后的参考解答比较,并在 Playground 的模型图中检查第二个连接器。

查看解答

把 Alarm.Siren.sond 改成 Alarm.Siren.sound。本地警告消失,连接器的两端都能解析。此查询打印实际的端点名称,让修复后的关系可见:

参考解答修复缺失的目标端口在 Playground 中打开 ↗
alarm.flexmi
<?nsuri components?>
<architecture name="Alarm">
  <component name="OrGate">
    <inPort name="door" type="DIGITAL"/>
    <inPort name="window" type="DIGITAL"/>
    <outPort name="open" type="DIGITAL"/>
  </component>
  <component name="AndGate">
    <inPort name="open" type="DIGITAL"/>
    <inPort name="armed" type="DIGITAL"/>
    <outPort name="sound" type="DIGITAL"/>
  </component>
  <component name="Siren">
    <inPort name="sound" type="DIGITAL"/>
  </component>
  <connector source="Alarm.OrGate.open" target="Alarm.AndGate.open"/>
  <connector source="Alarm.AndGate.sound" target="Alarm.Siren.sound"/>
</architecture>
其他文件: inspect.eol, components.emf
inspect.eol
for (c in Connector.all) {
  (c.source.eContainer().name + "." + c.source.name + " -> " +
   c.target.eContainer().name + "." + c.target.name).println();
}
components.emf
@namespace(uri="components", prefix="components")
package components;

enum PortType {
  DIGITAL;
  ANALOG;
}

class Architecture {
  attr String[1] name;
  val Component[*]#architecture components;
  val Connector[*] connectors;
}

class Component {
  attr String[1] name;
  ref Architecture#components architecture;
  val Port[*] ports;
}

abstract class Port {
  attr String[1] name;
  attr PortType type;
}
class InPort extends Port {}
class OutPort extends Port {}

class Connector {
  ref OutPort source;
  ref InPort target;
}
以下输出由构建本页时的 Epsilon 2.8.0 实际运行得到。Playground 中的提示措辞可能有所不同,并且不显示关于模型的警告。
输出
OrGate.open -> AndGate.open
AndGate.sound -> Siren.sound
8

自测

1
XML Flexmi 模型用什么选择 Emfatic 包?
2
为什么 <outPort> 嵌套在 <component> 内?
3
连接器的 target="Alarm.Siren.sound" 起什么作用?
4
哪一行属于这里的 Epsilon 2.8 YAML 示例?
5
“无法解析的端口引用”示例数出了两个连接器,这能证明什么?
6
图示示例中的 @edge(source="source", target="target") 改变了什么?
7
元模型图展示什么?
9

延伸阅读