关于Visual Studio DSL改善状态机实例说明
Vistual Studio DSL是微软针对特定领域开发而专门设计的,其主要作用是允许开发人员自行设计专属的图形化工具,DSL是微软为特定领域开发的方面会用到的工具。
在前几节中,我们以创建的默认项目介绍了Visual Studio DSL的一些基本的知识,包括域类,域关系,图形符号,图形映射等,这些东西看起来可能会有些抽象,和我们第二节介绍的需求还没有直接关系,不过这些概念确实我们开发我们自己的Visual Studio DSL之前必须要掌握的。
如果你对我们将要做的这个实际的案例的需求并不是很了解,请仔细需求一下我们这个状态机的需求.如果你第一次看这一系列,或者是对这些基础概念还不是很熟悉,建议你看一下前面的几节基础知识。首先,按照第三节创建一个Minimal Lanauge模板项目,打开DSLDefinition.DSL文件:
1. 把根域类ExampleModel的名称修改StateMachine.同时签入关系ExampleModelHasElements也会自动重命名为StateMachineHasElements.
2. 修改域关系StateMachineHasElements左侧的域角色Elements的属性名(Property Name 注意不是修改Name--域角色名)改成States.同时签入关系StateMachineHasElements自动更新为StateMachineHasStates. [你也可以直接在域角色上点击修改,因为图的域角色上显示的是属性名而非角色名).
3. 修改域关系StateMachineHasStates的右侧已经更名的域角色StateMachine,修改属性Name为State.[注意,这里修改的是Name,而不是Property Name].
4. 修改域类ExampleElement的Name为State。
注意这里是基于模板项目进行更改,当然,你也可以删除这些自动生成的域类而是全新重新添加.另外或许你对这里的属性名(property name)和域角色名(name)有些迷惑,请看前面的详细区分。其实到这一步我们已经完成了状态机与状态之间元数据的DSL描述,接下来我们来完成状态之间的关系。
5.我们可以看到,图中的State与State之间已经是引用关系,这正是我们想要的,我们修改关系StateReferencesTargets为Transition。
6.修改Targets为属性名为Successors,域角色名为Predecessor。
7.修改Sources的属性名为Predecessors,域角色名为Successor。
同样,如果不是基于修改,而是重新添加域类也是完全可以的,我们接下来给域类添加一些属性:
8.给域关系Transition右键添加域属性(DomainProperty)Event,Condition,Action,Label.类型都为string。现在来看一下我们的DSL,状态机StateMachine,状态State.State之间的有引用关系Transition,也就是我们需求中描述的转移,它的属性也就是状态机元数数据---事件Event,警戒条件Condition,操作Action。
接下来,我们还需要给状态添加一个属性,来表示状态机中的状态分类,是起始状态,结束状态,还是普通状态.那么这个属性就需要是枚举类型,下面我们需要添加一个自定义的枚举类型:
9.打开DSL Explorer,在根结点LanguageSm(这个代表我们的DSL)上右键,选择添加Domain Enumeration。
选中刚添加的域枚举类型,右键选择属性,修改Name为StateKind,这样在DSL浏览器的Domain Types下面除了通用的类型外,就多了我们的StateKind枚举类型,同样,我们可用同样的方式添加其它外部类型(External Type),供我们的元数据所用。我们为这个枚举类型添加枚举值,右键添加Enumeration Literal,添加三个枚举值Normal,Initial,Final,值分别对应0,1,2.为我们的域类State添加一个属性Kind,数据类型Type选择我们刚刚添加的StateKind。
接下来,我们添加一个新的域类(从工具条中选择Domain Class拖到左侧域类区),更名为Action,这就是我们的元数据“操作”,为这个域类添加两个string类型的属性Label,Code。
现在我们需要考虑一下元数据中提到的进入操作和退出操作,在进入一个状态前,对于这个状态可以有进入操作,在退出一个状态时,可以有退出操作,很明显,在状态和操作之间,应该是嵌入关系而非引用关系,也就是我们的状态可以包含零或多个进入操作,零或多个退出操作,那我们这个进入操作和退出操作怎么来用DSL的域类表示呢?如果我们也象描述状态State那样,由一个属性来区分是进入操作还是退出操作是否可行呢?如果是这样的话,对操作Action的添加等就需要特殊处理。另外一点,如果我们针对状态State与操作Action建立多个零至多的嵌入关系会导致DSL编译时就会发生错误,这是Visual Studio DSL所不允许的,这会造成歧义.[包含域类方面和图形映射方面都会有问题]。在这里我们打算用DSL的另外一个特性来实现,也就是域类的继承,我们建立两个新的域类来表示进入操作和退出操作,他们都继承操作Action:
10.添加两个新域类EntryAction和ExitAction,并不需要给他们添加任何属性。
11.建立它们与Action的继承关系,选中工具箱中的Inheritance,先选中EntryAction,再指向选中Action。
上图就是完成后Action,我们可以通过Bring Tree Here更简化域类显示(上右图)。
12.建立EntryAction和ExitAction和State的嵌入关系,注意选中工具箱中的embedding relationship后,要从State指向EntryAction.注意左边的重数是0…*,右边的重数是1..1.也就是说一个状态可以没有进入操作或退出操作,也可以有多个。而且对于每个进入操作和退出操作,它们只能从属于一个状态State。我们现在来看一下我们完成的整个DSL元数据:
保存整个DSL文件后,我们点击转换所有模板(Transform All Templates),Visual Studio DSL根据我们的DSL文件中的元数据,用T4模板文件生成对应的C#代码,注意我们在以后每当修改完DSL文件中的元数据后,都要记得转换模板,才会使更改起作用.当然,你也可以选中某一个tt文件,右键运行自定义工具(Run Custom Tool),针对这个文件单独生成,尤其当你的DSL文件相当庞大时,这样能够提高生成速度。转换完成后,可以重新编译整个解决方案,查看是否有错误发生。我们象第五节那样,查看一下DomainClasses.cs文件类图:
可以看到,元数据中的域类,域关系都体现在生成的代码中了。