Ein umfassender Leitfaden zur Objekt-Beschränkungs-Sprache (OCL)
Basierend auf den bereitgestellten Lehrmaterialien behandelt dieser Leitfaden die grundlegenden Konzepte, die Syntax und praktischen Anwendungen der Objekt-Beschränkungs-Sprache (OCL) im Kontext der UML-Modellierung.

1. Einführung in die OCL
Warum benötigen wir die OCL?
Während UML-Diagramme (wie Klassendiagramme) hervorragend zur Visualisierung von Strukturen geeignet sind, können sie nicht alle relevanten Aspekte einer Spezifikation ausdrücken.
-
Natürliche Sprache ist mehrdeutig.
-
Traditionelle formale Sprachen sind eindeutig, aber für durchschnittliche Systemmodelle zu schwierig zu lesen und zu schreiben.
-
OCL schließt diese Lücke: Es ist eine formale Sprache die verwendet wird, um präzise Beschränkungen auszudrücken, bleibt aber einfach zu lesen und zu schreiben.
Wesentliche Eigenschaften der OCL
-
Reine Ausdrucks-Sprache: Ausdrücke haben keine Nebenwirkungen. Die Auswertung eines OCL-Ausdrucks gibt einen Wert zurück, aber kann den Zustand des Systems nicht verändern. Es kann jedoch beschreiben eine Zustandsänderung (z. B. in einer Nachbedingung).
-
Keine Programmiersprache: Sie können keinen Programmlauf, Steuerungsfluss oder Prozesse/Operationen schreiben, die den Zustand verändern.
-
Typisierte Sprache: Jeder Ausdruck hat einen Typ. Gültige Ausdrücke müssen Typkonformitätsregeln befolgen. Jeder UML-Klassifikator ist ein OCL-Typ.
-
Sofortige Auswertung: Der Zustand von Objekten kann sich während der Auswertung eines OCL-Ausdrucks nicht ändern.während der Auswertung eines OCL-Ausdrucks.
2. Grundlegende Syntax und Semantik
Kontext und Selbst
Jeder OCL-Ausdruck wird im Kontext eines bestimmten UML-Klassifiers (Klasse, Schnittstelle usw.) ausgewertet.
-
Kontext: Definiert die Klasse, auf die die Einschränkung angewendet wird. -
selbst: Ein reserviertes Wort, das auf die spezifische Instanz der zu evaluierenden Kontextklasse verweist.
Kontext Person
-- 'selbst' bezieht sich auf eine spezifische Instanz von Person
Kommentare und Operatoren
-
Kommentare: gekennzeichnet durch
--(z. B.-- dies ist ein Kommentar). -
Infix-Operatoren: Sie können Standard-Mathematik-/Logik-Operatoren verwenden (
+,-,=,<). Konzeptionell ista + bidentisch mita.+(b).
Zugriff auf Eigenschaften
Verwenden Sie die Punktnotation, um auf Attribute oder Operationen zuzugreifen. Klammern sind für Operationen obligatorisch, auch wenn sie keine Parameter haben.
Kontext Person
self.age -- Zugriff auf das Attribut 'age'
self.stockPrice() -- Aufruf der Operation 'stockPrice'
3. Typen und Sammlungen
Grundlegende vordefinierte Typen
OCL enthält Standardtypen mit spezifischen Operationen:
-
Boolesch:
wahr,falsch(Operationen:und,oder,nicht,impliziert,exklusiv oder) -
Ganzzahl / Real: Zahlen (Operationen:
+,-,*,/,abs,max,min,div,mod) -
String: Text (Operationen:
größe,verkettung,teilstreng,zurInteger)
Sammlungen
Sammlungen sind abstrakte Typen, die verwendet werden, um mehrere Elemente zu speichern. Sie werden über die -> Notation.
-
Menge: Mathematische Menge (keine Duplikate, ungeordnet).
Menge {1, 2, 5} -
GeordneteMenge: Keine Duplikate, aber geordnet nach Position.
GeordneteMenge {5, 4, 3} -
Menge: Erlaubt Duplikate, ungeordnet.
Menge {1, 2, 2, 5} -
Folge: Erlaubt Duplikate, geordnet.
Folge {1, 2, 5, 10}-
Hinweis: Die
..Notation erstellt eine Folge aufeinanderfolgender ganzer Zahlen:Folge {1..5}.
-
Wichtige Sammlungsoperationen
-
Allgemein:
größe(),istLeer(),istNichtLeer(),enthält(v),enthältNicht(v),anzahl(v),summe(). -
Iterator (Wesentlich für OCL):
-
wähle(expr): Gibt Elemente zurück, bei denenexprwahr ist. -
reject(expr): Gibt Elemente zurück, bei denenexprfalsch ist. -
collect(expr): Extrahiert eine bestimmte Eigenschaft aus allen Elementen in eine neue Sammlung. -
forAll(expr): Gibtwahrwennexprfür alle Elemente wahr ist. -
exists(expr): Gibtwahrwennexprfür mindestens ein Element wahr ist. -
iterate(elem; acc = init | expr): Die generische Basis für alle Iteratoren.
-
Iterator-Syntax-Variationen:
self.employee->select(age > 50)
self.employee->select(p | p.age > 50)
self.employee->select(p: Person | p.age > 50)
OCL wird stark als Navigationssprache verwendet, um Assoziationen zu durchlaufen.
Navigieren Sie mit dem entgegengesetzten Rollennamen. Der Rückgabetyp hängt von der Vielzahl ab:
-
Vielfachheit
1oder0..1: Gibt ein einzelnes Objekt zurück. -
Vielfachheit
*: Gibt eineMenge(oderGeordnete Mengewenn markiert{geordnet}).
Kontext Unternehmen
self.manager.alter > 40 -- manager ist 0..1, gibt ein Person-Objekt zurück
self.mitarbeiter->nichtLeer() -- mitarbeiter ist 0..*, gibt eine Menge zurück
Wenn ein Rollenname fehlt, verwenden Sie den Kleinbuchstabennamen der Klasse am anderen Ende (z. B. self.bank).
Assoziationsklassen
Navigieren Sie zu einer Assoziationsklasse mithilfe des Kleinbuchstabennamens der Assoziationsklasse.
Kontext Person
self.arbeit->größe() >= 1 -- 'arbeit' ist die Assoziationsklasse zwischen Person und Unternehmen
Rekursive Assoziationen
Wenn eine Assoziation zurück zur selben Klasse führt (z. B. Ehe zwischen zwei Personen), müssen Sie die Richtung mit eckigen Klammern angeben [rollenName].
Kontext Person
self.marriage[frau]->select(m | m.beendet = false)->size() = 1
Qualifizierte Assoziationen
Verwenden Sie Qualifiziererattribute in eckigen Klammern, um ein bestimmtes Objekt auszuwählen.
Kontext Bank
self.kunde[12345] -- Gibt die spezifische Person mit Kontonummer 12345 zurück
self.kunde -- Gibt eine Menge aller Kunden zurück
Neu-Typisierung (Casten)
Verwenden Sie oclAsType() um auf Eigenschaften einer Untertyp oder einer versteckten Oberklasseneigenschaft zuzugreifen.
self.oclAsType(Unter).p -- Greift auf die Eigenschaft 'p' zu, die im Untertyp definiert ist
self.oclAsType(Ober).p -- Greift auf die Eigenschaft 'p' zu, die in der Oberklasse definiert ist
5. Schreiben von Einschränkungen
1. Invarianten (inv)
Bedingungen, die immer für alle Instanzen einer Klasse wahr sein müssen.
Kontext Unternehmen
inv: self.anzahlMitarbeiter <= 50
inv KMU: self.aktienkurs() > 0 -- 'KMU' ist ein optionaler Name für die Einschränkung
2. Vor- und Nachbedingungen (pre, post)
Einschränkungen, die an Operationen gebunden sind.
-
pre: Muss wahr sein vor der Ausführung. -
post: Muss wahr sein nach Ausführung. Verwenden SieErgebnisfür den Rückgabewert. -
@pre: Wird in Postbedingungen verwendet, um auf den Wert einer Eigenschaft zu verweisen vor der Operation begann.
Kontext Person::income(): Integer
pre adult: self.alter >= 18
post resultOK: Ergebnis < 5000
Kontext Company::angestelltenAnstellen(p: Person)
post: Mitarbeiter = Mitarbeiter@pre->einschließen(p)
3. Body-, Anfangs- und abgeleitete Ausdrücke
-
body: Definiert das Ergebnis einer Abfrageoperation (kann rekursiv sein). -
init: Definiert den Anfangswert eines Attributs. -
derive: Definiert ein abgeleitetes Attribut.
Kontext Person::income(): Integer
body: self.job.gehalt->sum()
Kontext Person::verheiratet: Boolean
init: falsch
Kontext Company::anzahlMitarbeiter: Integer
derive: self.mitarbeiter->größe()
6. Erweiterte OCL-Funktionen
Let- und Def-Ausdrücke
-
let: Definiert eine lokale Variable innerhalb eines einzelnen Ausdrucks.let totStunden: Integer = self.arbeitetAn->sammeln(stunden)->sum() in totStunden >= 30 -
def: Definiert wiederverwendbare Variablen oder Operationen, die einer Klassifikation angehängt sind.def: hatTitel(t: String): Boolean = self.job->existiert(titel = t)
Undefinierte Werte (3-Wertige Logik)
Wenn ein Unterausdruck undefiniert ist (z. B. Navigation einer null-Verbindung), ist der gesamte Ausdruck undefiniert. Allerdings haben boolesche Operatoren Ausnahmen mit Kurzschluss:
-
wahr oder <undefiniert>= wahr -
falsch und <undefiniert>= falsch -
falsch impliziert <undefiniert>= wahr -
<undefiniert> impliziert wahr= wahr
Klassenmerkmale und vordefinierte Objekteigenschaften
-
allInstances(): Gibt eine Menge aller vorhandenen Instanzen einer Klasse zurück.Person.allInstances()->size() <= 100 -
Objekteigenschaften:
oclIsTypeOf(Typ),oclIsKindOf(Typ),oclInState(Zustand),oclIsNew(gebraucht in Nachbedingungen).
7. Umfassender Fallstudien-Beispiel: Das Unternehmensmodell
Um diese Konzepte zu festigen, hier eine Sammlung von Integritätsbedingungen, die auf ein Unternehmensdatenbankmodell (Mitarbeiter, Abteilungen, Projekte, Abhängige) angewendet werden.
Grundlegende Attributbeschränkungen
-- Das Alter muss >= 18 sein
kontext Mitarbeiter inv: self.Alter() >= 18
-- Das Einstellungsdatum muss nach dem Geburtsdatum liegen
kontext Mitarbeiter inv: self.Einstellungsdatum > self.Geburtsdatum
-- Das Startdatum des Managers muss nach dem Einstellungsdatum liegen
kontext Mitarbeiter inv: self.leitet->nichtleer() impliziert self.leitet.Startdatum > self.Einstellungsdatum
-- Der Vorgesetzte muss älter und besser bezahlt sein als der Mitarbeiter
kontext Mitarbeiter
inv: self.Vorgesetzter->nichtleer() impliziert self.Alter() > self.Vorgesetzter.Alter()
inv: self.Vorgesetzter->nichtleer() impliziert self.Gehalt < self.Vorgesetzter.Gehalt
-- Ein Vorgesetzter muss vor seinen Untergebenen eingestellt worden sein
kontext Mitarbeiter
inv: self.Untergebene->nichtleer() impliziert self.Untergebene->forall(e | e.Einstellungsdatum > self.Einstellungsdatum)
-- Der Abteilungsleiter muss ein Mitarbeiter dieser Abteilung sein
kontext Abteilung
inv: self.ArbeitetIn->enthält(self.leitet.Mitarbeiter)
-- Der Projektort muss einer der Orte der Abteilung sein
kontext Projekt
inv: self.Steuerung.Orte->enthält(self.Ort)
-- Ein Mitarbeiter kann nur an Projekten arbeiten, die von seiner Abteilung gesteuert werden
kontext Mitarbeiter
inv: self.ArbeitetIn.Steuerung->enthältAlle(self.ArbeitetAn.Projekt)
Sammlungs- und Iterator-Beschränkungen
-- Die Sozialversicherungsnummer muss eine eindeutige Kennung für alle Mitarbeiter sein
kontext Mitarbeiter
inv: Mitarbeiter.allInstances->fürAlle(e1, e2 | e1 <> e2 impliziert e1.SSN <> e2.SSN)
-- Name und Beziehung des Abhängigen müssen pro Mitarbeiter eindeutig sein
kontext Mitarbeiter
inv: self.Abhängige->fürAlle(e1, e2 |
e1 <> e2 impliziert (e1.Name <> e2.Name oder e1.Beziehung <> e2.Beziehung))
-- Abgeleitetes Attribut: Anzahl der Mitarbeiter in einer Abteilung
kontext Abteilung
inv: self.AnzahlMitarbeiter = self.ArbeitetIn->größe()
-- Ein Mitarbeiter arbeitet an maximal 4 Projekten
kontext Mitarbeiter
inv: self.ArbeitetAn->größe() <= 4
Komplexe Logik unter Verwendung von lass und wähle
-- Die Gesamtarbeitsstunden müssen zwischen 30 und 50 pro Woche liegen
kontext Mitarbeiter
inv: lass totStunden: Ganzzahl = self.arbeitetAn->sammle(stunden)->summe()
in totStunden >= 30 und totStunden <= 50
-- Auf einem bestimmten Projekt dürfen maximal 2 Mitarbeiter weniger als 10 Stunden arbeiten
kontext Projekt
inv: self.arbeitetAn->wähle(stunden < 10)->größe() <= 2
-- Nur Abteilungsleiter dürfen weniger als 5 Stunden an einem Projekt arbeiten
kontext Mitarbeiter
inv: self.arbeitetAn->wähle(stunden < 5)->nichtLeer() impliziert
self.arbeitetIn.managt.mitarbeiter = self
-- Mitarbeiter ohne Unterstellte müssen auf ALLEN ihren Projekten mindestens 10 Stunden arbeiten
kontext Mitarbeiter
inv: self.unterschleusse->leer() impliziert self.arbeitetAn->fürAlle(stunden >= 10)
-- Abteilungsleiter müssen auf allen Projekten, die von der Abteilung kontrolliert werden, mindestens 5 Stunden arbeiten
kontext Abteilung
inv: self.kontrolliert->fürAlle(p: Projekt |
self.managt.mitarbeiter.arbeitetAn->wähle(stunden >= 5)->enthält(p))
Verhinderung von Zyklen (rekursive Logik)
-- Ein Mitarbeiter kann sich selbst nicht überwachen
kontext Mitarbeiter inv: self.unterschleusse->ausschließt(self)
-- Die Überwachungshierarchie darf keine Zyklen enthalten
kontext Mitarbeiter
def: alleUnterschleusse = self.unterschleusse->vereinigung(
self.unterschleusse->sammle(e: Mitarbeiter | e.alleUnterschleusse))
inv: self.alleUnterschleusse->ausschließt(self)














