de_DEen_USes_ESfa_IRfr_FRhi_INid_IDjapl_PLpt_PTru_RUvizh_CNzh_TW

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.

Objct Constraint Language (OCL)


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

  1. 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).

  2. Keine Programmiersprache: Sie können keinen Programmlauf, Steuerungsfluss oder Prozesse/Operationen schreiben, die den Zustand verändern.

  3. Typisierte Sprache: Jeder Ausdruck hat einen Typ. Gültige Ausdrücke müssen Typkonformitätsregeln befolgen. Jeder UML-Klassifikator ist ein OCL-Typ.

  4. 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 ist a + b identisch mit a.+(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: wahrfalsch (Operationen: undodernichtimpliziertexklusiv oder)

  • Ganzzahl / Real: Zahlen (Operationen: +-*/absmaxmindivmod)

  • String: Text (Operationen: größeverkettungteilstrengzurInteger)

Sammlungen

Sammlungen sind abstrakte Typen, die verwendet werden, um mehrere Elemente zu speichern. Sie werden über die -> Notation.

  1. Menge: Mathematische Menge (keine Duplikate, ungeordnet). Menge {1, 2, 5}

  2. GeordneteMenge: Keine Duplikate, aber geordnet nach Position. GeordneteMenge {5, 4, 3}

  3. Menge: Erlaubt Duplikate, ungeordnet. Menge {1, 2, 2, 5}

  4. 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 denen expr wahr ist.

    • reject(expr): Gibt Elemente zurück, bei denen expr falsch ist.

    • collect(expr): Extrahiert eine bestimmte Eigenschaft aus allen Elementen in eine neue Sammlung.

    • forAll(expr): Gibt wahr wenn expr für alle Elemente wahr ist.

    • exists(expr): Gibt wahr wenn expr fü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)

4. Navigieren von UML-Modellen

OCL wird stark als Navigationssprache verwendet, um Assoziationen zu durchlaufen.

Standardnavigation

Navigieren Sie mit dem entgegengesetzten Rollennamen. Der Rückgabetyp hängt von der Vielzahl ab:

  • Vielfachheit 1 oder 0..1: Gibt ein einzelnes Objekt zurück.

  • Vielfachheit *: Gibt eine Menge (oder Geordnete Menge wenn 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 (prepost)

Einschränkungen, die an Operationen gebunden sind.

  • pre: Muss wahr sein vor der Ausführung.

  • post: Muss wahr sein nach Ausführung. Verwenden Sie Ergebnis fü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

Navigations- und Beziehungseinschränkungen

-- 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)