Kompletny przewodnik po języku ograniczeń obiektowych (OCL)
Opierając się na podanych materiałach wykładowych, ten przewodnik obejmuje podstawowe pojęcia, składnię oraz zastosowania praktyczne języka ograniczeń obiektowych (OCL) w kontekście modelowania UML.

1. Wprowadzenie do OCL
Dlaczego potrzebujemy OCL?
Choć diagramy UML (takie jak diagramy klas) są doskonałe do wizualizacji struktury, nie mogą wyrażać wszystkich istotnych aspektów specyfikacji.
-
Język naturalny jest niejednoznaczny.
-
Tradycyjne języki formalne są jednoznaczne, ale zbyt trudne do odczytania i pisania dla przeciętnych modelistów systemów.
-
OCL mostkuje tę przerwę: jest to język formalny używany do wyrażania dokładnych ograniczeń, a mimo to nadal pozostaje łatwy do odczytania i pisania.
Kluczowe cechy OCL
-
Czysty język wyrażeń: Wyrażenia nie mają skutków ubocznych. Ocena wyrażenia OCL zwraca wartość, ale nie może zmienić stan systemu. Może jednak opisać zmianę stanu (np. w warunku po).
-
Nie jest językiem programowania: Nie możesz pisać logiki programu, przepływu sterowania ani wywoływać procesów/operacji, które zmieniają stan.
-
Język typów: Każde wyrażenie ma typ. Poprawnie zapisane wyrażenia muszą spełniać zasady zgodności typów. Każdy klasifikator UML jest typem OCL.
-
Ocena natychmiastowa: Stan obiektów nie może się zmieniać podczas oceny wyrażenia OCL.
2. Podstawowa składnia i semantyka
Kontekst i self
Każdy wyrażenie OCL jest oceniane w kontekście określonego klasyfikatora UML (klasa, interfejs itp.).
-
kontekst: Określa klasę, do której stosuje się ograniczenie. -
self: Słowo kluczowe oznaczające konkretny egzemplarz klasy kontekstu, który jest oceniany.
kontekst Osoba
-- 'self' odnosi się do konkretnego egzemplarza Osoba
Komentarze i operatory
-
Komentarze: Oznaczane przez
--(np.-- to jest komentarz). -
Operatory infiksowe: Można używać standardowych operatorów matematycznych/logicznych (
+,-,=,<). Koncepcyjnie,a + bjest identyczne za.+(b).
Dostęp do właściwości
Użyj notacji kropkowej, aby uzyskać dostęp do atrybutów lub operacji. Nawiasy są wymagane dla operacji, nawet jeśli nie mają parametrów.
kontekst Osoba
self.wiek -- Dostęp do atrybutu 'wiek'
self.cenaAkcji() -- Wywołanie operacji 'cenaAkcji'
3. Typy i kolekcje
Podstawowe zdefiniowane typy
OCL zawiera typy standardowe z określonymi operacjami:
-
Logiczny:
prawda,fałsz(Operacje:i,lub,nie,oznacza,xor) -
Liczba całkowita / Liczba rzeczywista: Liczby (Operacje:
+,-,*,/,abs,maks,min,dzi,mod) -
Ciąg: Tekst (Operacje:
rozmiar,koniugacja,podciąg,naLiczbeCalkowita)
Zbiory
Zbiory to typy abstrakcyjne używane do przechowywania wielu elementów. Dostęp do nich uzyskuje się za pomocą -> notacji.
-
Zbiór: Zbiór matematyczny (bez powtórzeń, nieuporządkowany).
Zbiór {1, 2, 5} -
UporządkowanyZbiór: Bez powtórzeń, ale uporządkowane według pozycji.
UporządkowanyZbiór {5, 4, 3} -
Pojemnik: Zezwala na duplikaty, nieuporządkowane.
Pojemnik {1, 2, 2, 5} -
Sequencja: Zezwala na duplikaty, uporządkowane.
Sequencja {1, 2, 5, 10}-
Uwaga: Zapis
..zapis tworzy sekwencję kolejnych liczb całkowitych:Sequencja {1..5}.
-
Kluczowe operacje na zbiorach
-
Wspólne:
rozmiar(),jestPusty(),nieJestPusty(),zawiera(v),nieZawiera(v),liczba(v),suma(). -
Iteratory (kluczowe dla OCL):
-
wybierz(expr): Zwraca elementy, gdziewyrażeniejest prawdziwe. -
odrzuc(wyrażenie): Zwraca elementy, gdziewyrażeniejest fałszywe. -
zbierz(wyrażenie): Wyodrębnia określoną właściwość ze wszystkich elementów do nowej kolekcji. -
dlaWszystkich(wyrażenie): Zwracaprawdajeśliwyrażeniejest prawdziwe dla wszystkich elementów. -
istnieje(wyrażenie): Zwracaprawdajeśliwyrażeniejest prawdziwe dla przynajmniej jednego elementu. -
iteruj(elem; acc = init | wyrażenie): Ogólna podstawa dla wszystkich iteratorów.
-
Wariacje składni iteratora:
self.employee->select(wiek > 50)
self.employee->select(p | p.wiek > 50)
self.employee->select(p: Osoba | p.wiek > 50)
4. Przechodzenie po modelach UML
OCL jest intensywnie wykorzystywany jako język nawigacji do przemieszczania się po powiązaniach.
Standardowa nawigacja
Nawiguj używając nazwy roli przeciwnej. Typ zwracany zależy od wielokrotności:
-
Wielokrotność
1lub0..1: Zwraca pojedynczy obiekt. -
Wielokrotność
*: ZwracaZbiór(lubUporządkowany zbiórjeśli oznaczony{uporządkowany}).
kontekst Company
self.manager.age > 40 -- manager to 0..1, zwraca obiekt Person
self.employee->notEmpty() -- employee to 0..*, zwraca Zbiór
Jeśli nazwa roli jest brakująca, użyj małej litery nazwy klasy po drugiej stronie (np. self.bank).
Klasy powiązań
Przejdź do klasy powiązania używając małej litery nazwy klasy powiązania.
kontekst Person
self.job->size() >= 1 -- 'job' to klasa powiązania między Person i Company
Powiązania rekurencyjne
Gdy powiązanie wraca do tej samej klasy (np. Małżeństwo między dwoma osobami), musisz określić kierunek za pomocą nawiasów kwadratowych [nazwaRoli].
kontekst Osoba
self.marszyna[żona]->select(m | m.zakończona = false)->size() = 1
Zaawansowane powiązania
Użyj atrybutów jako wyróżnika w nawiasach kwadratowych, aby wybrać konkretny obiekt.
kontekst Bank
self.klient[12345] -- Zwraca konkretną osobę z numerem konta 12345
self.klient -- Zwraca zbiór wszystkich klientów
Przypisywanie typu (rzutowanie)
Użyj oclAsType() aby uzyskać dostęp do właściwości podtypu lub ukrytej właściwości nadklasy.
self.oclAsType(Pod).p -- Dostęp do właściwości 'p' zdefiniowanej w podtypie
self.oclAsType(Nad).p -- Dostęp do właściwości 'p' zdefiniowanej w nadklasie
5. Pisanie ograniczeń
1. Inwarianty (inv)
Warunki, które muszą zawsze być prawdziwe dla wszystkich instancji klasy.
kontekst Firma
inv: self.liczbaPracownikow <= 50
inv SME: self.cenaAkcyji() > 0 -- 'SME' to opcjonalna nazwa ograniczenia
2. Warunki wstępne i końcowe (pre, post)
Ograniczenia związane z operacjami.
-
pre: Musi być prawdziwe przed wykonaniem. -
post: Musi być prawdziwe po wykonanie. Użyjwynikdla wartości zwracanej. -
@pre: Używane w warunkach po, aby odwołać się do wartości właściwości przed rozpoczęcia operacji.
kontekst Osoba::dochod(): Integer
pre dorosly: self.wiek >= 18
post wynikOK: wynik < 5000
kontekst Firma::zatrudnijPracownika(p: Osoba)
post: pracownik = pracownik@pre->włączając(p)
3. Wyrażenia ciała, początkowe i wyprowadzone
-
ciało: Definiuje wynik operacji zapytania (może być rekurencyjne). -
init: Definiuje wartość początkową atrybutu. -
wyprowadź: Definiuje atrybut wyprowadzony.
kontekst Osoba::dochod(): Integer
ciało: self.praca.pensja->suma()
kontekst Osoba::zamężna: Logiczna
init: fałsz
kontekst Firma::liczbaPracownikow: Integer
wyprowadź: self.pracownik->rozmiar()
6. Zaawansowane cechy OCL
Wyrażenia let i def
-
let: Definiuje zmienną lokalną wewnątrz pojedynczego wyrażenia.let sumaGodzin: Integer = self.pracujeNa->zbieraj(godziny)->suma() w sumaGodzin >= 30 -
def: Definiuje ponownie używane zmienne lub operacje przypisane do klasyfikatora.def: maTytul(t: String): Logiczna = self.praca->istnieje(tytul = t)
Nieokreślone wartości (logika trójwartościowa)
Jeśli podwyrażenie jest nieokreślone (np. przeglądanie nullowego związku), całe wyrażenie jest nieokreślone. Jednak operatory logiczne mają wyjątki skrótu:
-
prawda lub <nieokreślone>= prawda -
fałsz i <nieokreślone>= fałsz -
fałsz implikuje <undefined>= prawda -
<undefined> implikuje prawdę= prawda
Cechy klasy i zdefiniowane właściwości obiektów
-
allInstances(): Zwraca zbiór wszystkich istniejących instancji klasy.Person.allInstances()->size() <= 100 -
Właściwości obiektów:
oclIsTypeOf(Typ),oclIsKindOf(Typ),oclInState(Stan),oclIsNew(używane w warunkach po).
7. Kompleksowy przykład badania: Model Firmy
Aby utwierdzić te koncepcje, przedstawiamy zestaw ograniczeń integralności stosowanych do modelu bazy danych Firmy (Pracownicy, Działy, Projekty, Zależni).
Podstawowe ograniczenia atrybutów
-- Wiek musi być >= 18
kontekst Pracownik inv: self.wiek() >= 18
-- Data zatrudnienia musi być późniejsza niż data urodzenia
kontekst Pracownik inv: self.dataZatrudnienia > self.dataUrodzenia
-- Data rozpoczęcia pracy menedżera musi być późniejsza niż data zatrudnienia
kontekst Pracownik inv: self.zarzadza->niepuste() implikuje self.zarzadza.dataRozpoczecia > self.dataZatrudnienia
Ograniczenia nawigacji i relacji
-- Przypisany nadzorcy musi być starszy i zarabiać więcej niż pracownik
kontekst Pracownik
inv: self.nadzorowany->niepuste() implikuje self.wiek() > self.nadzorowany.wiek()
inv: self.nadzorowany->niepuste() implikuje self.pensja < self.nadzorowany.pensja
-- Nadzorca musi zostać zatrudniony wcześniej niż jego podwładni
kontekst Pracownik
inv: self.podwladni->niepuste() implikuje self.podwladni->forall(e | e.dataZatrudnienia > self.dataZatrudnienia)
-- Menedżer działu musi być pracownikiem tego działu
kontekst Dział
inv: self.pracujeW->includes(self.zarzadza.pracownik)
-- Lokalizacja projektu musi być jedną z lokalizacji działu
kontekst Projekt
inv: self.kontroluje.lokalizacje->includes(self.lokalizacja)
-- Pracownik może pracować tylko nad projektami kontrolowanymi przez swój dział
kontekst Pracownik
inv: self.pracujeW.kontroluje->includesAll(self.pracujeNad.projekt)
Ograniczenia kolekcji i iteracji
-- Numer ubezpieczenia społecznego musi być unikalnym identyfikatorem dla wszystkich pracowników
kontekst Pracownik
inv: Employee.allInstances->forAll(e1, e2 | e1 <> e2 implikuje e1.SSN <> e2.SSN)
-- Imię i relacja zależnego muszą być unikalne dla każdego pracownika
kontekst Pracownik
inv: self.zależni->forAll(e1, e2 |
e1 <> e2 implikuje (e1.imie <> e2.imie lub e1.relacja <> e2.relacja))
-- Atrybut pochodny: liczba pracowników w dziale
kontekst Dział
inv: self.liczbaPracownikow = self.pracujeW->size()
-- Pracownik może pracować nad maksymalnie 4 projektami
kontekst Pracownik
inv: self.pracujeNad->size() <= 4
Złożona logika przy użyciu niech i wybierz
-- Łączny czas pracy musi wynosić od 30 do 50 godzin tygodniowo
kontekst Pracownik
inv: niech totGodz: Integer = self.worksOn->zbierz(godziny)->suma()
w totGodz >= 30 i totGodz <= 50
-- Na konkretnym projekcie może pracować maksymalnie 2 pracownicy przez mniej niż 10 godzin
kontekst Projekt
inv: self.worksOn->wybierz(godziny < 10)->rozmiar() <= 2
-- Na projekcie mogą pracować tylko kierownicy działów przez mniej niż 5 godzin
kontekst Pracownik
inv: self.worksOn->wybierz(godziny < 5)->niepusty() implikuje
self.worksFor.manages.employee = self
-- Pracownicy bez podwładnych muszą pracować >= 10 godzin na WSZYSTKICH swoich projektach
kontekst Pracownik
inv: self.subordinates->pusty() implikuje self.worksOn->dlaWszystkich(godziny >= 10)
-- Kierownik działu musi pracować >= 5 godzin na wszystkich projektach kontrolowanych przez dział
kontekst Dział
inv: self.controls->dlaWszystkich(p: Projekt |
self.manages.employee.worksOn->wybierz(godziny >= 5)->zawiera(p))
Zapobieganie cyklom (logika rekurencyjna)
-- Pracownik nie może nadzorować samego siebie
kontekst Pracownik inv: self.subordinates->wyklucza(self)
-- Hierarchia nadzoru nie może być cykliczna
kontekst Pracownik
def: wszystkiePodwladne = self.subordinates->suma(
self.subordinates->zbierz(e: Pracownik | e.wszystkiePodwladne))
inv: self.wszystkiePodwladne->wyklucza(self)














