Object Constraint Language (OCL) – wersja 2.0 Standardowy język definicji ograniczeń dla modeli UML
Wprowadzenie OCL jest formalnym językiem specyfikacji wyrażeń występujących w modelach UML Wyrażenia OCL nie mogą mieć efektów ubocznych – nie mogą zmieniać stanu systemu OCL może natomiast specyfikować zmianę stanu OCL nie jest językiem programowania Nie da się w nim wyrazić zagadnień związanych z implementacją
Użycie wyrażeń OCL Specyfikacja zapytań Specyfikacja niezmienników klas i typów danych w modelu klas Specyfikacja niezmienników typu dla stereotypów Specyfikacja warunków wstępnych (preconditions) i warunków końcowych (postconditions) dla operacji i metod Specyfikacja warunków dozoru (guards) Specyfikacja celu komunikatów i akcji Specyfikacja ograniczeń dla operacji Specyfikacja reguł wyprowadzenia atrybutów pochodnych
Przykładowy diagram klas
Związki OCL z metamodelem UML – określenie kontekstu Każde wyrażenie OCL jest pisane w kontekście pewnej instancji konkretnego typu Wewnątrz wyrażenia instancja stanowiąca kontekst określana jest słowem zarezerwowanym self Kontekst wewnątrz wyrażenia można określić przy pomocy deklaracji kontekstu context Company Deklarację kontekstu zwykle się opuszcza jeśli dane ograniczenie jest pokazane na diagramie UML. W powyższej deklaracji self jest instancją klasy Company.
Związki z metamodelem UML – niezmienniki Niezmiennik to rodzaj ograniczenia, który musi być spełniony przez wszystkie instancje danego klasyfikatora w każdej chwili. context Company inv: self.numberOfEmployees > 50 self zazwyczaj można opuścić: inv: numberOfEmployees > 50
Związki z metamodelem UML – niezmienniki (c.d.) Można zdefiniować symbol zastępujący self: context c: Company inv: c.numberOfEmployees > 50 Ograniczenie może mieć nazwę: inv enoughEmployees: c.numberOfEmployees > 50
Związki z metamodelem UML – warunki wstępne i końcowe Kontekstem jest operacja lub inna cecha czynnościowa self jest instancją typu, do którego należy dana cecha czynnościowa Słowo zarezerwowane result określa wynik operacji context Type::operation(par1: T1, ...): ReturnType pre: ... post: ...
Związki z metamodelem UML – warunki wstępne i końcowe (c.d.) Przykład warunku końcowego: context Person::income(d: Date): Integer post: result = 5000 Warunki wstępne i końcowe mogą mieć nazwy: context ClassA::op(par: T): ReturnType pre parameterOk: par > 63 post resultOk: result = par + 23
Związki z metamodelem UML – określenie kontekstu pakietu package A::B context C inv: ... context C::op1() pre: ... endpackage
Związki z metamodelem UML – ograniczenia na zwracaną wartość Ograniczenia body condition dla operacji będących zapytaniami context ClassA::op(): T body: ... Przykład: context Person::getCurrentSpouse(): Person pre: self.isMarried = true body: self.marriages->select(ended = false).spouse
Związki z metamodelem UML – wartości początkowe i pochodne Wyrażenia OCL mogą opisywać wartości początkowe lub pochodne (wyprowadzone) atrybutów i końców powiązań context Person::income: Integer init: parents.income->sum() * 0.01 -- kieszonkowe derive: if underAge then parents.income->sum() * 0.01 else job.salary endif -- rozpoczyna komentarz
Typy podstawowe OCL Integer Real Boolean String Operacje (przykładowe): +, -, *, /, abs() Real +, -, *, floor() Boolean and, or, xor, not, implies, if-then-else String concat(), size(), substring() Wszystkie są instancjami InfrastructureLibrary::Core::Constructs::PrimitiveType
Typy z modeli UML Wszystkie klasyfikatory zdefiniowane w danym modelu UML są typami dostępnymi w wyrażeniach OCL dołączonych do tego modelu
Typy wyliczeniowe context Person inv: wife.gender = Gender::female Powyższy niezmiennik jest niekompletny (brakuje sprawdzenia czy wife->notEmpty() )
Definiowanie zmiennych o zasięgu pojedynczego wyrażenia context Person inv: let income: Integer = job.salary->sum() in if isUnemployed then income < 100 else income >= 100 endif
Definiowanie dodatkowych atrybutów/operacji Przy pomocy ograniczeń oznaczonych stereotypem <<definition>> można w wyrażeniach OCL definiować dodatkowe atrybuty i operacje klasyfikatorów Pozwala to współdzielić zmienne i operacje pomiędzy kilkoma ograniczeniami Dodatkowe cechy stają się zwykłymi atrybutami i operacjami klasyfikatora o stereotypie <<OclHelper>> Można je równoważnie (używając stereotypu <<OclHelper>>) zdefiniować w modelu UML
Definiowanie atrybutów/operacji – przykład context Person def: income: Integer = job.salary->sum() def: nickname: String = ‘Super Dyzio’ def: hasTitle(t: String): Boolean = job->exists(title = t)
Zgodność typów Każdy typ jest zgodny z każdym swoim nadtypem (uogólnieniem) Relacja zgodności jest przechodnia (nie jest symetryczna) Typ kolekcji elementów typu T1 jest zgodny z typem kolekcji elementów typu T2 o ile T1 jest zgodny z T2 Collection(T1) jest zgodny z Collection(T2) o ile T1 jest zgodny z T2 Ta sama reguła obowiązuje dla par Set(T1)/Set(T2), OrderedSet(T1)/OrderedSet(T2), Bag(T1)/Bag(T2) i Sequence(T1)/Sequence(T2)
Komentarze w OCL Komentarze wierszowe – od dwuznaku ‘--’ do końca linii -- komentarz wierszowy Komentarze blokowe – ograniczone dwuznakami ‘/*’ i ‘*/’ /* komentarz blokowy */
Wartości nieokreślone Są wynikiem niektórych operacji (np. branie pierwszego elementu pustej kolekcji) Na ogół wyrażenie o składowych nieokreślonych ma wartość nieokreśloną Wyjątek stanowią wyrażenia logiczne, w których wartość nieokreślona spełnia reguły logiki trójwartościowej Można jawnie testować, czy dany obiekt ma wartość nieokreśloną przy pomocy operacji oclIsUndefined() typu OclAny
Atrybuty obiektów Wynikiem wyrażenia jest wartość typu atrybutu context Person inv: self.age > 0 Wynikiem wyrażenia jest wartość typu atrybutu W przypadku atrybutów o krotności większej niż 1 wynikiem jest kolekcja
Operacje obiektów Zwykłe wywołanie operacji – wynikiem jest wartość typu zwracanego przez operację aPerson.income(aDate) Jeśli operacja ma parametry inout lub out wynikiem jest krotka (tuple) złożona ze wszystkich parametrów out, inout i zwracanej wartości
Przykład operacji Jeśli operacja ma sygnaturę Person::income(d: Date, out bonus: Integer): Integer to wynikiem jej wywołania jest krotka typu Tuple(bonus: Integer, result: Integer) do której składowych można odwołać się następująco: p.income(d).bonus = 100 and p.income(d).result = 3000
Końce powiązań i nawigacja context Company inv: self.manager.isUnemployed = false inv: self.employee->size() <= 50 Jeśli krotnością końca jest co najwyżej 1 wartością wyrażenia jest obiekt odpowiedniego typu self.manager to obiekt klasy Person Jeśli koniec ma górne ograniczenie krotności większe niż 1 wartością jest kolekcja standardowo Set(T), dla właściwości uporządkowanych OrderedSet(T) self.employee to kolekcja Set(Person) OCL definiuje standardowe typy kolekcji i operacje na nich. Operator dostępu do operacji kolekcji to ‘->’.
Brakujące nazwy końców powiązań Wyprowadzane są z nazw typów na końcach przez zamianę pierwszej litery na małą
Końce o krotności nie większej niż 1 Mogą być traktowane jako pojedyncze obiekty albo zbiory złożone z pojedynczych obiektów (lub puste) context Person inv: self.wife->notEmpty() implies self.wife.gender = Gender::female
Klasy powiązań Do specyfikacji nawigacji do klasy powiązania OCL stosuje regułę nazewniczą analogiczną do reguły brakującej nazwy końca context Person inv: self.job->size() <= self.age
Klasy powiązań zwrotnych Nawigacja w stronę końca bosses self.employeeRanking[bosses]->sum() > 0 Nawigacja w stronę końca employees self.employeeRanking[employees]->sum() > 0
Powiązania kwalifikowane W przypadku użycia wartości kwalifikatora otrzymujemy pojedynczy obiekt context Bank inv: self.customer[11].gender = Gender::male Opuszczając kwalifikator otrzymujemy kolekcję inv: self.customer->size() >= 30000
Predefiniowane operacje wszystkich obiektów Następujące operacje są zdefiniowane we wspólnym przodku wszystkich typów OCL, tzn. OclAny, a zatem są dostępne dla wszystkich obiektów: oclIsTypeOf(t: OclType): Boolean oclIsKindOf(t: OclType): Boolean oclInState(s: OclState): Boolean oclIsNew(): Boolean oclIsUndefined(): Boolean oclIsInvalid(): Boolean oclAsType(t: OclType): T allInstances(): Set(T) oclIsKindOf() uwzględnia nadtypy allInstances() to operacja klasowa
Dostęp do przedefiniowanych atrybutów i operacji Jeśli B jest podklasą A i przedefiniowuje właściwość A::p1 i jeśli self jest klasy B to self.p1 oznacza (zawsze) B::p1, natomiast self.oclAsType(A).p1 oznacza A::p1
Przykład cechy klasowej context Singleton inv: Singleton.allInstances()->size() <= 1
Przykład określenia stanu obiektu c.oclInState(On) c.oclInState(Off) c.oclInState(Off::NoPower)
Wartości poprzednie w warunkach końcowych W warunku końcowym wartość właściwości jest rozumiana jako jej wartość w momencie zakończenia operacji stanowiącej kontekst Jednak często jest potrzebne odniesienie do wartości właściwości w momencie rozpoczynania operacji – służy do tego operator przyrostkowy @pre context Company::hireEmployee(p: Person) post: numberOfEmployees = numberOfEmployees@pre + 1 and employee = employee@pre->including(p) and stockprice() = stockprice@pre() + 10 @pre jest zawsze przyrostkiem nazwy operacji/atrybutu (przed ewentualnymi parametrami)
Wartości poprzednie (c.d.) Jeśli wartością poprzednią właściwości jest obiekt, to jego dla jego właściwości uwzględniane są wartości aktualne a.b@pre.c chyba że jawnie wyspecyfikujemy odniesienie do wartości poprzednich a.b@pre.c@pre
Krotki Krotka jest zestawem nazwanych części o zróżnicowanych typach. Poniższe definicje literałów krotkowych są równoważne. Tuple {name: String = ‘Kowalski’, age: Integer = 20} Tuple {name = ‘Kowalski’, age = 20} Tuple {age = 20, name = ‘Kowalski’} Odniesienie do składowej części krotki: Tuple {age = 20, name = ‘Jan’}.age = 50 Wartość części krotki może być określona przez dowolne wyrażenie OCL
Kolekcje OCL OCL zawiera 5 typów kolekcji: Abstrakcyjny typ Collection(T) Specjalizujące go typy konkretne Set(T) – zbiór elementów typu T OrderedSet(T) – zbiór uporządkowany Bag(T) – wielozbiór (zbiór z powtórzeniami) Sequence(T) – ciąg (wielozbiór uporządkowany)
Kolekcje OCL a nawigacja powiązań Kolekcje są wynikami nawigacji powiązań Set(T) dla powiązań prostych (bez dodatkowych atrybutów) Bag(T) dla powiązań złożonych oraz prostych z powtórzenami OrderedSet(T) dla powiązań prostych uporządkowanych Sequence(T) dla powiązań prostych uporządkowanych z powtórzeniami
Literały kolekcji Set { ‘zielony’, ‘niebieski’, ‘czerwony’ } OrderedSet { ‘Ala’, ‘Ela’, ‘Ola’ } Bag { 1, 1, 3, 5, 1, 10, 10 } Dla literałów ciągów istnieje notacja skrótowa. Poniższe definicje są równoważne: Sequence { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } Sequence { 1..10 } Sequence { 1..(3+7) }
Operacje na kolekcjach Standardowa biblioteka OCL zawiera dużą liczbę operacji typu Collection(T) i jego podtypów Zapewnia to możliwość manipulowania standardowymi kolekcjami Operacje standardowe nigdy nie zmieniają stanu kolekcji (są zapytaniami)
Operacja select Pozwala wyspecyfikować podzbiór wyjściowej kolekcji przez podanie warunku przynależności Składnia collection->select(warunek) collection->select(v | warunek-z-v) collection->select(v: Typ | warunek-z-v)
Operacja select – przykłady context Company inv: employee->select(age > 50)->notEmpty() inv: employee->select(p | p.age > 50)->notEmpty() inv: employee->select(p: Person | p.age > 50)->notEmpty()
Operacja reject Operacja o składni analogicznej do select, ale określająca warunek odrzucenia context Company inv: employee->reject(isMarried)->isEmpty() inv: employee->reject(p | p.isMarried)->isEmpty() inv: employee->reject(p: Person | p.isMarried)->isEmpty()
Operacja collect Konstruuje kolekcję wywiedzioną z danej kolekcji, ale zawierającą inne obiekty Set(T) przekształca w Bag(T) OrderedSet(T) przekształca w Sequence(T) Przykład (definicje równoważne) employee->collect(birthDate) employee->collect(person | person.birthDate) employee->collect(p: Person | p.birthDate)
Operacja collect – notacja skrótowa Zamiast employee->collect(birthDate) self.employee->collect(income(‘2009-10-31’)) można pisać employee.birthDate self.employee.income(‘2009-10-31’)
Kwantyfikator ogólny context Company inv: employee->forAll(age <= 65) inv: employee->forAll(p | p.age <= 65) inv: employee->forAll(p: Person | p.age <= 65) inv: employee->forAll(e1, e2 | e1 <> e2 implies e1.lastName <> e2.lastName)
Kwantyfikator szczegółowy context Company inv: employee->exists(firstName = ‘Elvis’) inv: employee->exists(p | p.firstName = ‘Elvis’) inv: employee->exists(p: Person | p.firstName = ‘Elvis’)
Operacja iterate Najogólniejsza z operacji iteratorowych Pozwala stworzyć nową kolekcję na podstawie danej przy pomocy jawnie określonego wyrażenia collection->iterate(e: Type; acc: Type2 = <wyr> | wyrażenie-z-e-i-acc)
Operacja iterate - przykład Następująca operacja collect collection->collect(x: T | x.property) może być zdefiniowana przy pomocy iterate w następujący sposób collection->iterate(x: T; acc: Bag(T2) = Bag {} | acc->including(x.property))
Dodatkowe operacje typu Collection(T) includes(object: T): Boolean excludes(object: T): Boolean count(object: T): Boolean includesAll(c: Collection(T)): Boolean excludesAll(c: Collection(T)): Boolean sum(): T product(c: Collection(T2)): Set(Tuple(first: T, second: T2)) product() wylicza iloczyn kartezjański
Dodatkowe operacje wspólne dla Bag(T), Set(T) i Sequence(T) operator = (również dla OrderedSet(T)) including(object: T): odpowiednia_kolekcja excluding(object: T): odpowiednia_kolekcja flatten(): odpowiednia_kolekcja asSet(): Set(T) asBag(): Bag(T) asOrderedSet(): OrderedSet(T) asSequence(): Sequence(T)
Dodatkowe operacje wspólne dla Set(T) i Bag(T) union(s: Set(T)): odpowiednia_kolekcja union(b: Bag(T)): Bag(T) intersection(s: Set(T)): Set(T) intersection(b: Bag(T)): odp_kolekcja
Dodatkowe operacje kolekcji uporządkowanych append(object: T): odpowiednia_kolekcja prepend(object: T): odpowiednia_kolekcja insertAt(index: Integer, object: T): odpowiednia_kolekcja at(index: Integer): T indexOf(object: T): Integer first(): T last(): T
Dodatkowe operacje Set(T) OrderedSet(T) Sequence(T) operator - (s: Set(T)): Set(T) symmetricDifference(s: Set(T)): Set(T) OrderedSet(T) subOrderedSet(lower: Integer, upper: Integer): OrderedSet(T) Sequence(T) subSequence(lower: Integer, upper: Integer): Sequence(T)
Dodatkowe operacje iteratorowe Collection(T) isUnique(iterator | body): Boolean any(iterator | body): T one(iterator | body): Boolean Set(T) sortedBy(iterator | body): OrderedSet(T) Bag(T) i Sequence(T) sortedBy(iterator | body): Sequence(T)
Komunikaty w OCL Komunikaty (instancje OclMessage) służą do specyfikacji zdarzenia komunikacyjnego – wywołania operacji lub wysłania sygnału Operator ‘^’ określa czy komunikat został wysłany context Subject::hasChanged() post: observer^update(3, 19) post: observer^update(?, ?) post: observer^update(?: Integer, ?: Integer)
Analiza instancji komunikatów Operator ‘^^’ zwraca ciąg rzeczywiście wysłanych komunikatów observer^^update(3, 4) Formalne parametry komunikatu są dostępne jako atrybuty instancji OclMessage context Subject::hasChanged() post: let msgs: Sequence(OclMessage) = observer^^update(?, ?) in msgs->notEmpty() and msgs->exists(m | m.j > m.i) Tutaj: Observer::update(i: Integer, j: Integer)
Analiza instancji komunikatów (c.d.) Analiza komunikatów wysłanych do różnych obiektów context Subject::hasChanged() post: let msgs: Sequence(OclMessage) = observers->collect(o | o^^update(?, ?)) in msgs->forAll(m | m.i < m.j)
Przetwarzanie zwracanych wartości context Person::giveSalary(amount: Integer) post: let msg: OclMessage = company^^getMoney(amount) in msg.hasReturned() and msg.result() = true Jest to sposób na określenie sposobu wyliczenia zwracanych wartości dla operacji, które nie są zapytaniami
Pozostałe operacje klasy OclMessage isOperationCalled(): Boolean isSignalSent(): Boolean
Inne typy specjalne OCL OclVoid Jedyna instancja nazywa się null (odpowiada specyfikacji wartości UML NullLiteral) OclInvalid Jedyna instancja nazywa się invalid OclElement OclType
Rozwiązywanie konfliktów nazw właściwości Może zachodzić w przypadku zmiennych niejawnych self niejawne iteratory Rozstrzygane na korzyść zmiennych z najbardziej wewnętrznego zasięgu
Przykład konfliktu nazw context Person inv: employer->forAll( employee->exists(lastName = name) ) znaczy to samo, co employee->exists(p | p.lastName = name) )
Użycie wyrażeń OCL w modelach UML