Modellierung mit
UML
Loading

C.3 OCL

C.3.1 Syntax der OCL

Da die OCL eine textuelle Sprache ist, wird für ihre Definition die Erweiterte Backus-Naur-Form (EBNF) verwendet. Die in diesem Kapitel vorgestellte OCL/P besitzt einige signifikante syntaktische Unterschiede zur OCL in der Standarddefinition [OMG10b], die sich vor allem durch die syntaktische Annäherung an die Zielprogrammiersprache Java und konzeptuellen Verbesserungen erklären. Diese Unterschiede werden in Abschnitt C.3.2 im Detail erklärt.

Die OCL ist eine auf dem ASCII-Alphabet basierende Sprache. Zwar werden zur besseren Lesbarkeit Schlüsselwörter wie forall in einem anderem Layout dargestellt, jedoch werden keine mathematischen Sonderzeichen wie genutzt. Die Praxis der Softwareentwicklung zeigt, dass die Verwendung einer Spezifikationssprache, die ähnlich einer Programmmiersprache aussieht und damit einem in Programmiersprachen geübten Modellierer bekannt vorkommt, eine deutlich niedrigere Hürde zu ihrer Verwendung darstellt.

Diese Hürde wurde für OCL/P weiter verringert, indem die in Java und OCL gemeinsamen Modellierungskonzepte in dieselbe syntaktische Form gebracht wurden. Deshalb kann die nachfolgende OCL-Grammatik an mehreren Stellen durch Verweise in die in Anhang B gegebene Java-Grammatik erklärt werden.

OCL-Bedingungen können in OCL-Dokumenten gesammelt werden oder als Bedingungen innerhalb von anderen Diagrammen und in Java als Invarianten auftreten. Deshalb existiert in UML/P eine Dokumentart OCL für OCL-Bedingungen.

Der UML-Standard sieht auch vor, dass OCL-Bedingungen als Annotationen für Klassen und Methoden innerhalb von Klassendiagrammen verwendet werden können. Die Praxis und auch der UML-Standard selbst zeigen jedoch, dass dies leicht zu einer Überladung der Diagramme führt. Eine Separation der OCL-Bedingungen in eigenständige Dokumente oder Dokumentabschnitte ist daher oft ratsam. Das Aussehen solcher Dokumente wurde mit den Abbildungen 3.30 und 3.17 bereits illustriert. Die parallele Bearbeitung dieser Dokumente kann durch geeignete Werkzeuge, wie etwa dem in [HRR99] beschriebenen Editor-Framework, gut unterstützt werden.

In Abbildung C.7 ist die oberste Ebene der OCL-Grammatik beschrieben. Ein OCL-Dokument besteht aus einer Sammlung von Invarianten und Methodenspezifikationen. Methoden können in einem OCL-Dokument mit Merkmalen und Stereotypen attributiert werden. Dadurch ist es möglich, Hilfsfunktionen, die zur Beschreibung von Bedingungen verwendet werden können, direkt in OCL-Dokumenten zu definieren, ohne dass diese in der Implementierung oder in Klassendiagrammen erscheinen müssen.

OCL ::= { ⟨Constraint⟩ }*;
Constraint ::= StereotypC.4* RawConstraint⟩ ⟨MerkmalC.4* ;
RawConstraint ::= Invariant | OperationConstraint
Invariant ::= ClassContextopt inv InvariantNameopt
: OCLExpr*;
OperationConstraint
::= context OperationContext
{ let OCLVarDeclarator;1-* }opt
{ pre InvariantNameopt : OCLExpr;* }opt
{ post InvariantNameopt : OCLExpr;* }opt
  
ClassContext ::= { context | import }
{ ⟨ClassOrInterface⟩ ⟨IdentifierB.1opt }*,
  
OperationContext ::= OperationSignature⟩ ⟨ThrowsB.4
OperationSignature
::= MethodSignature | ConstructorSignature
MethodSignature ::= TypeVoidB.2opt { ⟨ClassOrInterfaceB.2 . }opt
IdentifierB.1⟩ ⟨FormalParametersB.4 []*
ConstructorSignature
::= new ClassOrInterface⟩ ⟨FormalParametersB.4
ClassOrInterface ::= NameB.2⟩ ⟨TypeArgumentsB.2opt
InvariantName ::= IdentifierB.1
  
OCLConstraint ::= InvariantName | OCLExpr | Constraint
Abbildung C.7: OCL-Bedingungen

Die Bedingungssprache OCL besteht im Wesentlichen aus einer Sammlung von Sprachkonzepten für die Definition von Ausdrücken mit booleschen Werten. Deshalb sind die Nichtterminale OCLExpr und OCLPrimeExpr entsprechend komplex aufgebaut.2

OCLExpr ::= OCLPrefixOp* OCLPrimary
| OCLExpr⟩ ⟨OCLInfixOp⟩ ⟨OCLExpr
| OCLExpr instanceof TypeB.2
| if OCLExpr then OCLExpr else OCLExpr
| OCLExpr ? OCLExpr : OCLExpr
| typeif IdentifierB.1 instanceof TypeB.2
     then OCLExpr else OCLExpr
| IdentifierB.1 instanceof TypeB.2
     ? OCLExpr : OCLExpr
| let OCLVarDeclarator;1-* in OCLExpr
| CollectionExpr
  
OCLInfixOp ::= InfixOpB.6 | <=> | implies
OCLPrefixOp ::= + | - | ~ | ! | ( TypeB.2 )
OCLVarDeclarator ::= TypeB.2opt IdentifierB.1 []* = OCLExpr
Abbildung C.8: OCL-Ausdrücke

Im Prinzip ist es möglich als Bedingungen direkt Java-Ausdrücke einzusetzen, die um einige OCL-spezifische Konstrukte erweitert werden. Jedoch gibt es bei den Java-Ausdrücken neben den Operatoren ++ und -- sowie der Zuweisung, die jeweils Seiteneffekte haben, auch Konstrukte zur Erzeugung neuer Objekte und Arrays, die in OCL-Bedingungen nicht verwendet werden. Dies könnte durch geeignete Kontextbedingungen ausgedrückt werden. Es ist jedoch sinnvoller, diese Unterschiede entsprechend Abbildung C.9 direkt in der Grammatik zu verankern, in der ein eigenes Nichtterminal OCLPrimary verwendet wird, das analog zu PrimaryB.6 aufgebaut ist. Die letzte Alternative des Nichtterminals OCLPrimary dient zur Integration der OCL mit Objektdiagrammen und wird in Kapitel 4 diskutiert.

OCLPrimary ::= ( OCLExpr )
| LiteralB.1
| { ⟨OCLPrimary . }opt IdentifierB.1
OCLArgumentsopt
| { ⟨OCLPrimary . }opt IdentifierB.1 @pre
| { ⟨OCLPrimary . }opt IdentifierB.1 ⋆⋆
| OCLPrimary [ OCLExpr ]
| super . IdentifierB.1⟩ ⟨OCLArgumentsopt
| super . IdentifierB.1 @pre
| TypeB.2 @preopt
| this
| result
| isnew ( OCLExpr )
| defined ( OCLExpr )
| Comprehension
| OD . Diagrammname // siehe Abschnitt C.4.1
  
OCLArguments ::= ( OCLExpr,* )
Abbildung C.9: Primitive der OCL-Ausdruckssprache

In der OCL spielen Container eine wesentliche Rolle. Deshalb sind die Möglichkeiten, Ausdrücke mit Containern zu definieren, in Abbildung C.10 zusammengefasst. Mit dem Nichtterminal CollectionExpr werden Quantoren und andere Spezialoperationen für Container beschrieben. Das Nichtterminal Comprehension beschreibt die Varianten zur Aufzählung von Containerelementen und zu deren eigenschaftsorientierter Beschreibung in Form einer Komprehension. Neben dem bereits bekannten OCLVarDeclarator zur Einführung einer Variablen im let-Konstrukt können neue Variablen mit dem Nichtterminal SetVarDeclaration eingeführt werden. Diese Form der Variablendeklaration wird genutzt, um eine Variable über die Elemente eines Containers variieren zu lassen.

CollectionExpr ::= forall SetVarDeclataror,1-* : OCLExpr
| exists SetVarDeclataror,1-* : OCLExpr
| any OCLExpr
| iterate { SetVarDeclataror ; OCLVarDeclarator
: Identifier = OCLExpr }
  
Comprehension ::= ContainerTypeopt { CollectionItem*, }
| ContainerTypeopt
{ OCLExpr | ComprehensionItem*, }
| ContainerTypeopt
{ SetVarDeclarator | ComprehensionItem*, }
  
SetVarDeclarator ::= TypeB.2opt IdentifierB.1 []* in OCLExpr
| TypeB.2⟩ ⟨IdentifierB.1 []*
ContainerType ::= { Set | List | Collection }
TypeArgumentsB.2opt
CollectionItem ::= OCLExpr⟩ { .. OCLExpr⟩ }opt
ComprehensionItem
::= SetVarDeclarator
| OCLVarDeclarator
| OCLExpr
Abbildung C.10: Container in der OCL-Ausdruckssprache

Wie bereits erwähnt, haben die Grammatiken der Sprachen Java und OCL viele strukturelle und inhaltliche Übereinstimmungen. Die gemeinsam genutzten und die strukturell ähnlichen Nichtterminale sind in Tabelle C.11 zusammengefasst.

Korrespondierende Nichtterminale
OCL-Nichtterminal Java-Nichtterminal


OCLArguments ArgumentsB.6
OCLExpr ExpressionB.6
OCLInfixOp InfixOpB.6
OCLPrimary PrimaryB.6
OCLPrefixOp PrefixOpB.6
OCLVarDeclarator VariableDeclaratorB.4
OCLVarInitializer VariableInitializerB.4


Von Java übernommene Nichtterminale


FormalParametersB.4
IdentifierB.1
InfixOpB.6
NameB.2
LiteralB.1
TypeB.2
TypeVoidB.2


Tabelle C.11: Vergleich der Grammatiken von OCL/P und Java

C.3.2 Unterschiede zu dem OCL-Standard

Um die Lesbarkeit und damit die Benutzbarkeit der OCL zu erhöhen, sowie die Integration mit der Zielsprache Java zu verbessern, wurden eine Reihe konzeptueller und syntaktischer Änderungen an der OCL gegenüber dem in [OMG10b] definierten Standard vorgenommen. Die wichtigsten Änderungen sind in Tabelle C.12 zusammengefasst.


OCL/P OCL-Standard



Grunddatentypen1 boolean, char, int, long, float, Integer, String, Boolean, Real, Enumeration
Generische Typen2 Klasse<T1,T2,...> -
Universal-Typ Object OclAny
Meta-Datentypen - OclType, OclState, OclExpression
Aufzählungen3 simuliert Enumeration
Container4 Set<X>, List<X> Set, Sequence, Bag



Selbstreferenz this self
Logikoperatoren &&, ||, ^, ! and, or, xor, not
Vergleiche ==, != ==, <>
Definiertheit defined(expr) fehlt
Applikation5 set.size set->size()
Typkonversion6 (Typ)expr expr.oclAsType(Typ)
 -abfrage expr instanceof Typ expr.oclIsKindOf(Typ)



Mengen- { v in set | expr } set.select(expr)
 operationen7 { expr | v in set } set.collect(expr)
Quantoren forall v in set: expr set.forall(expr)
exists v in set: expr set.exists(expr)
any v in set: expr set.any(expr)
sum, iterate8 Bibliothek in Sprache integriert



Operationskontext Typ Klasse.operation() Klasse::operation() : Typ
Variablendefinition Typ variable variable : Typ
Pfadnamen Pfad.Klasse Pfad::Klasse



Kommentar /⋆ … ⋆/, // --



Tabelle C.12: Unterschiede zwischen OCL/P und dem OCL-Standard

Anmerkungen zu den Unterschieden in Tabelle C.12:

  1. Der Datentyp String wird in der OCL/P nicht als Grunddatentyp betrachtet, sondern als normale Klasse. Neben der Änderung der restlichen Typnamen wurden auch die zur Verfügung stehenden Konstanten und Operationen an Java angepasst.
  2. OLC/P besitzt generische Typen analog zu Java.
  3. Aufzählungen werden in Java durch Konstanten und deren definierenden Klassen simuliert. OCL/P bietet ebenfalls nur simulierte Aufzählungstypen. Die Zusammenfassung aller Werte von Aufzählungstypen in Enumeration im OCL-Standard hatte demgegenüber den Nachteil, dass weitere Typisierungsinformation fehlt.
  4. Der Datentyp Bag für Multimengen wurde aus pragmatischen Gründen weggelassen. Häufig werden diese Multimengen nicht benötigt und die dann explizit notwendige Konversion in Mengen fällt so weg. Die Signaturen der Container-Typen wurden außerdem mit den aus Java bekannten Klassen integriert. Tabelle C.13 zeigt einen Vergleich für Mengenoperatoren. In der OCL/P wurden vor allem solche Operatoren aus Standard-Java weggelassen, die durch einfaches Voranstellen der Negation nachgebildet werden können.
  5. Die Anwendung von OCL-eigenen Operatoren auf Container-Strukturen wird im OCL-Standard grundsätzlich durch -> eingeleitet. Dies wurde mit syntaktischer Erkennbarkeit der OCL-Operatoren begründet, ist aber prinzipiell unnötig, da diese bereits am Namen erkannt werden.
  6. Der Operator instanceof wird in Kombination mit typeif angeboten, um damit eine implizite und damit sichere vorzunehmen, wenn das Argument den beschriebenen Typ besitzt.
  7. Die Möglichkeiten der Mengen- und Listenkomprehension wurden wesentlich ausgeweitet. Sie erlauben Generatoren, Filter und Zwischenvariablen zu definieren.
  8. Der flatten-Operator wird nicht rekursiv angewandt, sondern verflacht nur die oberste Ebene.
  9. OCL/P ist in Bezug auf angebotene Operatoren schlanker als der OCL-Standard, da einige der Operatoren in eine Bibliothek ausgelagert wurden. Dies erhöht gleichzeitig die Flexibilität der OCL/P, da so auch benutzerspezifische Operatoren definiert werden können.

Über die in der Tabelle C.12 angegebenen Unterschiede hinaus wurden folgende Veränderungen vorgenommen:

  • Die Verwendung des let-Konstrukts wurde konsolidiert und die in der UML 1.4 eingeführte definierende Bedingung (Stereotyp definition) zugunsten des Stereotyps OCL weggelassen.
  • Assoziationsklassen werden bereits in UML/P-Klassendiagrammen nicht verwendet. Daher existiert auch keine Navigation zu solchen Klassen.
  • Die Einbettung von OCL-Bedingungen in einen Package-Kontext kann bereits durch explizite Angabe des Package-Namens bei einem Klassenkontext vorgenommen werden und ist daher in UML/P unnötig.
  • Die Typisierungsregeln wurden an einigen Stellen präzisiert. So war im OCL-Standard nicht festgelegt, wie heterogene Aufzählungen der Form Set{"text",person} zu typisieren sind.
  • Die OCL-Logik wurde an die Möglichkeiten von Java angepasst, indem die Interpretation mit einem undefinierten Wert als Ergebnis wie ein false behandelt wird. Dazu wurde der Liftingoperator eingeführt, der eine zweiwertige Logik erlaubt.

OCL/P Java OCL-Standard



add add including
addAll addAll union
contains contains includes
containsAll containsAll includesAll
- - excludes
- - excludesAll
count - count
== equals =
isEmpty isEmpty isEmpty
- - notEmpty
remove remove excluding
removeAll removeAll -
retainAll retainAll intersection
symmetricDifference - symmetricDifference
size size count
flatten - -
asList - asSequence



Tabelle C.13: Namensvergleich von Mengenoperatoren in Java, OCL und OCL/P


Bernhard Rumpe. Agile Modellierung mit UML. Springer 2012