会让我写这篇Blog的原因实在是因为OCL太好玩了,而且OCL的威力也逐渐让我有愈来愈深的体认,OCL现在已经成为我个人认为最重要的语言之一,我应该称OCL是『程式语言』?还是『正规语言』?还是『模型转换语言』?这我也不知道,这是因为OCL虽然是OMG的标准『正规语言』,大多数的人对于OCL的观念还停留在OCL是使用于UML模型中定义条件,限制,或是使用于MDA之中。这两者的应用都希望借由OCL能够更精确的定义模型的意义,以及借由OCL撰写和特定平台,语言,技术无关的企业逻辑,以便在实作或是应用MDA的模型转换过程中能够顺利产生有意义的结果模式,例如从PIM转换到PSM,从PSM转换到特定的程式语言,例如Java,C#或是Delphi等。
不过OCL在一些软体厂商,例如Borland,不断的改善之下,在原本只是唯读的OCL加入延伸的功能,以便让OCL具备修改和写入的能力。为什么要这样做? 请想一想,如果要在各种模型使用OCL做为和模型,平台,技术无关的中立语言,那么OCL只有唯读能力是不足的,例如在叙述图中使用OCL做为定义状态改变的标准语言,那么当状态改变时如何能够使用唯读的OCL来改变状态? 那要改变使用特定的程式语言来叙述吗?如果是这样的话,为什么还要使用OCL? 再想想,于模型转换时,我们需要有来源模型以及转换后的结果模型。结果模型是根据转换规则而产生的,因此如果我们决定使用OCL来定义,对映模型转换的规则,那么OCL必须具备建立结果模型的能力,否则我们要使用什么语言来叙述结果模型?
因此Borland的ECO便根据OCL加以延伸形成Action Language,其中最重要的就是允许开发人员使用OCL进行修改,写入或是建立物件的功能。
例如OCL借由加入:=运算元而拥有了指定功能之后,在状态图中我们才能够改变应用程式执行时的状态:
self.FirstName := home.owners->first.FirstName
至于Together 2006就对OCL加入了更多的能力了,因为Together 2006选择了使用OCL语言来定义模式和模式之间转换的规则。严格的说,应该是指Together 2006借由强化OCL语法让开发人员能够定义MetaModel和MetaModel之间对映的关系,如此一来Together 2006就能够借由OCL和MetaModel来转换不同的模型了。 这可以由我在上海/成都时展示的范例来说明。
在2005年12月时,我在上海/成都展示了如何把一个由BPMN叙述的企业流程转换为UML的UseCase。BPMN模型通常是由商业人士使用来叙述企业领域的商业流程,当领域专家(Domain Expert)使用BPMN叙述了特定的企业流程之后,必须交由IT人员来开发系统。但是对于IT人员来说,BPMN模型可能很陌生,而且大多数的IT开发工具都只接受UML模型而不是更抽象的BPMN模型。因此我们需要把领域专家实作的商业模型转换为IT人员熟悉的UML模型。
当时我使用的BPMN模型如下所示:
我希望把这个BPMN的模型转换为UML的UseCase,如下所示:
这要如何做到? 其实这正是MetaModel到MetaModel之间转换的范例,要转换这两个特定的模型非常的简单,我们只需要定义BPMN的MetaModel之间的每一个元素如何对映到UML的MetaModel的每一个元素,那么一旦这个对映规则定义好了之后,任何的BPMN就可以转换为UML之间的模型了,下图就说明了这个观念。
001 transformation Bpmn_To_Uml;
002
003 metamodel 'http://www.borland.com/together/2005/bpmn';
004 metamodel 'http://www.borland.com/together/uml';
005 metamodel 'http://www.borland.com/together/uml20';
006
007 mapping main(in model: bpmn::BpmnProcessPool): uml::together::Model {
008 init {
009 var actors := model.lanes->select(lane | lane.oclIsKindOf(bpmn::BpmnLane))->oclAsType(Sequence(bpmn::BpmnLane));
010 var tasks := model.lanes.flowObjects->select(lane | lane.oclIsKindOf(bpmn::BpmnTask))->oclAsType(Sequence(bpmn::BpmnTask));
011 }
012 object {
013 ownedMembers := actors->collect(a | makeActor(a))->asOrderedSet();
014 ownedMembers += tasks->collect(t | makeUseCase(t))->asOrderedSet();
015 }
016 }
017
018
019 mapping makeActor(in lane: bpmn::BpmnLane): uml20::usecases::Actor {
020 object {
021 name := lane.name;
022 description := lane.documentation;
023
024 }
025 }
026
027 mapping makeUseCase(in task: bpmn::BpmnTask): uml20::usecases::UseCase {
028 object {
029 name := task.name;
030 description := task.documentation;
031 }
032 }
007行的mapping main定义了一个QVT转换的主进入点,这是一个什么样的转换呢?从main 的signature就可以了解,它接受一个BPMN的流程模型,
(in model: bpmn::BpmnProcessPool)
转换并且回传一个Together的模型
uml::together::Model
接著我们定义了下面两个对映MetaModel到MetaModel之间元素的规则:
n 每一个BPMN MetaModel之中的Lane元素都对映到UML中UseCase图形中的Actor元素
n 每一个BPMN MetaModel之中的Task元素都对映到UML中UseCase图形中的UseCase元素
有了这两个规则之后,一切就简单了。首先我们从来源BPMN模型中找到所有Lane元素以及Task元素,接著把Lane元素转换Actor元素,把Task元素转换为UseCase元素,工作就完成了。让就让我们解释一下前面的OCL如何完成它的工作。
一个转换主进入点可以分为两个部份,第一个部份称为init区块,它的目的主要是宣告变数或是查询转换过程需要使用的模型元素。因此在009行中宣告了一个actors变数,它的数值就是来源BPMN模型中所有的Lane元素。为什么,让我们看看它使用的OCL代表的意义。009行使用了如下的OCL程式码:
model.lanes->select(lane | lane.oclIsKindOf(bpmn::BpmnLane))->oclAsType(Sequence(bpmn::BpmnLane)
让我们猜拆解它的语法和009行意如下:
程式码段 |
意义 |
model.lanes |
取得来源BPMN模型中所有的Lane物件 |
->select |
由于model.lanes是collection 物件,因此使用->代表符号的左边是collection 物件。Select 类似SQL的Select语法 |
(lane | lane.oclIsKindOf(bpmn::BpmnLane)) |
宣告一个暂时变数lane,这个lane如果是bpmn中的BpmnLane物件,也就是我们需要的Lane物件,它的代表符号是bpmn::BpmnLane。那么就符合select条件而进入结果物件资料集中(object resultset) |
->oclAsType(Sequence(bpmn::BpmnLane)
|
由于->select(lane | lane.oclIsKindOf(bpmn::BpmnLane)) 是collection 物件,因此使用->代表符号的左边是collection 物件。 oclAsType是OCL的函式,这个函式把(bpmn::BpmnLane转换为OCL的Sequence资料型态 |
了解了009行的意义之后,您应该可以了解010行的意义。
在主进入点的init区块之后就是主进入点的主要部份了,也就是主进入点回传的结果物件。这第二个区块是由object {}包围的。在object {}区块中,object就代表回传的uml::together::Model模型,那么回传的模型中包含什么东西呢?这就是由在object {}区块中的OCL程式码来决定的,也就是013和014行的执行结果。看看013行在做什么?
013 ownedMembers := actors->collect(a | makeActor(a))->asOrderedSet();
程式码段 |
意义 |
ownedMembers |
结果模型之中的特性值,代表结果模型中拥有的元素 |
:= |
指定符号,把:=符号右边的执行结果指定给:=符号左边的特性值 |
actors-> |
009行宣告的变数,它是collection 物件,因此使用->代表符号的左边是collection 物件 |
collect(a | makeActor(a))
|
collect是OCL的函式,它把()中的执行结果形成一个collection 物件。而a是暂时变数,这个变数呼叫makeActor对映函式 |
->asOrderedSet(); |
把这行程式码的执行结果转换为一个有次序的结果collection 物件。 |
而019行的makeActor是一个对映函式,它接受一个bpmn::BpmnLane的Lane物件并且回传一个UML 20中UseCase模型中的 Actor物件。
这整个转换流程可以使用下面的图形来说明:
由这个范例可以看到OCL语言强大的力量,Together 2006提供的OCL支援包含了执行OCL转换能力,除错OCL转换程式,更提供了MetaModel类似Code Insight的功能,可以帮助开发人员在撰写OCL转换程式时对于模型和语法的帮助。例如下图就是在Together 2006的OCL编辑器中打入model.之后,Code Insight视窗便可以出现指引开发人员在之后能够撰写的程式码。
除了BPMN模型之外,Together 2006也提供了其他广泛模型转换的能力,例如我们也可以对Eclispe的EMF模型进行各种不同的转换,再结合EMF产生Java程式码的能力最终把PIM转换为Java的PSM。