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功力大增, 事業更上一層樓。


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