OCL的乐趣和威力 Part 2

下图是Together 2006提供的各种不同的模型和MetaModel之间转换的能力。

 x1pFUxp6NG29bXY4hHUvhWU938vgNEsQW76lC8_5YICKBc1QfJ2iEZeopNzUUulZeujxs00c6zWuNKnD7OBfFbc6hHU1aAs9CTpSx8ak9_T8aWk4k31zCI4yNQw7S8iaupsvlazpPqr1OdW5TguuIGrTA

看看上图中的rdb MetaModel,这就非常的有意思了。目前OR-Mapping技术似乎非常的流行,由Hibernate带起的风潮从Java平台一直延烧到.NET平台,虽然MSObjectSpace延迟了,但是并不代表MS会错过这股热潮,而Hibernate也移植到了.NET平台。不过我并不是要讨论Hibernate,而是要说说OR-Mapping

 

Together 2006允许开发人员从UML模型转换到RDB模型,这代表由开发人员设计的类别图可以借由OCL的对映规则转换为关连资料库的纲要。BorlandECO也提供了这个强大的能力,但是ECO目前并不允许开发人员自行定义转换的规则,而是由ECO架框本身来完成这个工作,但是Together 2006却提供了开放的架构,开发人员可以借由撰写OCL来本行定义这个OR-Mapping的流程,想想这是多么强大的功能。如果我们对映到Hibernate的话,那么我们也可以定义一个Java类别到rdb的转换规则,如此一来我们可以借由撰写OCL程式来客制化和自动化转换的流程。

 

看看下面一个Together 2006内建的范例,这个范例清楚的使用了OCL来转换UML模型到关连资料库纲要。

001    /*

002     * The SimpleUML to RDB Sample demonstrates how to use QVT transformations for

003     * transforming platform independent model to platform specific model.

004     *

005     * It also demonstrates the following basic features of QVT language:

006     * helper queries, mapping guards, and resolution operations.

007     *

008     * Sample model pim.simpleuml is included to be used as an input for the transformation.

009     */

010   

011    transformation Simpleuml_To_Rdb;

012   

013    metamodel 'http:///SimpleUML.ecore';

014    metamodel 'http:///rdb.ecore';

015   

016    mapping main(in model: simpleuml::Model) : rdb::Model {

017      name := model.name;

018      schemas := package2schemas(model);

019    }

020    

021    query package2schemas(in root: simpleuml::Package) : OrderedSet(rdb::Schema) {

022      package2schema(root)->

023          union(root.getSubpackages()->collect(p | package2schemas(p)))->asOrderedSet()

024    }

025   

026    mapping package2schema(in pack: simpleuml::Package) : rdb::Schema

027      when { pack.hasPersistentClasses() }

028    {

029      name := pack.name;

030      elements := pack.ownedElements->select(oclIsKindOf(simpleuml::Class))->

031          collect(c | persistentClass2table(c.oclAsType(simpleuml::Class)))->asOrderedSet()

032    }

033   

034    mapping persistentClass2table(in cls: simpleuml::Class) : rdb::Table

035      when { cls.isPersistent() }

036    {

037      name := cls.name;

038      columns := class2columns(cls);

039      primaryKey := class2primaryKey(cls);

040      foreignKeys := class2foreignKeys(cls);

041    }

042   

043    mapping class2primaryKey(in cls: simpleuml::Class) : rdb::constraints::PrimaryKey {

044      name := 'PK'.concat(cls.name);

045      includedColumns := cls.resolveByRule('persistentClass2table', rdb::Table)->any(true).getPrimaryKeyColumns()

046    }

047   

048    query class2foreignKeys(in cls: simpleuml::Class) : OrderedSet(rdb::constraints::ForeignKey) {

049      cls.attributes->collect(resolveByRule('relationshipAttribute2foreignKey', rdb::constraints::ForeignKey))->

050          asOrderedSet()

051    }

052   

053    query class2columns(in cls: simpleuml::Class) : OrderedSet(rdb::TableColumn) {

054      dataType2columns(cls)->

055          union(generalizations2columns(cls))->asOrderedSet()

056    }

057   

058    query dataType2columns(in dt: simpleuml::DataType) : OrderedSet(rdb::TableColumn) {

059      primitiveAttributes2columns(dt)->

060          union(enumerationAttributes2columns(dt))->

061          union(relationshipAttributes2columns(dt))->

062          union(assosiationAttributes2columns(dt))->asOrderedSet()

063    }

064   

065    query dataType2primaryKeyColumns(in dt: simpleuml::DataType, in prefix : String, in leaveIsPrimaryKey : Boolean) : OrderedSet(rdb::TableColumn) {          

066      dataType2columns(dt)->select(isPrimaryKey)->

067          collect(c | object rdb::TableColumn {

068              name := prefix.concat('_').concat(c.name);

069              domain := c.domain;

070              type := c.type;

071              isPrimaryKey := leaveIsPrimaryKey

072          })->asOrderedSet()

073    }

074   

075    query primitiveAttributes2columns(in dt: simpleuml::DataType) : OrderedSet(rdb::TableColumn) {

076      dt.attributes->collect(a | primitiveAttribute2column(a))->asOrderedSet()

077    }

078    

079    query umlPrimitive2rdbPrimitive(in name : String) : String {

080      if name = 'String' then 'varchar' else

081          if name = 'Boolean' then 'int' else

082              if name = 'Integer' then 'int' else

083                  name

084              endif

085          endif

086      endif

087    }

088   

089    mapping primitiveAttribute2column(in prop: simpleuml::Property) : rdb::TableColumn

090      when { prop.isPrimitive() }

091    {

092      isPrimaryKey := prop.isPrimaryKey();

093      name := prop.name;

094      type := object rdb::datatypes::PrimitiveDataType { name := umlPrimitive2rdbPrimitive(prop.type.name); };

095    }

096   

097    query enumerationAttributes2columns(in dt: simpleuml::DataType) : OrderedSet(rdb::TableColumn) {

098      dt.attributes->collect(a | enumerationAttribute2column(a))->asOrderedSet()

099    }

100   

101    mapping enumerationAttribute2column(in prop: simpleuml::Property) : rdb::TableColumn

102      when { prop.isEnumeration() }

103    {

104      isPrimaryKey := prop.isPrimaryKey();   

105      name := prop.name;

106      type := object rdb::datatypes::PrimitiveDataType { name := 'int'; };

107    }

108   

109    query relationshipAttributes2columns(in dt: simpleuml::DataType) : OrderedSet(rdb::TableColumn) {

110      dt.attributes->collect(a | relationshipAttribute2foreignKey(a))->

111          collect(includedColumns)->asOrderedSet();

112    }

113   

114    mapping relationshipAttribute2foreignKey(in prop: simpleuml::Property) : rdb::constraints::ForeignKey

115      when { prop.isRelationship() }

116    {

117      name := 'FK'.concat(prop.name);

118      includedColumns := dataType2primaryKeyColumns(prop.type.asDataType(), prop.name, prop.isIdentifying());

119      referredUC := prop.type.lateResolveByRule('class2primaryKey', rdb::constraints::PrimaryKey);

120    }

121   

122    query assosiationAttributes2columns(in dt: simpleuml::DataType) : OrderedSet(rdb::TableColumn) {

123      dt.attributes->select(isAssosiation())->

124          collect(p | dataType2columns(p.type.asDataType()))->asOrderedSet()

125    }

126   

127    query generalizations2columns(in cls: simpleuml::Class) : OrderedSet(rdb::TableColumn) {

128      cls.generalizations->collect(g | class2columns(g.general))->asOrderedSet()

129    }

130   

131    query simpleuml::Package::getSubpackages() : OrderedSet(simpleuml::Package) {

132      ownedElements->collect(oclAsType(simpleuml::Package))->asOrderedSet()

133    }

134   

135    query simpleuml::Type::asDataType() : simpleuml::DataType {

136      oclAsType(simpleuml::DataType)

137    }

138   

139    query simpleuml::Property::isPrimaryKey() : Boolean {

140      stereotype->includes('primaryKey')

141    }

142   

143    query simpleuml::Property::isIdentifying() : Boolean {

144      stereotype->includes('identifying')

145    }

146   

147    query simpleuml::Property::isPrimitive() : Boolean {

148      type.oclIsKindOf(simpleuml::PrimitiveType)

149    }

150   

151    query simpleuml::Property::isEnumeration() : Boolean {

152      type.oclIsKindOf(simpleuml::Enumeration)

153    }

154   

155    query simpleuml::Property::isRelationship() : Boolean {

156      type.oclIsKindOf(simpleuml::DataType) and type.isPersistent()

157    }

158   

159    query simpleuml::Property::isAssosiation() : Boolean {

160      type.oclIsKindOf(simpleuml::DataType) and not type.isPersistent()

161    }

162   

163    query rdb::Table::getPrimaryKeyColumns() : OrderedSet(rdb::TableColumn) {

164      columns->select(isPrimaryKey)

165    }

166   

167    query simpleuml::ModelElement::isPersistent() : Boolean {

168      stereotype->includes('persistent')

169    }

170   

171    query simpleuml::Package::hasPersistentClasses() : Boolean {

172    --    ownedElements->exists(e | e.oclIsKindOf(simpleuml::Class)

173    --        and e.oclAsType(simpleuml::Class).isPersistent())

174      ownedElements->select(oclIsKindOf(simpleuml::Class))->

175          select(c | c.oclAsType(simpleuml::Class).isPersistent())->size() > 0

176    }

 

看看它的主要进入点,接受一个UML模型,转换出一个rdb模型,整个OCL程式并不困难了解,其中一些查询函式更是有趣,例如决定primarykey栏位使用了 :

139    query simpleuml::Property::isPrimaryKey() : Boolean {

140      stereotype->includes('primaryKey')

141    }

 

它是根据UML模型中是否有定义'primaryKey'stereotype来决定,而在决定把UML类别图转换为关连资料库纲要的过程中,我们只需要处理Persistent型态的类别即可,这则是由下面的OCL函式来进行:

171    query simpleuml::Package::hasPersistentClasses() : Boolean {

172    --    ownedElements->exists(e | e.oclIsKindOf(simpleuml::Class)

173    --        and e.oclAsType(simpleuml::Class).isPersistent())

174      ownedElements->select(oclIsKindOf(simpleuml::Class))->

175          select(c | c.oclAsType(simpleuml::Class).isPersistent())->size() > 0

176    }

 

它如何找到模型中需要永续储存的类别? 简单,首先从所有拥有的模型元素中选择出是类别的元素:

ownedElements->select(oclIsKindOf(simpleuml::Class))->

再对其中每一个类别元素,看看它是否有使用stereotype来定义'persistent'属性:

select(c | c.oclAsType(simpleuml::Class).isPersistent())

 

167    query simpleuml::ModelElement::isPersistent() : Boolean {

168      stereotype->includes('persistent')

169    }

 

最后再检查所有select出来的collection物件是否有符合条件的类别元素即可得到答案:

->size() > 0

 

是不是觉得OCL又有趣又符合物件导向和直觉呢?

 

撰写OCL成为了一个很有趣的工作,因为它会强迫开发人员以物件导向的方式思考,又强迫开发人员以模型和MetaModel做为物件的处理来源,一旦您习惯使用OCL之后,您会发现它和我们使用的物件导向程式语言,物件导向分析,物件导向设计以及模型是如此的搭配,它会让您觉得撰写OCL是很享受的一件事。

 

有了OCL能够让我们定义模型之间转换的规则什么好处? 好处多了,例如一个企业如果旧的系统都是使用流程图(Flow Chart)或是资料图(Data Diagram)等定义的,那么就可以借由撰写OCL对映规则让Together 2006执行它并且自动的转换流程图/资料图为UML模型。又例如一个企业已经拥有了许多的程式码,函式库或是架框,又不希望传统的UML工具只能根据模型产生简单的类别程式码,而希望能够让模型直接对映到企业已经发展出来的程式码,函式库或是架框,进行UML PIM到特定程式码,函式库或是架框PSM的对映,那么现在借由Together 2006OCL现在这都可以做到了。如此一来企业可以大幅减少根据模型再转换到实作程式码之间的时间和成本,更重要的是一旦OCL对映程式码开发完成之后,可以不断的自动化模型到程式码,函式库或是架框之间对映的工作。

 

如此看来不管是在Java平台,.NET平台,Win32平台或是UML的软体工程世界中,OCL都成为了不可或缺的重要的语言。也因此,对于开发人员来说Java+SQL+OCL,或是C#+SQL+OCL,或是Delphi+SQL+OCL似乎是JavaC#Delphi开发人员必须熟练的金三角语言了。不过现在再加上OCL的语法,我脑袋中各种语言语法就更混乱了,希望我下次做产品发表或是技术研讨会时我还能够无误的打出正确的语法!

 

最后在年初开春时先祝各位2006IT功力大增, 事业更上一层楼。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章