漫談OCL概念、特征和實踐
閑聊:
Jos Warmer的Object Constraint Language, The: Getting Your Models Ready for MDA, Second Edition》終于看完了。這是一本不可多得的好書,一本好書可以讓人節約很多的時間,其實我一直在尋找MDA相關規范的書,這本OCL是我認為最好的一本。關于MOF規范一直沒有由淺入深的教程。XMI倒是有一本《精通XMI》,不過也不甚精彩。UML當然汗牛充棟,這倒不必多說了。
OCL概念
我的BLOG上面兩篇都是介紹OCL的,導致人氣低迷,本來關注MDA技術的人就不多,關注OCL的就更少了。不過無論如何,OCL是MDA技術中不可缺少的部分。OCL雖然號稱“對象約束語言”,不過實際上可以用來約束MOF四層模型中任意一層的模型以及實例。它真正的意義是建模相關領域約束語言。
除了約束模型以外,OCL的一個重要用途是可以用來描述模型轉換規則。雖然這并不是OCL的主要用途(我沒有仔細查閱OCL規范,不知道是否在規范中正式提出過這個用途),但是很多研究者進行了研究和探索。其中Jos Warmer專門在一節中討論了這個問題。下面是摘自他文章中的一個用OCL描述模型轉換的例子:
1. 自然語言描述的轉換規則
· For each class named className in the PIM, there is a class named className in the PSM.
· For each public attribute named attributeName : Type of class className in the PIM the following attributes and operations are part of the class className in the target model.
- A private attribute with the same name: attributeName : Type
- A public operation named with the attribute name, preceded with 'get' and the attribute type as return type: getAttributeName() : Type
- A public operation named with the attribute name, preceded with 'set' and with the attribute as parameter and no return value: setAttributeName(att : Type)
從上面可以知道,這是一個將PIM中的class轉換為PSM中class的規則,以及為private屬性添加getter和setter方法。
2. 用OCL寫的轉換規則,其中用到了作者自己發明的偽符號
Transformation ClassToClass (UML, UML) {
source c1: UML::Class;
target c2: UML::Class;
source condition -- none
target condition -- none
mapping
try PublicToPrivateAttribute on
c1.features <~> c2.features;
-- everything else remains the same
}
Transformation PublicToPrivateAttribute (UML, UML) {
source sourceAttribute : UML::Attribute;
target targetAttribute : UML::Attribute;
getter : UML::Operation;
setter : UML::Operation;
source condition
sourceAttribute.visibility = VisibilityKind::public;
target condition
targetAttribute.visibility = VisibilityKind::private
and -- define the set operation
setter.name = 'set'.concat(targetAttribute.name)
and
setter.parameters->exists( p |
p.name = 'new'.concat(targetAttribute.name)
and
p.type = targetAttribute.type )
and
setter.type = OclVoid
and -- define the get operation
getter.name = 'get'.concat(targetAttribute.name)
and
getter.parameters->isEmpty()
and
getter.returntype = targetAttribute.type;
mapping
try StringToString on
sourceAttribute.name <~> targetAttribute.name;
try ClassifierToClassifier on
sourceAttribute.type <~> targetAttribute.type;
}
-- somewhere the rules StringToString and ClassifierToClassifier
-- need to be defined
看來作者很有興趣擴展OCL,將它變為Model Transformation Language。
另外,Kent大學的研究者D.H.Akehurst在《Relations in OCL》(http://www.cs.kent.ac.uk/pubs/2004/2007/index.html)一文中專門做出了探索。這篇文章的大意是:擴展目前的OCL,將Relation(關系)作為first class(第一性的)元素加入OCL語言,然后利用Relation和目前的OCL相結合來描述模型轉換規則(這篇文章我也許會在以后專門討論)。中文的例子比較長,相關知識較多,就不列舉了。
OCL特征
關于OCL的特征書中做出了總結,這里我沒有回頭仔細找,而是就腦海中最深的一些映像說幾點。
OCL是一個查詢性的語言,也就是說任何OCL的動作都不會對模型本身造成任何的影響或者改變。例如select操作,選出原來Set的一個子集,collect操作,將原來集合中的一些元素的值組成另一個集合。這些操作都不會對模型本身造成影響,最多就是構建了另外的對象或者集合。
OCL是一個強類型的語言,任何一個元素,都是有類型的,并且任何操作的返回值一定有一個確定的類型。如果不能確定類型,那么此元素屬于OclVoid類型的值Undefined。OCL的類型有三種:基本類型(Integer,Boolean等)、Collection類型(五種,虛類型Collection,以及它的子類型Set,OrderedSet,Bag,Sequence)和自定義類型(UML類,Association,Enumeration等等)。
OCL里面很強調時間點,任何操作都定義為瞬時完成的,即操作中模型的狀態不會改變。基于實現考慮,這么規定有一定的道理,不然在多線程系統中,OCL約束很有可能失效。另外precondition和postcondition也明確規定是在方法執行的前后時間點才有約束,時間點不對約束無效。
OCL是一個宣言式(Declarative)的語言,描述了what to do,沒有描述how to do。例如self.attribute->select(i| i.name = ‘wxb_nudt’)描述了將某個類的所有attribute組成一個集合,然后將屬性名為wxb_nudt的屬性提取出來組成一個子集(顯然這樣的屬性不會多于一個,但這并不是我們關心的問題)。這個表達式描述以上的目的,但是沒有給出執行過程。
OCL是基于集合論和謂詞邏輯的,這點從它的表達式中可以很輕易的看出來。但是并不是集合論中所有的集合操作在OCL中都具有相應的符號表達。例如映射(project)就沒有。而且OCL沒有證明集合論中的所有集合操作都可以用OCL中現有的操作組合出來。但是我們相信這一點(盲目的,我沒有時間去證明這個,呵呵)。另外關于OCL操作的中止性沒有得到證明,也就是說“不能確定每個OCL操作都可以在有限時間內完成”,并且OCL并不能保證任意的OCL表達式是可中止的(我感覺自己簡直就在說廢話)。其實OCL已經說明了,無論如何實現,OCL假定所有表達式的計算都在瞬間完成。
雖然這部分內容在前面的blog中提到過,不過那個時候僅僅是照本宣科,和現在心有所感是不一樣的。
OCL實踐
目前OCL沒有標準的實現,Jos Warmer在他的個人網站上列出了目前可用的OCL實現列表http://www.klasse.nl/ocl/ocl-services.html。
其中我選擇了Kent大學的OCL實現。還是kent大學的D.H.Akehurst,他們的research team開發了一個KMF(Kent Model Framework),其中有一個OCL的實現??梢杂脕眢w驗一下用OCL來編程(編程?不是建模么?)。下載地址http://www.cs.kent.ac.uk/projects/ocl/
需要給他們寫email才能得到下載地址。
然后Zurich大學的一位研究人員寫了這個版本的OCL的簡單實踐http://www.zurich.ibm.com/~wah/doc/emf-ocl/,源代碼如下:
import java.util.List;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import uk.ac.kent.cs.kmf.util.ILog;
import uk.ac.kent.cs.kmf.util.OutputStreamLog;
import uk.ac.kent.cs.ocl20.OclProcessor;
import uk.ac.kent.cs.ocl20.bridge4emf.EmfOclProcessorImpl;
public class OCLDemo {
public static boolean checkOCLConstraint(OclProcessor processor, String expr, Object model) {
List l = processor.evaluate(expr, model);
return Boolean.valueOf(l.get(0).toString()).booleanValue();
}
public static void main(String[] args) {
ILog log = new OutputStreamLog(System.err);
OclProcessor processor = new EmfOclProcessorImpl(log);
System.out.println(processor.evaluate("1+1"));
processor.addModel(EcorePackage.eINSTANCE);
EClass eClass = EcoreFactory.eINSTANCE.createEClass();
eClass.setName("Library");
EAttribute attr = EcoreFactory.eINSTANCE.createEAttribute();
attr.setName("books");
attr.setEType(EcorePackage.eINSTANCE.getEInt());
eClass.getEStructuralFeatures().add(attr);
System.out.println(processor.evaluate("context ecore::EClass " +
"inv:self.eAttributes->select(x|x.name='books')", eClass));
System.out.println(processor.evaluate("context ecore::EClass " +
"inv:self.eAttributes->exists(x|x.name='books' and x.eType.name = 'EInt')", eClass));
boolean pre = checkOCLConstraint(processor, "context ecore::EClass inv: not self.oclIsUndefined()",eClass);
// do something with eClass
boolean post = checkOCLConstraint(processor, "context ecore::EClass inv: self.eAttributes->forAll(c| not c.changeable)",eClass);
if (!(!pre | post))
System.out.println("OK.");
else
System.out.println("Ooops.");
}
}
我在Eclipse3.0.1和EMF2.0以及上面下載的OCLjava包環境下運行了這個例子,結果如下:
[2]
[[org.eclipse.emf.ecore.impl.EAttributeImpl@62937c (name: books) (ordered: true, unique: true, lowerBound: 0, upperBound: 1) (changeable: true, volatile: false, transient: false, defaultValueLiteral: null, unsettable: false, derived: false) (iD: false)]]
[true]
OK.
例子很簡單,詳細的解釋在上面鏈接的文章中解釋了。但是這個例子僅僅構造了一個簡單的ECore模型,而且是在程序中構造的,不是使用EclipseUML或者EMF畫出來的,另外如何將OCL和模型連接起來也沒有提到。如果有時間,我會看看KMF的文檔,應該有答案。
后記
目前對OCL有了基本的把握,現在缺少的就是一個具體系統的構建實踐。希望在Eclipse、EMF、EclipseUML和KMF的環境下來完成這個例子。