OCL precyzyjne modelowanie w UML

Slides:



Advertisements
Podobne prezentacje
Język C/C++ Funkcje.
Advertisements

Programowanie obiektowe
Programowanie obiektowe
Wykład 10 Metody Analizy Programów Specyfikacja Struktur Danych
REGUŁOWO-MODELOWE SKORUPOWE SYSTEMY EKSPERTOWE Część 1
Wprowadzenie do C++ Zajęcia 2.
PROGRAMOWANIE STRUKTURALNE
Badania operacyjne. Wykład 1
Materiały do zajęć z przedmiotu: Narzędzia i języki programowania Programowanie w języku PASCAL Część 7: Procedury i funkcje © Jan Kaczmarek.
Materiały do zajęć z przedmiotu: Narzędzia i języki programowania Programowanie w języku PASCAL Część 8: Wykorzystanie procedur i funkcji © Jan Kaczmarek.
Co UML może zrobić dla Twojego projektu?
Struktury.
Materiały pomocnicze do wykładu
Pakiety i ATD 1 Definicja. Pakietem albo jednostką programową nazywamy grupę logicznie powiązanych elementów, które mogą być typami, podtypami, obiektami.
Wstęp do programowania obiektowego
Projektowanie i programowanie obiektowe II - Wykład IV
Projektowanie i programowanie obiektowe II - Wykład II
Wstęp do interpretacji algorytmów
Modele baz danych - spojrzenie na poziom fizyczny
Projektowanie - wprowadzenie
Bazy Danych II prowadzący: mgr inż. Leszek Siwik
Podstawy programowania
Podstawy układów logicznych
ANNA BANIEWSKA SYLWIA FILUŚ
Elementy Rachunku Prawdopodobieństwa i Statystyki
Języki i automaty część 3.
Programowanie obiektowe III rok EiT
Dziedziczenie Maciek Mięczakowski
Inicjalizacja i sprzątanie
XML – eXtensible Markup Language
Algorytmy.
Rozwiązanie zadań do zaliczenia I0G1S4 // indeks
Programowanie obiektowe – język C++
Programowanie obiektowe 2013/2014
ZWIĄZKI MIĘDZY KLASAMI KLASY ABSTRAKCYJNE OGRANICZENIA INTERFEJSY SZABLONY safa Michał Telus.
Modelowanie obiektowe Diagramy UML – diagram przypadków użycia
Wprowadzenie do UML dr hab. inż. Kazimierz Subieta profesor PJWSTK.
Modelowanie obiektowe Diagramy klas
Projektowanie relacyjnych baz danych – postacie normalne
UML W V ISUAL S TUDIO Mateusz Lamparski. UML D EFINICJA Unified Modeling Language (UML) to graficzny język do obrazowania, specyfikowania, tworzenia i.
Interakcja człowiek – komputer Podstawy metod obiektowych mgr inż. Marek Malinowski Zakład Matematyki i Fizyki Wydz. BMiP PW Płock.
PRZYGOTOWALI Bartosz Pawlik Daniel Sawa Marcin Turbiński.
ZAPIS BLOKOWY ALGORYTMÓW
Temat 3: Integralność danych. Integralność danych, określana również mianem spójności danych, jest to funkcja SZBD, która gwarantuje, że dane nie zostaną.
Programowanie strukturalne i obiektowe C++
Model obiektowy bazy danych
URZĄDZENIA TECHNIKI KOMPUTEROWEJ
Diagram klas Kluczowymi elementami są: klasy (class)
Zarządzanie zagrożeniami
Przegląd konstrukcji języka OCL
Proces tworzenia oprogramowania Proces tworzenia oprogramowania jest zbiorem czynności i związanych z nimi wyników, które prowadzą do powstania produktu.
Algorytmy- Wprowadzenie do programowania
OCL.
Modelowanie obiektowe - system zarządzania projektami.
Projektowanie relacyjnych baz danych – diagramy związków encji
Projekt modułu Nazwa całego projektu Nazwa modułu Imię i Nazwisko Inżynieria Oprogramowania II dzień, godzina rok akademicki W szablonie na niebiesko zamieszczone.
Projektowanie bazy danych z użyciem diagramów UML Obiektowe projektowanie relacyjnej bazy danych Paweł Jarecki.
Platforma .Net.
Systemy wspomagające dowodzenie twierdzeń
Wstęp do interpretacji algorytmów
Do czego służy arkusz kalkulacyjny, jego budowa
Zmienne typy danych w VBA. MS Excel – typy danych w języku programowania VBA.
Inżynieria systemów informacyjnych
Teoria sterowania Wykład /2016
Programowanie Obiektowe – Wykład 2
Projekt modułu BANK INTERNETOWY Moduł funkcji banku
JavaBeans by Paweł Wąsala
Haskell Składnia funkcji.
POJĘCIE ALGORYTMU Wstęp do informatyki Pojęcie algorytmu
Modele baz danych - spojrzenie na poziom fizyczny
Zapis prezentacji:

OCL precyzyjne modelowanie w UML

Język OCL Język OCL (Object Constraint Language) powstał w 1995r podczas prac nad modelowaniem procesów handlowych w IBM. W pracach brali udział Steve Cook, Jos Warmer, członkowie zespołu Object Technology Practice. Na realizację przedsięwzięcia w IBM znaczny wpływ miały koncepcje przyjęte w języku Syntropy. W przeciwieństwie jednak do Syntropy w języku OCL nie korzysta się z nieznanych symboli matematycznych. Zaprojektowano go tak, aby był zarówno formalny, jak i prosty: jego składnia jest bardzo przystępna – może ją szybko przyswoić każdy, kto jest dostatecznie dobrze zaznajomiony z modelowaniem i programowaniem. OCL jest integralną częścią UML, jest przeznaczony do specyfikowania niezmienników, warunków początkowych, warunków końcowych i innego rodzaju ograniczeń. OCL może być nazwany językiem „formalnym”, lecz w przeciwieństwie do innych dostępnych obecnie tego typu języków, takich jak Objective-Z lub VDM++, nie jest zaprojektowany dla osób, które mają mocne podstawy matematyczne. Użytkownicy języka OCL korzystają ze standardu UML. Język ten opracowano pod kątem użyteczności, choć jest podbudowany logiką matematyczną i teorią zbiorów.

Język OCL OCL jest językiem wyrażeń, umożliwiającym formułowanie ograniczeń (constraints) dla modeli obiektowych i innych artefaktów powstałych w czasie modelowania obiektowego. Ograniczenie to restrykcja nałożona na jedną lub więcej wartości (części) modelu lub systemu obiektowego. Język OCL jest częścią języka UML – standardu przeznaczonego do analizy i projektowania obiektowego, opracowanego przez konsorcjum OMG. Ścisłą definicję języka OCL można znaleźć w Object Constraint Language Specification.

Definicja ograniczenia Ograniczenie to restrykcja nałożona na jedną lub więcej wartości (części) modelu lub systemu obiektowego. To co nazywa się mianem ograniczenia, jest znane w technologii obiektowej pod różnymi nazwami. Bertrand Meyer, jeden z pionierów stosowania ograniczeń, nazywa je asercjami (assertion) i definiuje jako „wyrażenie znaczenia elementu”. Asercje w rozumieniu Meyera występują w trzech odmianach: jako warunki początkowe, warunki końcowe i niezmienniki; są zdefiniowane lokalnie w obrębie klasy. Zrealizowano je w języku programowania Eiffel, w którym stanowią podstawę zasady projektowania według umowy (kontraktu). Ian Graham używa terminu asercja, jak i reguła (rule). Asercje to w jego rozumieniu „postać niezmiennika, warunku początkowego i warunku końcowego, który musi być spełniony, odpowiednio, gdy metoda jest wykonywana, wywoływana i zakończona”. Natomiast pojęcie reguły, zaczerpnięte z terminologii systemów z bazą wiedzy (knowledge-based systems), określa sposób wyrażania wzajemnego oddziaływania obiektów. James Rumbaugh definiuje ograniczenie jako „zależność funkcjonalną między bytami modelu obiektowego. W opracowanej przez niego metodzie analizy i projektowania – OMT – ograniczenia zawężają zbiór możliwych wartości bytów modelu obiektowego. Grady Booch także używa pojęcia ograniczenia, rozumiejąc je jako „wyrażenie opisujące warunek znaczeniowy, który musi być spełniony”. Podkreśla, że ograniczenie może być spełnione tylko wtedy, kiedy system jest w stanie stabilnym. Może dojść do przejściowych sytuacji, kiedy ograniczenia nałożone na system nie będą obowiązywały. Istnieje wiele różnych interpretacji ograniczenia, przy czym w każdej co innego jest istotne. Język OCL pomaga wyrazić wspólny element wszystkich definicji i ustalić zrozumiały i łatwy w użyciu standard, umożliwiający projektantowi specyfikowanie tego, co niezbędne.

Projektowanie według umowy Za pomocą warunków początkowych i końcowych można skutecznie określać operacje i metody. Używając wyrażeń OCL wewnątrz modelu UML, można wyspecyfikować warunki początkowe i końcowe operacji oraz metod dla wszystkich klas, typów i interfejsów. Zasada projektowania według umowy może być stosowana w dowolnej metodzie obiektowej. Jest jednym z podstawowych paradygmatów inżynierii oprogramowania. W terminologii obiektowej umowa jest sposobem jasnego i jednoznacznego przydzielania obiektowi odpowiedzialności (responsibilities). Obiekt jest zobowiązany do wykonania usługi wtedy i tylko wtedy, kiedy są spełnione określone warunki (prawa). Umowa jest zatem dokładną specyfikacją interfejsu obiektu, nazywanego dostawcą (supplier). Natomiast obiekty, korzystające z oferowanych przez dostawcę usług, są nazywane klientami (clients) lub konsumentami (consumers). W technologii obiektowej umowa jest formułowana niezależnie od obecności któregokolwiek klienta, niemniej jednak klient, chcąc korzystać z oferowanych usług, musi wypełnić warunki umowy.

Treść umowy W umowie ustala się listę usług dostarczanych przez obiekt, określając dla każdej z nich dwie kwestie: warunki oferowania usługi, wyniki wykonania usługi, przy założeniu spełnienia określonych warunków. Przykłady: List wysłany przed 18: powinien być dostarczony w następnym dniu roboczym pod dowolny adres Za 12zł list o wadze nie przekraczającej 80 g powinien być dostarczony pod dowolny adres w ciągu 4 godzin od wysłania Niezależnie od stopnia złożoności istotne jest, aby prawa i obowiązki zawarte w umowie były określone jednoznacznie, czyli posługując się terminologią programistyczną, formalnie wyspecyfikowane.

Zalety umów Dobrze sformułowana umowa przynosi obustronną korzyść: Dostawca zna dokładnie sytuacje, w których usługa może być wykonywana. Jeśli klient nie wypełnia warunków umowy, to dostawca nie odpowiada za konsekwencje. Dostawca może założyć, że uzgodnione warunki są zawsze spełnione. Klient zna dokładne zasady korzystania z usług. Jeśli przestrzega tych zasad, ma gwarancję jej poprawnego wykonania. Jeśli którakolwiek ze stron nie dotrzyma ustalonych reguł, to umowa zostaje zerwana. Wiadomo wówczas, kto zerwał kontrakt – czy klient nie wypełnił swoich obowiązków, czy dostawca nie wykonał usługi należycie.

Prawa i obowiązki zawarte w umowie Strona Obowiązki Prawa Klient Zapłacenie 12zł, dostarczenie listu o wadze nie przekraczającej 80 g, podanie adresu Dostarczenie listu w ciągu 4 godzin Firma oferująca usługę przesyłki ekspresowej Adres, pobranie 12zł za każdy list o wadze mniejszej niż 80g

Warunki początkowe i warunki końcowe Na interfejs obiektu składa się pewna liczba wykonywanych operacji. Prawa obiektu-dostawcy są określane w postaci warunków początkowych, które muszą być spełnione tuż przed wywołaniem operacji; obowiązki specyfikują warunki końcowe, które muszą być prawdziwe zaraz po jej zakończeniu. Niedotrzymanie warunków początkowego lub końcowego oznacza naruszenie umowy. W języku programowania Eiffel - jedynym w którym zrealizowano zasadę projektowania według umowy – taka sytuacja powoduje wywołanie wyjątku. Mechanizm wyjątków jest w nim nieodzownym elementem realizacji zasady projektowania według umowy.

Niezmienniki Bertrand Meyer uzupełnia warunki początkowe i końcowe trzecim rodzajem ograniczenia: niezmiennikiem – restrykcją ściśle związaną z klasami, typami i interfejsami. Niezmienniki jest ograniczeniem, które musi być zawsze spełnione przez wszystkie egzemplarze klas, typów lub interfejsów. Zapisuje się go w postaci wyrażenia, przyjmującego wartość true, gdy warunek jest spełniony. Jak wiemy, warunki początkowe i końcowe muszą być prawdziwe tylko w określonej chwili – odpowiednio przed i po wykonaniu operacji, niezmienniki natomiast – nieustannie. W języku UML powyższe trzy postacie ograniczeń zdefiniowano jako standardowe stereotypy: invariant, precondition, postcondition.

Zalety ograniczeń Ograniczenia przysparzają licznych korzyści: Lepsza dokumentacja – ograniczenia są doskonałą formą dokumentacji, ponieważ uzupełniają charakterystykę wizualnych modeli nowymi elementami i zależnościami między nimi. Muszą być ściśle związane z modelem. Kolejnym wersjom ograniczeń powinny towarzyszyć odpowiednie wersje modelu. Warto wiedzieć, że model wizualny może sam w sobie zawierać pewne ograniczenia. Liczebność na końcu powiązania na diagramie klas jest na przykład ograniczeniem determinującym liczbę obiektów, które mogą być powiązane. Za pomocą języka OCL można ukazać mnóstwo innego rodzaju ograniczeń. Większa precyzja – niemożliwe jest odmienne interpretowanie ograniczeń przez różnych ludzi. Ograniczenia są jednoznaczne i zwiększają dokładność modelu lub systemu, do którego się odnoszą. Stosując język OCL do wyrażania ograniczeń można użyć analizatora składni OCL, aby upewnić się co do sensowności i poprawności sformułowania ograniczeń nałożonych na model. Taka weryfikacja pomaga opracować spójny i precyzyjny model lub system. Prototyp składni OCL, zaimplementowany w Javie, jest dostępny bezpłatnie na stronach IBM: http://www.software.ibm.com/ad/ocl Komunikacja bez nieporozumień – komunikacja między użytkownikami, projektantami, programistami i innymi ludźmi odbywa się za pomocą modeli. Nieumiejętność porozumiewania się jest przyczyną niepowodzeń wielu, przedsięwzięć programistycznych. Pomagając odbiorcom zrozumieć model, większość projektantów posługuje się językiem naturalnym. Posiłkując się językiem OCL, projektanci mogą wyrazić swoje pomysły w sposób jednoznaczny i zrozumiały dla pozostałych członków grupy projektowej.

Ograniczenia deklaracyjne czy operacyjne Aby model lub system był spójny i poprawny, ograniczenia muszą być spełnione. Z doświadczenia wynika, że ograniczenia uzupełniają model informacjami, których nie można wyrazić w inny sposób. Tym co polaryzuje różne interpretacje ograniczeń, jest pytanie, co należy zrobić, gdy ograniczenie zostaje naruszone. Czyli, jakie działanie powinno zostać podjęte, gdy ograniczenia nałożone na obiekty nie są spełnione. Część osób uważa, że ograniczenia powinny deklarować, co ma być prawdą, nie mówiąc nic o tym, co powinno zostać zrobione. Tak rozumiane ograniczenia nazywane są deklaracyjnymi. W UML ograniczenia są wyrażane tylko w postaci deklaracyjnej, trzeba jednak być świadomym, że w językach programowania muszą być zapisane w postaci operacji. Tym samym ograniczenia stają się czystą metodą modelowania. Inni argumentują, że naruszenie ograniczenia powinno wywoływać jakiś rodzaj wyjątku albo przynajmniej możliwość taka powinna być dostępna jako wariant. Jeszcze inni uważają, że naruszenie powinno powodować działanie. Takie ograniczenia nazywane są operacyjnymi. W opracowanej przez Iana Grahama metodzie analizy i projektowania, o nazwie Soma, reguły są wyzwalaczami wykonania przez system określonych czynności.

Zalety języka deklaracyjnego Ograniczenia wyrażane w języku deklaracyjnym nie mają wpływu na stan systemu. Stan systemu nie ulega zmianie z powodu takiej a nie innej wartości wyrażenia. Można wyróżnić trzy zalety tego podejścia. 1. Projektant nie musi decydować o sposobie obsługi naruszonego ograniczenia. Twórca modelu w pierwszej kolejności tylko opisuje ograniczenia, które muszą być spełnione. Dopiero później zastanawia się, jak postąpić z naruszonymi ograniczeniami, często pozostawiając tę kwestię komuś innemu (programiście). W ten sposób udaje się wyraźnie oddzielać specyfikację od implementacji. 2. Ograniczenie powinno być ustalone dla danego zastosowania i wraz z upływem czasu modyfikowane tylko w razie konieczności. Działania podejmowane po naruszeniu ograniczenia często się jednak zmieniają i mogą być różne dla różnych aplikacji z danej dziedziny. 3. Jeśli wartość ograniczenia jest sprawdzana w celu podjęcia działania, wykonanie takiego testu musi być niepodzielne. Gdyby wartości, których dotyczy ograniczenie, mogły zmieniać się podczas weryfikowania ograniczenia, wynik weryfikacji nie byłby wiarygodny.

Notacja – język naturalny czy formuły matematyczne W związku z tym, że ograniczenia są niezbędne do wyrażania pewnych informacji o modelu obiektowym lub aplikacji, należy wybrać formę ich zapisu. Można więc użyć języka naturalnego lub formuł matematycznych. Czy ograniczenie wyrażone w języku naturalnym jest wystarczająco precyzyjne i jasne do zastosowania w modelowaniu oprogramowania i systemów komercyjnych? Niestety dialog prowadzony w języku naturalnym polega w znacznym stopniu na wzajemnym zrozumieniu, domniemaniach i przypuszczeniach osób prowadzących rozmowę. Stosując ograniczenia do modelu lub opisu aplikacji, chcemy wyrazić treści niewyrażalne w inny sposób. Wybór notacji, której użycie powoduje, że przekazywana informacja jest jasna tylko dla osób mający doświadczenie związane z dziedziną problemu, nie jest właściwym podejściem. Model musi być zrozumiały także dla nowicjuszy, którzy nie znają naszego obszaru zastosowań. Ludzie mają skłonność do wypowiadania się w języku naturalnym w sposób nieprecyzyjny i niejednoznaczny. W modelowaniu obiektowym często nie troszczymy się o szczegóły, jednocześnie jednak dbamy o dokładność. Język naturalny nie nadaje się do opisu ograniczeń. Doświadczenia z notacją formalną lub matematyczną prowadzą do następującego wniosku: z jej pomoc można bardzo precyzyjnie i jednoznacznie wyrazić dowolne treści, lecz niewiele osób potrafi tego dokonać. Mimo że notacja matematyczna jest dokładna i jednoznaczna, nie może być użyta jako standard języka ograniczeń. Celem standardu jest powszechność, a nie doskonałość, nie mająca odzwierciedlenia w popularności.

Wymagania wobec OCL Ograniczenie to restrykcja nałożona na jedną lub wiele wartości (części) modelu lub systemu obiektowego. W praktyce wielu ograniczeń nakładanych na obiekty lub klasy nie można, bez poważnych trudności wyrazić za pomocą wizualnej reprezentacji modelu obiektowego. Ograniczenia umożliwiają ekspresję informacji uzupełniających wizualny model lub artefakt. Ograniczenia odnoszą się do elementów modelu lub systemu obiektowego, zastrzegając dla nich pewien zbiór wartości. Aby model lub artefakt był poprawny, ograniczenia muszą być spełnione w określonej chwili. Wymagania wobec języka sformułowano następująco: OCL musi być językiem umożliwiającym wyrażanie dodatkowych informacji w modelach i innych artefaktach używanych w procesie obiektowym tworzenia. Język OCL musi być precyzyjny, jednoznaczny i łatwy do czytania i stosowania dla praktyków technologii obiektowych i ich klientów. Oznacza to, że powinien być zrozumiały dla osób nie będących ani matematykami ani teoretykami informatyki. OCL musi pozostać językiem deklaracyjnym. Zapisane w nim wyrażenia nie mogą mieć wpływu na stan systemu. Naruszenie ograniczenia niezależnie od chwili, w której nastąpiło, oznacza niespójność modelu lub systemu i powoduje konieczność podjęcia działania przywracającego właściwy stan. Podejmowane działanie nie jest określane w języku OCL. OCL musi być językiem z kontrolą typów. Wyrażenia OCL będą przeważnie używane do modelowania i specyfikowania.

Przegląd konstrukcji języka OCL Podstawowymi jednostkami, z których składa się wyrażenie OCL, są obiekty i ich właściwości. O każdym obiekcie można powiedzieć, że jest pewnego typu, definiującego wykonywane na nim operacje. Typy podzielono na następujące grupy: Typy predefiniowane, czyli: typy podstawowe (Integer, Real, String i Boolen) typy kolekcyjne (Collection, Set, Bag i Sequence) służące do precyzyjnego opisywania rezultatu nawigacji po powiązaniach w modelu klas. Typy modelowe, definiowane przez użytkownika. Wszystkie klasy, interfejsy lub typy z modelu UML stają się samoczynnie typami modelowymi w języku OCL. Odnosi się to także do typów wyliczeniowych, używanych przy definiowaniu atrybutów. Typami modelowymi są klasy Klient i ProgramLojalnegoKlienta zdefiniowane w naszym przykładowym modelu UML.

Typy wartości i typy obiektowe W języku OCL mianem typów określa się zarówno typy wartości (ang. value type), jak i typy obiektowe (ang. object type). Egzemplarz typu wrtości ma stałą wartość. Przykładowo „jedynka” typu Integer nigdy nie stanie się „dwójką”. Inaczej jest z typami obiektowymi, definiują one bowiem egzemplarze, które mogą zmieniać wartości. Dlatego, nawet gdy obiekt typu modelowego Osoba zmieni wartość atrybutu imię, pozostanie tym samym bytem. M. Fowler nazywa typy obiektowe obiektami referencyjnymi (reference object), a typy wartości – obiektami-wartościami (value object). Inną ważną cechą typów wartości jest fakt, że ich wartości są same w sobie zarówno obiektami, jak i ich nazwami. Dwa obiekty typu wartości, mające tę samą wartość, są z definicji tym samym egzemplarzem, w przeciwieństwie do obiektów typu obiektowego, które mogą mieć zarówno takie same, jak i różne wartości, pozostając odrębnymi egzemplarzami. Identyczne są tylko wówczas, gdy mają wspólną tożsamość. W języku OCL predefiniowane typy podstawowe i kolekcyjne należą do typów wartości, natomiast wprowadzane przez użytkownika typy modelowe mogą być zarówno typami wartości, jak i typami obiektowymi.

Wyrażenia OCL a ograniczenia OCL Wyrażenie OCL jest poprawne wtedy i tylko wtedy, gdy jest napisane zgodnie z regułami języka OCL. Wszystkie wyrażenia OCL są typu predefiniowanego lub modelowego (klasy albo typu zdefiniowanego w modelu UML). Każde zwraca wynik, czyli rezultat wyznaczenia jego wartości. Przykładowo, rezultatem wyrażenia 1 + 3 jest 4. Jego typem jest natomiast Integer, ponieważ typ wyrażenia to także typ wyniku. Ograniczenie jest restrykcją nałożono na jedną lub więcej wartości (części) modelu lub systemu obiektowego, przedstawiono za pomocą wyrażeń logicznych, przyjmujących wartość true, gdy restrykcja jest spełniona. Ograniczenie jest zatem wyrażeniem typu predefiniowanego Boolean. Istnieją jednak wyrażenia OCL innego typu. Przykładem niech będzie 1 + 3 – poprawne wyrażenie OCL typu Integer. Na podstawie tych rozważań można zapisać następującą definicję ograniczenia: Ograniczenie OCL jest poprawnym wyrażeniem OCL typu Boolean Wartością wynikową ograniczenia jest albo true, albo false.

Kontekst warunków początkowych i warunków końcowych Tylko operacja lub metoda może być kontekstem warunków początkowego i końcowego, a jej parametry mogą występować w wyrażeniach ustanawiających te warunki. Operację kontekstową zapisuje się w osobnym wierszu, poprzedzając ją nazwą odpowiedniego interfejsu, typu lub klasy (podkreślony krój pisma). Pełna sygnatura operacji powinna zawierać typ wyniku oraz listę parametrów i ich typów. Pod kontekstem, w wierszach oznaczonych etykietami pre: i post: zapisuje się warunki początkowe i końcowe. Typ1::operacja (argument :Typ2) : TypWyniku Pre : argument.atrybut = true Post: result = argument.atrybut xor self.atrybut2 Tylko napisy widoczne po etykietach pre: i post: są wyrażeniami OCL. Klasę, interfejs lub typ, do którego należy specyfikowana operacja lub metoda, nazywamy typem kontekstowym. Słowo kluczowe self wskazuje obiekt, na którym operacja będzie wykonywana.

Słowo kluczowe self Niekiedy zachodzi potrzeba bezpośredniego odwołania się do obiektu kontekstowego. Wskazuje nań słowo kluczowe self. Sposób zapisu wyrażeń może być następujący: Klient self.nazwisko = ‘Kowalski’ Przykład bezpośredniego odwołania z użyciem self Członkowstwo Klient.karta.członkowstwo.includes(self) Słowo self zawsze odnosi się do obiektu typu, który jest kontekstem ograniczenia OCL. Jeśli kontekstem jest na przykład klasa Klient, to self wskazuje jej egzemplarz. Dlatego self można traktować jak obiekt, od którego rozpoczyna się wyrażenie. Zazwyczaj nie ma potrzeby zapisywania go, ponieważ kontekst jest oczywisty: nazwisko = ‘Kowalski’

Typy podstawowe Typy podstawowe nie są skomplikowane. Różnią się nieco od swoich odpowiedników znanych z języków programowania. Uwaga: chociaż wyrażenia OCL są związane z modelem i jego diagramami, to typy podstawowe wartości są niezależne od jakichkolwiek diagramów i mogą wystąpić w dowolnym wyrażeniu. Ilustracją tego jest napis 1 +3, będący poprawnym wyrażeniem OCL, albo faza true = not false – ograniczenie OCL niezależnie od jakiegokolwiek modelu, lecz zgodne z regułami języka.

Typ Boolean Wyrażenie logiczne typu Boolean może przyjmować tylko jedną z dwu wartości: true lub false. Działania na typie Boolean, zestawiono w tabeli. Jedyną nietypową operacją standardową jest implikacja (implies). Może ona występować w językach specyfikacji lub w środowiskach teoretycznych, rzadko pojawia się w językach programowania. Zwraca ona wartość true, gdy oba argumenty występujące w wyrażeniu przyjmują wartość true lub gdy pierwszy argument ma wartość false. Jako przykład rozważymy klasę Usługi systemu LK, której model przedstawia rysunek. Zapisane poniżej wyrażenie przyjmuje wartość true tylko wówczas, gdy o każdej usłudze można powiedzieć, że jeśli klient zużywa na nią punkty, to nie może otrzymać za nią żadnych dodatkowych punktów. Innymi słowy, klient nie otrzyma punktów za zakup usługi, dokonany za zebrane punkty.

Operacje standardowe dla typu Boolean Notacja Typ wyniku Alternatywa Koniunkcja Alternatywa wykluczająca Negacja Równy Różny Implikacja If then else a or b a and b a xor b not a a = b a <> b a implies b if a then b else b’ endif Boolean Typ b i b’

Przykładowa hierarchia klas Dla przykładowej hierarchii klas przedstawionej na rysunku proste ograniczenie wyrażone w języku naturalnym zdaniem „wykładowcą może być wyłącznie osoba pełnoletnia” można zapisać w OCL wyrażeniem postaci: Context Wykładowca inv: Self.wiek() > 18

W przykładzie zastosowano dostępne w języku OCL operacje na kolekcjach W przykładzie zastosowano dostępne w języku OCL operacje na kolekcjach. Niech dane będzie ograniczenie: „na zajęcia nie może zapisać się więcej studentów niż maksymalna liczba studentów określona dla poszczególnych zajęć”. Jego specyfikację w języku OCL można przedstawić następująco: Context Zajęcia inv: MaxIloscStudentow Zajecia.allInstances -> forall ( z | z.zapisany_na -> notEmpty implies z.zapisany_na -> size <= z.iloscMiejsc)

Przykład systemu System „Lojalny klient” pomaga przedsiębiorstwom nagradzać ich wiernych klientów. Za dokonane zakupy konsumenci otrzymują najczęściej punkty lub darmowe przeloty na ustaloną odległość, rzadziej rabaty, możliwość wynajmu wygodniejszego samochodu w niższej cenie, dodatkowe lub wyższej jakości usługi linii lotniczych itp. Wszelkie usługi, jakich dana firma chce udzielić stałym klientom, mogą być oferowane w programie lojalnego klienta.

Przykład Główna klasa modelu nosi miano ProgramuLojalnegoKlienta. Jeśli system zarządza jednym programem, to zawiera tylko jeden obiekt tej klasy. Przedsiębiorstwo proponujące klientom uczestnictwo w danym programie to PartnerProgramu. Jeśli wiele firm uczestniczy w danym programie, to klienci czerpią korzyści z usług dostarczanych przez każdą z nich. Konsument staje się uczestnikiem programu, gdy wypełni formularz zgłoszeniowy i otrzyma kartę członkowstwa, opisaną w klasie KartaKlienta. W systemie LK taką osobę reprezentuje obiekt klasy Klient. Ponieważ nie sprawdza się, kto korzysta z karty, mogą jej używać krewni członka programu lub pracownicy jednej firmy. Większość programów lojalnego klienta polega na zdobywaniu punktów. Każdy PartnerProgramu udziela pewnej ich liczby za określony zakup. Następnie są one deponowane na Koncie uczestnika programu, przypisanym każdemu Członkowstwu. Zebrane punkty można zamienić na określone usługi, oferowane przez któregoś z partnerów programu. Na koncie dokonuje się różnych transakcji. Załóżmy, że LK zarządza program lojalnego klienta o nazwie „Srebrni i Złoci”. Uczestniczą w nim czterej partnerzy: sieć domów handlowych, sieć stacji benzynowych, wypożyczalnia samochodów i linie lotnicze.

Przykład W domu handlowym można nabyć pewne towary wyłącznie w zamian za zebrane punkty. Konsument otrzymuje ponadto 5 punktów za każdy zakup na kwotę przekraczającą 25 zł. Na stacjach benzynowych kupującemu udziela się 5% rabatu. W wypożyczalni samochodów klient otrzymuje 20 punktów za każde wydane 100 zł. Podróżny korzystający z linii lotniczych zyskuje jeden punkt za każde piętnaście mil wykupionego lotu. Zebrane punkty można zamienić na darmowy przelot.

Przykład W omawianym przykładzie pojawiają się dwa rodzaje transakcji. Pierwsza, zwiększająca liczbę punktów klienta, jest reprezentowana w modelu przez podklasę Zdobywanie klasy Transakcja (rys). Druga polega na „wydawaniu” zebranych punktów – reprezentuje ją podklasa Zużywanie klasy Transakcja. Uwaga: dokonanie zakupów na jednej ze stacji benzynowych w żaden sposób nie wpływa na stan Konta, ponieważ nie dotyczy zbierania ani zużywania punktów. Aktywni uczestnicy programu „Srebrni i Złoci” uzyskują wyższy poziom usług – otrzymują złotą kartę. Jej posiadacze mają dostęp do rozszerzonej oferty, chociaż nadal mogą korzystać z usług przeznaczonych dla zwykłych uczestników: Co miesiąc w sieci domów handlowych jest oferowany darmowy produkt. Klienci nie płacą zań nawet zebranymi punktami. Jego średnia wartość wynosi 25 zł. W sieci stacji benzynowych otrzymają 10% zniżki. W wypożyczalni samochodów większy pojazd zostanie im zaproponowany za cenę zwykłego. Podróżni korzystający z linii lotniczych otrzymają przelot klasą pierwszą za cenę biletów w klasie ekonomicznej.

Przykład Złota karta przysługuje klientowi, który spełnia przynajmniej jeden z poniższych warunków: Przez trzy kolejne lata uczestnictwa w programie jego roczny obrót wyniósł około 5000 zł. W ciągu jednego roku uczestnictwa jego obrót wyniósł 15000 zł (dotyczy to łącznego obrotu u wszystkich partnerów programu). By zarządzać różnymi poziomami usług, do modelu wprowadzono klasę PoziomUsług, zdefiniowaną w programie lojalnego klienta i używaną przez każde Członkowstwo. Do obowiązków firmy LK należy reklamowanie programu i jego warunków oraz zarządzanie danymi i kontami klientów. Aby było to możliwe, partnerzy programu muszą informować LK o wszystkich transakcjach, dokonanych przy użyciu kart. Co roku LK wysyła wszystkim klientom nowe karty, pamiętając by we właściwej chwili zamienić określone karty na złote. Pracownicy LK czynią to, przesyłając klientowi nową kartę wraz z informacją o rozszerzonej ofercie i unieważnieniu starej karty. Aby wystąpić z programu, klient musi wysłać do przedsiębiorstwa LK formularz informujący o rezygnacji. Następuje wówczas unieważnienie jego karty i wyzerowanie konta. Zaprzestanie na dłuższy czas (rok) korzystania z przywilejów członkowstwa również jest traktowane jak rezygnacja.

Nakładanie niezmienników na atrybuty Bez trudu można nałożyć ograniczenia na jeden lub więcej atrybutów. Aby to uczynić trzeba najpierw wskazać klasę dla której chce się ustalić niezmiennik. Wybrana klasa nosi miano kontekstu niezmiennika (invariant context). Następnie buduje się wyrażenie boolowskie definiujące ograniczenie. Mogą w nim wystąpić dowolne atrybuty kontekstu (kontekst ozn. podkreśleniem). W systemie LK każdy uczestnik programu lojalnego klienta musi być pełnoletni. W języku modelu ozn. to, że wartość atrybutu wiek każdego obiektu klasy Klient musi być większa lub równa 18. Można to wyrazić następująco: Klient Wiek >= 18 Gdy typem atrybutu nie jest typ standardowy (np. Boolen lub Integer), lecz klasa, można używać jej operacji w wyrażeniu definiującym niezmiennik. Nazwę operacji wraz parametrami pisane są po nazwie atrybutu, oddzielone kropką. Napisany poniżej prosty, lecz użyteczny niezmiennik dla klasy KartaKlienta, specyfikuje wymóg, aby data w atrybucie ważnaOd była wcześniejsza od pamiętanej w atrybucie ważna Do: KartaKlienta WażnaOd..przed(ważnaDo) Użyto tutaj operacji przed klasy Data, zwracającej wartość boolowską. Sprawdza ona, czy podana w parametrze data poprzedza datę reprezentowaną przez obiekt wywołujący operację.

Nakładanie niezmienników na powiązane klasy Ograniczenia można nakładać nie tylko na obiekty jednej klasy, lecz także na atrybuty obiektów powiązanych klas. Do obiektu na drugim końcu powiązania odwołujemy się przez nazwę roli (rolename). Jeśli nie jest ona określona, to w tym samym celu można stosować nazwę klasy, pisaną małą literą. W systemie LK warto zaznaczyć, że karta i klient, związani z określonym członkowstwem, są również powiązani między sobą. Innymi słowy, karta wystawiona jako dowód danego członkostwa jest własnością klienta, wymienionego w członkostwie. W języku OCL można zapisać to następująco: Członkowstwo karta.klient = klient To, co napisano, znaczy dosłownie, że klient połączony z obiektem klasy KartaKlienta w członkostwie to ten sam obiekt, który jest połączony z członkostwem. W podobny sposób można nakładać ograniczenia powiązanych klas: KartaKlienta wydrukowaneNazwisko = klient.tytuł.concat(klient.nazwisko) Powyższy niezmiennik ozn., że wartość atrybutu wydrukowaneNazwisko każdego egzemplarza klasy KartaKlienta musi być równa konkatenacji atrybutów tytuł i nazwisko powiązanego egzemplarza klasy Klient.

Postępowanie z kolekcjami obiektów Liczność powiązania jest często większa niż 1, co ozn., że obiekt odnosi się do zestawu obiektów powiązanej klasy. W języku OCL zdefiniowano operację na kolekcjach (collection operations), służące do manipulacji takimi zestawami. Gdy rezultatem odniesienia w ograniczeniu jest zestaw obiektów, można użyć jednej z operacji na kolekcjach, rysując wskaźnik (strzałkę) między nazwą roli (lub nazwą klasy pisaną małą literą) a nazwą operacji. W programie „Srebrni i Złoci” są dwa poziomy usług. Oto odpowiedni niezmiennik: ProgramLojalnegoKlienta poziomUsług ->size = 2 Inny niezmiennik dla modelu LK brzmi: liczba ważnych kart danej osoby musi być równa liczbie programów lojalnego klienta, w których ta osoba uczestniczy. To ograniczenie można wyrazić za pomocą operacji na kolekcji o nazwie selekt, której parametrem jest wyrażenie OCL. Efektem wykonania operacji select na danym zbiorze jest podzbiór, którego każdy element spełnia to wyrażenie. W poniższym przykładzie jej rezultatem jest zestaw tych KartKlienta, których atrybut ważność ma wartość true. Klient Program->size = karty_>select(ważność = true)->size

Postępowanie z kolekcjami obiektów - cd Warto także zaznaczyć, że jeśli żadna z usług, oferowanych w danym programie lojalnego klienta, nie ma wpływu na stan konta, egzemplarze klasy Konto są bezużyteczne i nie mają racji bytu. Aby zapisać powyższy wymóg, zastosujemy operację forAll na zbiorze wszystkich usług. Podobnie jak select, operacja forAll ma parametr w postaci wyrażenia OCL. Jej wynikiem jest wartość logiczna: true – jeśli wszystkie elementy kolekcji spełniają dane wyrażenie, false – w przeciwnym przypadku. Niezmiennik zapisany niżej oznacza, że jeśli określony ProgramLojalnegoKlienta nie umożliwia zdobywania i zużywania punktów, to jego członkowie nie mają Kont. Innymi słowy, kolekcja Kont powiązana z Członkostwami musi być pusta. ProgramLojalnegoKlienta partnerzy.dostarczaneUsługi -> forAll(zyskWPunktach = 0 and kosztWPunktach = 0) Implies członkostwo.konto -> isEmpty W powyższym przykładzie pojawiły się po raz pierwszy dwie operacje logiczne: and i implies. Operacje and interpretuje się jak zwykłą koniunkcję wartości logicznych, implies natomiast zwraca wartość true, gdy obie strony wyrażenia są prawdziwe lub lewa strona jest fałszywa.

Postępowanie z kolekcjami obiektów Operacje na kolekcjach: notEmpty – zwraca wartość true, jeśli zbiór zawiera przynajmniej jeden element includes(obiekt) – daje w wyniku true, jeśli obiekt należy do zbioru union(zbiór obiektów) – zwraca zbiór, zawierający wszystkie elementy obu zbiorów Intersection(zbiór obiektów) – zwraca zbiór tych obiektów, które należą jednocześnie do obu zbiorów

Zbiory, wielozbiory, ciągi Działając na kolekcjach obiektów, należy widzieć różnice między pojęciami: zbiór (set), wielozbiór (bag) i ciąg (sequence). W zbiorze każdy element może występować tylko raz. Wielozbiór może zawierać dany element wielokrotnie. Ciąg jest natomiast uporządkowanym wielozbiorem. Aby zrozumieć, dlaczego te różnice są istotne, spójrzmy na atrybut liczbaKlientów klasy PartnerProgramu. Chcemy aby atrybut ten przechowywał liczbę uczestników wszystkich programów lojalnego klienta danej firmy partnerskiej. W języku OCL można to zapisać następująco: PartnerProgramu liczbaKlientów = programLojalnegoKlienta.klient ->size Ograniczenie to nie wyraża naszego zamysłu, ponieważ jedna osoba może uczestniczyć w wielu programach. Innymi słowy, obiekt klasy Klient może wielokrotnie wystąpić w kolekcji programLojalnegoKlienta.klient, jest ona bowiem wielozbiorem, a nie zbiorem. W powyższym wyrażeniu niektórych klientów policzyliśmy kilka razy, co nie było naszym zamierzeniem.

Zbiory, wielozbiory, ciągi Z reguły języka OCL wynika, że rezultatem nawigacji po więcej niż jednym powiązaniu o liczebności większej niż 1 jest wielozbiór. Jeśli natomiast poruszamy się tylko po powiązaniach o liczebności mniejszej lub równej 1, to otrzymamy zbiór. Zdefiniowano oczywiście standardowe operacje zamieniające zbiór w wielozbiór lub ciąg, przekształcające wielozbiór w zbiór lub ciąg oraz sprowadzające ciąg do zbioru lub wielozbioru. Za pomocą jednej z takich operacji możemy poprawić poprzedni niezmiennik: PartnerProgramu liczbaKlientów = programLojalnegoKlienta.klient ->asSet->size Gdy poruszamy się po powiązaniu oznaczonym {ordered} otrzymujemy ciąg. Zdefiniowano kilka standardowych operacji dotyczących porządku ciągu, m.in.: first, last i at(indeks). W modelu LK jedyne powiązanie oznaczone {ordered} znajduje się między klasami ProgramLojalnegoKlienta i PoziomUsług. W kontekście ProgramuLojalnegoKlienta, wynikiem wyrażenia poziomUsług jest zatem ciąg. Możemy zażądać, aby pierwszy element tego ciągu nazywał się Srebrny. ProgramLojalnegoKlienta poziomUsług ->first.nazwa = „Srebrny”

Dziedziczenie Dzięki mechanizmowi dziedziczenia, patrząc z punktu widzenia obiektu korzystającego z egzemplarza nadklasy, nie musimy nic wiedzieć o podklasach. Niekiedy jednak zachodzi potrzeba odwołania się do podklasy. Przyjmijmy założenie, że w systemie LK każdy partner programu chce rozdać klientom najwyżej 10000 punktów. Poniższe wyrażenie ogranicza sumę punktów, którymi obracano we wszystkich transakcjach danego partnera. Nie spełnia ono naszych oczekiwań, ponieważ nie rozróżnia się w nim transakcji Zdobywania od Zużywania. ProgramLojalnegoKlienta partnerzy.dostarczaneUsługi.transakcje.punkty ->sum <10000 Aby określić podklasę, do której należą elementy kolekcji transakcji, użyjemy standardowej operacji oclType. Następnie, aby dotrzeć do zestawu wszystkich obiektów tej podklasy, skorzystamy z operacji select. Teraz z utworzonej kolekcji wybieramy zbiór „wydanych” punktów, stosując operację collect. Na koniec porównamy sumę tych punktów z uzgodnionym limitem. partnerzy.dostarczaneUsługi.transakcje -> select(oclType = Zużywanie)->collect(punkty)-> sum < 10000

Stosowanie wyliczeń W modelu klas UML definiuje się nieraz typ wyliczeniowy dla atrybutów. W wyrażeniach OCL dopuszczalne wartości takiego atrybutu poprzedza się symbolem #. Jako przykład weźmy klasę KartaKlienta, której atrybut kolor może przyjmować dwie wartości: srebrna lub złota. Poniższy niezmiennik oznacza, że kolor karty musi odpowiadać poziomowi usług, określonemu dla danego członkowstwa. Członkostwo obecnyPoziom.nazwa = „Srebrny” implies karta.kolor = #srebrna and obecnyPoziom.nazwa = „Złoty” implies karta.kolor = #złota

Pisanie warunków początkowych i warunków końcowych Język OCL sprawdza się w sytuacjach, gdy zachodzi potrzeba precyzyjnego definiowania wymagań. Podległą pewnym restrykcjom operację wskazujemy, rozszerzając kontekst o jej nazwę. Ograniczenie dotyczy wówczas wyłącznie definiowanej operacji, aczkolwiek jej składowe mogą zawierać dowolne wiązania i atrybuty, osiągalne z ustalonego kontekstu. System LK zawiera klasę Konto z opcją czyPuste. Zwraca ona wartość true, gdy na koncie nie ma punktów. Ściśle mówiąc, jej wynikiem jest wartość logiczna wyrażenia punkty = 0. w poniższym przykładzie oznaczamy tę wartość słowem kluczowym result. Konto::czyPuste() pre : -- brak post: result = (punkty = 0) Powyższa operacja nie ma warunku początkowego; uwypuklono ten fakt komentarzem „brak” zapisanym w miejscu, gdzie ten warunek powinien się znaleźć. Umieszczenie warunku początkowego, nawet jeśli jest on pusty nie jest konieczne. To raczej sprawa stylu – w zasadzie można go pominąć. Gdyby jednak przytoczony przykład był częścią wykazu operacji, a tylko operacja czyPuste byłaby pozbawiona warunku początkowego, jego brak mógłby zostać błędnie zinterpretowany jako niedopatrzenie.

W którym miejscu rozpoczynać pisanie niezmienników Klasa na którą nakładamy ograniczenie, jest nazwana kontekstem niezmiennika. Można określać ograniczenia dla dowolnych obiektów z nią połączonych: Klient wiek > = 18 Nic nie stoi na przeszkodzie, aby powyższy niezmiennik umieścić w kontekście klasy ProgramLojalnegoKlienta: ProgramLojalnegoKlienta klient ->forAll(wiek >= 18) Choć niezmiennik określony w kontekście klasy ProgramLojalnegoKlienta jest poprawnym ograniczeniem, w kontekście klasy Klient łatwiej go odczytać. Do dobrego zwyczaju należy pisanie możliwie najprostszego niezmiennika.

Naruszone ograniczenia Wyrażenia OCL nie niosą informacji, co się dzieje, gdy ograniczenie zostanie naruszone. Ograniczenia jako część specyfikacji poprawnie zrealizowanego systemu, zawsze będzie spełnione, naruszenie któregokolwiek ograniczenia pociąga za sobą nieprawidłowe działanie systemu. W praktyce jednak trudno jest bezbłędnie zrealizować system, warto więc sprawdzać ograniczenia w trakcie działania. Oczywiście w wypadku wykrycia naruszonego ograniczenia trzeba podjąć pewne działania, na przykład wydrukować ostrzeżenia lub zakończenie pracy systemu. Taka technika nosi nazwę oprogramowania chronionego (defensive programming). Uwaga: ograniczenia OCL służą podczas modelowania i specyfikowania, lecz nie są opisem implementacji. Określają tylko, jakie twierdzenia są prawdziwe w wyidealizowanym, bezbłędnym systemie. Język OCL nie umożliwia wyrażenia działań, które powinny zostać podjęte w wypadku naruszenia ograniczenia w niepoprawnej realizacji systemu.