Kompletny przewodnik po używaniu OCL w UML z przykładami PlantUML
Wprowadzenie

Choć UML (Unified Modeling Language) jest wyjątkowy w wizualizacji strukturysystemu, brakuje mu precyzji do wyrażania skomplikowanych reguł biznesowych i semantyki. To właśnie tutaj pojawia się Język ograniczeń obiektów (OCL) . OCL działa jak formalny „klej”, który łączy diagramy UML z rygorystyczną, jednoznaczną logiką biznesową.
Ten przewodnik wyjaśnia, jak OCL integruje się z UML, kluczowe koncepcje, które musisz znać, oraz zapewnia praktyczne PlantUMLprzykłady do wizualizacji tych ograniczeń.
1. Synergia: Jak OCL uzupełnia UML
Wyobraź sobie UML jako „szkielet” Twojego systemu (klasy, asocjacje, stany) a OCL jako „układ nerwowy” (reguły, warunki i logika, które kierują zachowaniem szkieletu).
Reprezentacja wizualna w UML
W standardowym UML ograniczenia OCL są zwykle przedstawiane za pomocą Uwagi przypisanych do odpowiednich elementów modelu (klasy, operacje lub stany).
-
Stereotyp:Często notatki zawierające OCL otrzymują stereotyp
{ograniczenie}stereotyp. -
Kontekst:Każdy wyrażenie OCL zaczyna się od słowa kluczowego
contextdeklarującego, do którego elementu UML ma dotyczyć reguła.
2. Kluczowe pojęcia OCL w UML
A. Inwarianty klasy (inv)
Inwarianty to warunki, które muszązawsze być prawdziwedla każdego wystąpienia klasy, niezależnie od tego, jakie operacje są wywoływane.
Kluczowe pojęcie:Jeśli inwariant jest naruszony, system znajduje się w nieprawidłowym stanie.

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #FFF9C4
class BankAccount {
- accountNumber: String
- balance: Real
- isOverdraftEnabled: Boolean
+ withdraw(amount: Real)
+ deposit(amount: Real)
}
note right of BankAccount
**Inwarianty klasy:**
context BankAccount
-- Saldo nigdy nie może być ujemne, chyba że kredyt odbiegowy jest włączony
inv balanceRule:
self.balance >= 0 lub self.isOverdraftEnabled
-- Limit kredytu odbiegowego nie może przekraczać 500
inv overdraftLimit:
self.isOverdraftEnabled implikuje self.balance >= -500
end note
@enduml
B. Umowy operacji (pre, post, body)
Operacje (metody) mogą mieć ograniczenia definiujące ich zachowanie przed i po wykonaniu.
-
Warunek wstępny (
pre): Co musi być prawdziwe przed rozpoczęciu operacji. (Odpowiedzialność wywołującego). -
Warunek końcowy (
post): Co będzie prawdziwe po zakończeniu operacji. (Gwarancja operacji). -
Ciało (
ciało): Określa dokładną wartość zwracaną przez operacje zapytania.

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #E8F5E9
class ShoppingCart {
- items: Bag<Product>
- totalAmount: Real
+ checkout(): Receipt
+ getTotal(): Real
}
note right of ShoppingCart
**Umowy operacji:**
-- Warunek wstępny: Koszyk nie może być pusty
context ShoppingCart::checkout(): Receipt
pre: self.items->notEmpty()
-- Warunek końcowy: Koszyk jest pusty, generowany jest paragon
post: self.items->isEmpty() and result <> null
-- Ciało: definicja operacji zapytania
context ShoppingCart::getTotal(): Real
body: self.items->collect(price)->sum()
end note
@enduml
C. Wartości pochodne i początkowe (wyprowadź, init)
Zamiast przechowywać nadmiarowe dane, UML pozwala oznaczać atrybuty jako {wyprowadzone} lub {początkowe}, a OCL definiuje ich dokładną wartość.

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #E3F2FD
class Employee {
- firstName: String
- birthDate: Date
- {derived} age: Integer
- {initial} isActive: Boolean = true
}
note right of Employee
**Wartości pochodne i początkowe:**
-- Oblicz wiek dynamicznie
context Employee::age: Integer
derive: self.birthDate.yearsSinceToday()
-- Stan początkowy przy tworzeniu
context Employee::isActive: Boolean
init: true
end note
@enduml
D. Ochrony maszyn stanów
W diagramach maszyn stanów przejścia mogą miećochrony—wyrażenia logiczne, które muszą mieć wartość true, aby przejście mogło się wydarzyć. OCL to standardowy język do pisania tych ochron.

@startuml
skinparam stateBackgroundColor #F3E5F5
[*] --> Created
Created --> PendingApproval : submit()
state PendingApproval {
}
PendingApproval --> Approved : [total < 10000]
PendingApproval --> Rejected : [total >= 10000]
Approved --> Shipped : ship()
Shipped --> [*]
note right of PendingApproval
**Inwariant stanu:**
context Order
inv: self.status = 'Pending' implikuje
self.submittedBy->notEmpty()
end note
note right of Approved
**Warunek po:**
context Order::approve()
post: self.status = 'Approved' i
self.approvedDate <> null
end note
@enduml
3. Kompleksowy przykład badawczy: Model firmy
Zastosujmy OCL do złożonego diagramu klas UML obejmującegoEmployee, Department, orazProject. Pokazuje, jak OCL przemieszcza się po powiązaniach i używa iteratorów kolekcji (select, forAll, size).

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #FFFDE7
skinparam noteBorderColor #FBC02D
class Employee {
- SSN: String
- firstName: String
- birthDate: Date
- hireDate: Date
- salary: Float
+ age(): Integer
}
class Department {
- number: Integer
- name: String
- locations: Set<String>
- {derived} nbrEmployees: Integer
}
class Project {
- number: Integer
- name: String
- location: String
}
Employee "0..*" -- "1" Department : worksFor > department
Employee "0..*" -- "0..*" Project : worksOn > project
Employee "1" -- "0..1" Department : manages > managedDept
Department "1" -- "0..*" Project : controls > projects
Employee "1" -- "0..*" Employee : supervisor > subordinates
note right of Employee
**Złożone inwarianty i nawigacja:**
context Employee
-- 1. Podstawowe zasady atrybutów
inv ageCheck: self.age() >= 18
-- 2. Nawigacja po powiązaniach (mnożność)
inv maxProjects: self.worksOn->size() <= 4
-- 3. Używanie iteratorów (forAll)
-- Przypisany musi być zatrudniony wcześniej niż podwładni
inv hireOrder: self.subordinates->forAll(
sub | sub.hireDate > self.hireDate
)
-- 4. Reguła między powiązaniami
-- Pracownik może pracować tylko nad projektami kontrolowanymi przez swój dział
inv projectControl:
self.worksFor.controls->includesAll(self.worksOn)
-- 5. Unikalny identyfikator (klucz)
inv uniqueSSN: Employee.allInstances()->forAll(e1, e2 |
e1 <> e2 implikuje e1.SSN <> e2.SSN
)
end note
note bottom of Department
**Atrybut pochodny:**
context Department::nbrEmployees: Integer
derive: self.worksFor->size()
end note
note right of Project
**Inwariant projektu:**
context Project
-- Lokalizacja projektu musi być jedną z lokalizacji działu
inv validLocation:
self.controls.locations->includes(self.location)
end note
@enduml
4. Zaawansowane techniki OCL w UML
A. Używanielet dla czytelności
Gdy wyrażenie OCL staje się zbyt złożone, użyj letsłowa kluczowego do zdefiniowania zmiennych lokalnych w ograniczeniu. Ułatwia to odczytywanie notatki UML.

@startuml
skinparam classAttributeIconSize 0
class Employee {
- worksOn: Set<Project>
}
note right of Employee
**Używanie 'let' do złożonej logiki:**
context Employee
-- Oblicz całkowitą liczbę godzin i sprawdź granice
inv workingHours:
let totalHours: Integer =
self.worksOn->collect(hours)->sum()
in totalHours >= 30 i totalHours <= 50
end note
@enduml
B. Obsługa poprzednich wartości w warunkach po (@pre)
Podczas definiowania warunku po, często musisz porównać stan nowy stan z stary stanem. OCL używa @pre przyrostka do uzyskania wartości właściwości dokładnie takiej, jak była przed rozpoczęciem operacji.

@startuml
skinparam classAttributeIconSize 0
class Company {
- employees: Set<Employee>
+ hireEmployee(p: Employee)
}
note right of Company
**Używanie @pre w warunkach po:**
context Company::hireEmployee(p: Employee)
-- Nowy zbiór pracowników to stary zbiór + nowa osoba
post: self.employees = self.employees@pre->including(p)
end note
@enduml
5. Najlepsze praktyki dla OCL w UML
-
Nazwij swoje ograniczenia: Zawsze nadawaj nazwę swoim niezmiennikom i warunkom (np.
inv maxProjects:). Ułatwia to odwoływanie się do nich w dokumentacji lub macierzach śladu. -
Używaj notatek strategicznie: W PlantUML i wizualnych narzędziach UML nie zanieczyszczaj pola klasy OCL. Umieść OCL w sąsiednich notatkach. Zachowuje to czytelność i przejrzystość diagramu.
-
Nie nadmiernie ograniczaj: Pisz tylko OCL dla reguł, które nie mogą być łatwo wyrażone za pomocą wielkości UML (np.
0..1,1..*) lub typów podstawowych. Jeśli reguła może być pokazana wizualnie, pokaż ją wizualnie. -
Wykorzystaj iteratory kolekcji: Opanuj
->select(),->forAll(),->exists(), oraz->collect(). Są to najmocniejsze narzędzia do nawigowania po powiązaniach UML w OCL. -
Sprawdź wartości null/puste zbiory: Podczas nawigowania po opcjonalnych powiązaniach (wielkość
0..1lub0..*), zawsze używaj->notEmpty()lub->isEmpty()przed uzyskiwaniem dostępu do właściwości, aby uniknąć błędów nieokreślonej oceny.














