Modelowanie i implementacja systemów informatycznych 2.0 - Mariusz Trzaska

Kup ebooka

39.99 zł

-
Proszę czekać

Spis treści

Modelowanie i implementacja systemów informatycznych 2.0 (fragment)1 Wprowadzenie (aktualne)Wprowadzenie (z pierwszego wydania)2 Analiza2.1 Wymagania klienta2.2 Wymagania dla biblioteki2.3 Przypadki użycia2.3.1 Ogólny diagram przypadków użycia dla Biblioteki2.3.2 Szczegółowy diagram przypadków użycia2.4 Diagram klas2.4.1 Obiekt2.4.2 Klasa2.4.3 Metody2.4.4 Asocjacje2.4.5 Dziedziczenie2.4.6 Ograniczenia2.4.7 Ograniczenie {subset}2.4.8 Ograniczenie {ordered}2.4.9 Ograniczenie {bag} oraz {history}2.4.10 Ograniczenie {xor}2.4.11 Diagram klas dla biblioteki2.5 Diagram aktywności2.6 Diagram stanów3 Projektowanie3.1 Uwagi o programowaniu3.1.1 Język w programowaniu3.1.2 Nazewnictwo3.1.3 Samodokumentujący się kod3.1.4 Struktura kodu3.2 Klasy3.2.1 Obiekt3.2.2 Klasa3.2.3 Ekstensja klasy3.3 Atrybuty3.3.1 Atrybuty proste3.3.2 Atrybuty złożone3.3.3 Atrybuty wymagane oraz opcjonalne3.3.4 Atrybuty pojedyncze3.3.5 Atrybuty powtarzalne3.3.6 Atrybuty obiektu3.3.7 Atrybuty klasowe3.3.8 Atrybuty pochodne (wyliczalne)3.4 Metody3.4.1 Metoda obiektu3.4.2 Metoda klasowa3.4.3 Przeciążenie metody3.4.4 Przesłonięcie metody3.5 Trwałość ekstensji3.5.1 Ręczna implementacja trwałości danych3.5.2 Implementacja trwałości danych w oparciu o serializację3.5.3 Inne sposoby uzyskiwania trwałości danych3.6 Klasa ObjectPlus3.7 Asocjacje3.7.1 Implementacja asocjacji za pomocą identyfikatorów3.7.2 Implementacja asocjacji za pomocą natywnych referencji3.7.3 Implementacja różnych rodzajów asocjacji3.7.4 Implementacja asocjacji skierowanej3.7.5 Implementacja asocjacji rekurencyjnej3.7.6 Implementacja asocjacji z atrybutem3.7.7 Implementacja asocjacji kwalifikowanej3.7.8 Implementacja asocjacji n-arnej3.7.9 Implementacja agregacji3.7.10 Implementacja kompozycji3.8 Klasa ObjectPlusPlus3.9 Dziedziczenie3.10 Dziedziczenie rozłączne3.11 Polimorficzne wołanie metod3.12 Dziedziczenie typu overlapping3.12.1 Obejście dziedziczenia overlapping za pomocą grupowania3.12.2 Obejście dziedziczenia overlapping za pomocą agregacji lub kompozycji3.12.3 Polimorfizm w dziedziczeniu overlapping3.13 Dziedziczenie kompletne oraz niekompletne3.14 Dziedziczenie wielokrotne (wielodziedziczenie)3.15 Dziedziczenie wieloaspektowe3.16 Dziedziczenie dynamiczne3.17 Dziedziczenie a ekstensja klasy3.18 Podsumowanie implementacji dziedziczenia3.19 Ograniczenia i inne konstrukcje3.19.1 Implementacja ograniczeń dotyczących atrybutów3.19.2 Implementacja ograniczenia {subset}3.19.3 Implementacja ograniczenia {ordered}3.19.4 Implementacja ograniczenia {bag} oraz {history}3.19.5 Implementacja ograniczenia {XOR}3.19.6 Implementacja innych ograniczeń4 Model relacyjny4.1 Mapowanie klas4.2 Mapowanie asocjacji4.3 Mapowanie dziedziczenia4.4 Relacyjne bazy danych w językach obiektowych4.5 Wykorzystanie JDBC4.6 Mapery obiektowo-relacyjne (Hibernate)4.6.1 Przygotowanie Hibernate4.6.2 Klasy i atrybuty w Hibernate4.6.3 Praca z Hibernate4.6.4 Asocjacje w Hibernate4.6.5 Dziedziczenie w Hibernate4.6.6 Podsumowanie mapera Hibernate5 Projekt dla biblioteki5.1 Projektowy diagram klas dla biblioteki5.1.1 Hierarchia dziedziczenia klas Klient, Osoba i Postać5.1.2 Hierarchia dziedziczenia klasy Publikacja5.1.3 Informacje o wypożyczeniach5.1.4 Postacie w publikacjach5.1.5 Raporty w bibliotece5.1.6 Kompletny projektowy diagram klas dla biblioteki5.2 Projekt działania systemu dla biblioteki5.3 Projekt interfejsu użytkownika6 Użyteczność graficznych interfejsów użytkownika6.1 Co to jest użyteczność?6.2 Kształtowanie użyteczności6.3 Testowanie użyteczności6.4 Użyteczność niestety kosztuje6.5 Zalecenia dotyczące Graficznego Interfejsu Użytkownika6.5.1 Wymagania dotyczące funkcjonalności6.5.2 Wymagania związane z wykorzystywaną platformą6.5.3 Wymagania dotyczące okien6.5.4 Wymagania dotyczące zarządzania oknami dialogowymi6.5.5 Wymagania dotyczące kontrolek6.5.6 Wymagania dotyczące list6.5.7 Wymagania dotyczące podpisów6.5.8 Wymagania dotyczące pracy z klawiaturą6.6 Jakość interfejsu graficznego7 Implementacja7.1 Wprowadzenie do implementacji7.1.1 Zintegrowane środowisko programistyczne (IDE)7.1.2 Wykorzystanie narzędzi CASE7.1.3 Użyteczne biblioteki pomocnicze7.2 Zarządzanie danymi7.3 Framework Spring7.3.1 Wykorzystanie Spring Data REST7.3.2 Użycie kontrolerów Spring7.3.3 Zastosowanie Spring MVC z Thymeleaf7.3.4 Prosty widok Thymeleaf7.3.5 Przykładowa aplikacja Thymeleaf7.3.6 Podsumowanie frameworku Spring8 Testowanie i pielęgnacja oprogramowania8.1 Testowanie oprogramowania8.1.1 Testy białej i czarnej skrzynki8.1.2 Testy jednostkowe8.1.3 Testy integracyjne8.1.4 Testy End-to-end (E2E)8.1.5 Inne rodzaje testów8.1.6 Debugger8.2 Pielęgnacja oprogramowania9 Uwagi końcoweBibliografiaSpis listingówSpis rysunkówSpis uwagO autorze Title Page Cover

Modelowanie i implementacja systemów informatycznych 2.0 (fragment)

Copyright ? Mariusz Trzaska

Wszelkie prawa zastrzeżone. Zabronione jest nieautoryzowane rozpowszechnianie całości lub części niniejszej publikacji w jakiejkolwiek formie. Powielanie jej treści, zarówno metodą kserograficzną, fotograficzną, jak i kopiowanie na nośnikach filmowych, magnetycznych bądź innych, stanowi naruszenie praw autorskich.

Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli.

Autor dołożył wszelkich starań, aby informacje zawarte w niniejszej książce były pełne i wiarygodne. Nie ponosi jednak odpowiedzialności za sposób ich wykorzystania ani za ewentualne naruszenia praw autorskich czy patentowych. Autor nie odpowiada również za jakiekolwiek szkody, które mogą wyniknąć z zastosowania przedstawionych tu treści.

ISBN: 978-83-976442-0-5

e-mail: mtrzaska@mtrzaska.com

WWW: https://www.mtrzaska.com/

Wydanie 2026-04-07

Spis treści

1 Wprowadzenie (aktualne)2 Analiza2.1 Wymagania klienta2.2 Wymagania dla biblioteki2.3 Przypadki użycia2.3.1 Ogólny diagram przypadków użycia dla Biblioteki2.3.2 Szczegółowy diagram przypadków użycia2.4 Diagram klas2.4.1 Obiekt2.4.2 Klasa2.4.3 Metody2.4.4 Asocjacje2.4.5 Dziedziczenie2.4.6 Ograniczenia2.4.7 Ograniczenie {subset}2.4.8 Ograniczenie {ordered}2.4.9 Ograniczenie {bag} oraz {history}2.4.10 Ograniczenie {xor}2.4.11 Diagram klas dla biblioteki2.5 Diagram aktywności2.6 Diagram stanów3 Projektowanie3.1 Uwagi o programowaniu3.1.1 Język w programowaniu3.1.2 Nazewnictwo3.1.3 Samodokumentujący się kod3.1.4 Struktura kodu3.2 Klasy3.2.1 Obiekt3.2.2 Klasa3.2.3 Ekstensja klasy3.3 Atrybuty3.3.1 Atrybuty proste3.3.2 Atrybuty złożone3.3.3 Atrybuty wymagane oraz opcjonalne3.3.4 Atrybuty pojedyncze3.3.5 Atrybuty powtarzalne3.3.6 Atrybuty obiektu3.3.7 Atrybuty klasowe3.3.8 Atrybuty pochodne (wyliczalne)3.4 Metody3.4.1 Metoda obiektu3.4.2 Metoda klasowa3.4.3 Przeciążenie metody3.4.4 Przesłonięcie metody3.5 Trwałość ekstensji3.5.1 Ręczna implementacja trwałości danych3.5.2 Implementacja trwałości danych w oparciu o serializację3.5.3 Inne sposoby uzyskiwania trwałości danych3.6 Klasa ObjectPlus3.7 Asocjacje3.7.1 Implementacja asocjacji za pomocą identyfikatorów3.7.2 Implementacja asocjacji za pomocą natywnych referencji3.7.3 Implementacja różnych rodzajów asocjacji3.7.4 Implementacja asocjacji skierowanej3.7.5 Implementacja asocjacji rekurencyjnej3.7.6 Implementacja asocjacji z atrybutem3.7.7 Implementacja asocjacji kwalifikowanej3.7.8 Implementacja asocjacji n-arnej3.7.9 Implementacja agregacji3.7.10 Implementacja kompozycji3.8 Klasa ObjectPlusPlus3.9 Dziedziczenie3.10 Dziedziczenie rozłączne3.11 Polimorficzne wołanie metod3.12 Dziedziczenie typu overlapping3.12.1 Obejście dziedziczenia overlapping za pomocą grupowania3.12.2 Obejście dziedziczenia overlapping za pomocą agregacji lub kompozycji3.12.3 Polimorfizm w dziedziczeniu overlapping3.13 Dziedziczenie kompletne oraz niekompletne3.14 Dziedziczenie wielokrotne (wielodziedziczenie)3.15 Dziedziczenie wieloaspektowe3.16 Dziedziczenie dynamiczne3.17 Dziedziczenie a ekstensja klasy3.18 Podsumowanie implementacji dziedziczenia3.19 Ograniczenia i inne konstrukcje3.19.1 Implementacja ograniczeń dotyczących atrybutów3.19.2 Implementacja ograniczenia {subset}3.19.3 Implementacja ograniczenia {ordered}3.19.4 Implementacja ograniczenia {bag} oraz {history}3.19.5 Implementacja ograniczenia {XOR}3.19.6 Implementacja innych ograniczeń4 Model relacyjny4.1 Mapowanie klas4.2 Mapowanie asocjacji4.3 Mapowanie dziedziczenia4.4 Relacyjne bazy danych w językach obiektowych4.5 Wykorzystanie JDBC4.6 Mapery obiektowo-relacyjne (Hibernate)4.6.1 Przygotowanie Hibernate4.6.2 Klasy i atrybuty w Hibernate4.6.3 Praca z Hibernate4.6.4 Asocjacje w Hibernate4.6.5 Dziedziczenie w Hibernate4.6.6 Podsumowanie mapera Hibernate5 Projekt dla biblioteki5.1 Projektowy diagram klas dla biblioteki5.1.1 Hierarchia dziedziczenia klas Klient, Osoba i Postać5.1.2 Hierarchia dziedziczenia klasy Publikacja5.1.3 Informacje o wypożyczeniach5.1.4 Postacie w publikacjach5.1.5 Raporty w bibliotece5.1.6 Kompletny projektowy diagram klas dla biblioteki5.2 Projekt działania systemu dla biblioteki5.3 Projekt interfejsu użytkownika6 Użyteczność graficznych interfejsów użytkownika6.1 Co to jest użyteczność?6.2 Kształtowanie użyteczności6.3 Testowanie użyteczności6.4 Użyteczność niestety kosztuje6.5 Zalecenia dotyczące Graficznego Interfejsu Użytkownika6.5.1 Wymagania dotyczące funkcjonalności6.5.2 Wymagania związane z wykorzystywaną platformą6.5.3 Wymagania dotyczące okien6.5.4 Wymagania dotyczące zarządzania oknami dialogowymi6.5.5 Wymagania dotyczące kontrolek6.5.6 Wymagania dotyczące list6.5.7 Wymagania dotyczące podpisów6.5.8 Wymagania dotyczące pracy z klawiaturą6.6 Jakość interfejsu graficznego7 Implementacja7.1 Wprowadzenie do implementacji7.1.1 Zintegrowane środowisko programistyczne (IDE)7.1.2 Wykorzystanie narzędzi CASE7.1.3 Użyteczne biblioteki pomocnicze7.2 Zarządzanie danymi7.3 Framework Spring7.3.1 Wykorzystanie Spring Data REST7.3.2 Użycie kontrolerów Spring7.3.3 Zastosowanie Spring MVC z Thymeleaf7.3.4 Prosty widok Thymeleaf7.3.5 Przykładowa aplikacja Thymeleaf7.3.6 Podsumowanie frameworku Spring8 Testowanie i pielęgnacja oprogramowania8.1 Testowanie oprogramowania8.1.1 Testy białej i czarnej skrzynki8.1.2 Testy jednostkowe8.1.3 Testy integracyjne8.1.4 Testy End-to-end (E2E)8.1.5 Inne rodzaje testów8.1.6 Debugger8.2 Pielęgnacja oprogramowania9 Uwagi końcowe

1 Wprowadzenie (aktualne)

Długo się zastanawiałem, czy i w jakiej formie tworzyć nową wersję oryginalnego wydania tej książki z roku 2008. Od tego czasu minęło sporo lat, wiele rzeczy się zmieniło, włączając w to rynek książek i skłonność do ich czytania. Zachęcające były pytania potencjalnych czytelników o nową wersję i zgłaszane problemy z dostępnością tej oryginalnej. Rozważałem różne scenariusze, począwszy od napisania wszystkiego od nowa, poprzez modyfikacje istniejącej wersji, a kończąc na wariancie w którym w ogóle nic nie piszę.

Ostatecznie założyłem optymistycznie, że znajdą się jacyś zainteresowani i zaczynam prace. Uznałem, że większość zasadniczych pomysłów z oryginalnej książki wytrzymała próbę czasu i nie ma potrzeby ich wymyślać całkiem od nowa. Oczywiście trzeba było je zaktualizować - choćby o nowsze rozwiązanie programistyczne. W efekcie wyszło całkiem sporo zmian, które sprawiają, że rezultat moich prac jest bardziej zbliżony do nowej publikacji niż tylko kolejnego wydania.

Zmieniłem też tematykę wymagań użytkownika, wokół których jest budowana duża część niniejszej książki (analiza, projektowanie, implementacja). Oryginalnie była to wypożyczalnia wideo, o której niektórzy z potencjalnych czytelników może nigdy nie słyszeli. Zamiast tego mamy również wypożyczalnię, ale książek, czyli bibliotekę. Nie była to tylko prosta zamiana nazw, ponieważ w trakcie prac okazało się, że pewne koncepcje nie mają bezpośrednich odpowiedników.

Oprócz aktualizacji wszystkich diagramów, kodów programów, dodałem również całkiem nowe rozdziały/sekcje, których w ogóle nie było pierwotnie, m.in.:

implementacja różnych rodzajów aplikacji we frameworku Spring (Data REST, kontrolery Spring, MVC z Thymeleaf),

testy integracyjne we frameworku Spring,

testy E2E we frameworku Spring,

konserwacja oprogramowania.

Sporych aktualizacji doczekał się też rozdział o maperze obiektowo-re­la­cy­jnym Hibernate.

O skali zmian może też świadczyć fakt, iż niniejsza edycja książki ma ponad 200 stron więcej niż ta z roku 2008.

Znacząco zaktualizowana i rozbudowana została też bibliografia - teraz ma ponad 100 pozycji. Starałem się znaleźć nowe, interesujące publikacje dotyczące omawianych zagadnień. W niektórych przypadkach (szczególnie tych bardziej teoretycznych jak, np. modelowanie UML, czy dobre praktyki programistyczne) okazało się, że nadal popularne są wydania książek sprzed wielu lat. Z tego powodu niektóre z nich pozostawiłem. Być może są aż tak dobre i/lub w teorii zmiany następują zdecydowanie wolniej, a szybsze są w konkretnych technologiach.

Zastanawiałem się również jak rozwiązać kwestię zwracania się do czytelników oraz feminatywów, np. w nazwach zawodów. Ostatecznie uznałem, że pisanie za każdym razem "programista/programistka", "analityk/analityczka" będzie utrudniało czytanie i z tego zrezygnowałem. Mam nadzieję, że nikt nie poczuje się urażony.

Zmienił się również podział na rozdziały, sekcje itp. Nie zawsze jest idealnym odzwierciedleniem znaczenia poszczególnych koncepcji, ale zbyt dokładne trzymanie się takich reguł prowadziło do zbyt wielu poziomów zagnieżdżeń. Czasami granice są też dość płynne. W efekcie, np. model relacyjny jest poza projektowaniem.

Zastosowałem też pewnie zmiany natury edytorskiej w postaci tzw. uwag. Opisują one różne zagadnienia, które mogą być istotne dla wszystkich zainteresowanych wytwarzaniem systemów informatycznych.

Na koniec tego wprowadzenia, chciałbym prosić o przysyłanie znalezionych błędów (w tym literówek), uwag, komentarzy, pomysłów, sugestii na adres: mtrzaska@mtrzaska.com. Postaram się odpowiedzieć na każdego maila i odnieść się do każdej z uwag, a pomysły w miarę możliwości zrealizować w aktualizacjach do niniejszego wydania.

Wprowadzenie (z pierwszego wydania)

Poniżej znajduje się wprowadzenie z pierwotnej wersji książki z roku 2008. Jako, że w dużej części jest aktualne, to zdecydowałem się je zostawić.

Ponad dziesięć lat temu przeczytałem książkę o programowaniu, która mnie urzekła: "Symfonia C++" napisana przez Jerzego Grębosza [Greb96] . Do dzisiaj nie spotkałem lepiej napisanej książki dotyczącej języków programowania. Niektórzy mogą uważać, że pisanie o takich poważnych i skomplikowanych sprawach jak języki programowania wymaga bardzo naukowego stylu. Pan Grębosz zastosował styl "przyjacielski" - jak sam to określił: "bezpośredni, wręcz kolokwialny". Moim celem jest stworzenie książki podobnej w stylu, ale traktującej o całym procesie wytwarzania oprogramowania, ze szczególnym uwzględnieniem pojęć występujących w obiektowości i ich przełożenia na praktyczną implementację. Czy i w jakim stopniu mi się to udało oceną Czytelnicy.

Książka ta powstała na podstawie mojego doświadczenia nabytego w czasie prowadzenia wykładów, ćwiczeń oraz przy okazji prac w różnego rodzaju projektach, począwszy od badawczych, aż do typowo komercyjnych. Na co dzień pracuję w Polsko-Japońskiej Wyższej Szkole Technik Komputerowych jako adiunkt, więc mam też spore doświadczenie wynikające z prowadzenia zajęć ze studentami. Dzięki temu będę w stanie omówić też typowe błędy popełniane przy tworzeniu oprogramowania.

Odbiorcami tej publikacji mogą być wszyscy zainteresowani wytwarzaniem oprogramowania, obiektowością, programowaniem czy modelowaniem pojęciowym, np. programiści, analitycy czy studenci przedmiotów związanych z programowaniem, inżynierią oprogramowania, bazami danych itp. Zakładam, że Czytelnik ma już jakąś wiedzę na temat programowania oraz modelowania, ale wszędzie, gdzie to tylko możliwe, staram się przedstawiać obszerne wyjaśnienia. Aby oszczędzić Czytelnikowi przewracania kartek oraz ułatwić zrozumienie omawianych zagadnień, w niektórych miejscach powielam wyjaśnienia, ale z uwzględnieniem trochę innego punktu widzenia (lub argumentacji).

Pomysł na książkę był prosty: pokazać cały proces wytwarzania oprogramowania, począwszy od analizy potrzeb klienta, poprzez projektowanie, implementację (programowanie), a kończąc na testowaniu. Szczególnie chciałem się zająć przełożeniem efektów analizy na projektowanie oraz programowanie. W związku z tym, czynności z tej pierwszej fazy są potraktowane nieco skrótowo (nie dotyczy to diagramu klas, który jest omówiony bardzo szczegółowo). Czytelnik, który potrzebuje poszerzyć swoją wiedzę na temat tych zagadnień, powinien sięgnąć po którąś z książek traktujących o modelowaniu, UML itp. (lista proponowanych tytułów znajduje się w ostatnim rozdziale: Bibliografia). Większość przykładów w książce oparta jest na konkretnych wymaganiach biznesowych (wypożyczalnia wideo). Implementacja została wykonana dla języka programowania Java SE 6. Czasami też zamieszczam odnośniki do Microsoft C# czy C++. Na początku książki mamy jakiś biznes do skomputeryzowania (wypożyczalnia wideo), przeprowadzamy jego analizę, robimy projekt, a na koniec częściową implementację w postaci prototypu systemu komputerowego.

Proces analizy potrzeb użytkownika i projektowania oprogramowania oraz zagadnienia z tym związane (szeroko pojmowana obiektowość) są czasami postrzegane (szczególnie przez niektórych studentów) jako zbędny balast teoretyczny. Można spotkać się z opinią, że należy usiąść i zacząć pisać program (programować), a reszta jakoś się ułoży. Nieznajomość tych podstaw, nazwijmy to teoretycznych, lub ich niezrozumienie prowadzi do tego, że programy pisane w obiektowych językach programowania nie są wcale zbyt obiektowe. I tak naprawdę, przez to, że nie korzystają z tych udogodnień, wymagają więcej pracy oraz zawierają więcej błędów. Mam nadzieję, że po przeczytaniu tej książki uwierzysz, drogi Czytelniku, że jak powiedział Kurt Lewin, twórca podstaw współczesnej psychologii społecznej: "nie ma nic praktyczniejszego niż dobra teoria".

To tyle słowem wstępu - dalej już będzie bardziej konkretnie. I jeszcze jedna rzecz: bardzo proszę o przysyłanie uwag, komentarzy, pomysłów, sugestii na adres: mtrzaska@mtrzaska.com.

2 Analiza

Wytwarzanie współczesnego oprogramowania to proces bardzo skomplikowany. Bierze w nim udział cały sztab ludzi, z których każdy ma konkretne zadanie do wykonania. Aby te osoby mogły się wzajemnie porozumieć, muszą mówić wspólnym językiem. W przypadku projektowania systemu informatycznego do tego celu najczęściej używa się notacji UML (Unified Modeling Language) [54], [32], [118]. Umożliwia ona w miarę precyzyjne opisanie wszystkich elementów składających się na projekt nowoczesnego oprogramowania. Więcej informacji na temat samego procesu pozyskiwania wymagań można znaleźć np. w książkach [15], [27], [55].

Istnieje wiele różnych metodyk definiujących proces wytwarzania oprogramowania, zaczynając od klasycznych (zwanych często wodospadowymi (wa­ter­fall)), a kończąc na lekkich (Agile). W większości z nich mamy do czynienia z jakąś wersją faz pokazanych na rysunku 2.1. Nawet jeżeli metodyka jest przyrostowa (iteracyjna), to i tak ma ich jakieś odpowiedniki. Główna różnica polega na tym, że w podejściu przyrostowym fazy te są powtarzane wielokrotnie (w każdej iteracji), a w klasycznym realizujemy je jednorazowo.

Rysunek 2.1: Typowe fazy wytwarzania oprogramowania.

Krótko rzecz biorąc, zadaniem poszczególnych faz jest:

Faza strategiczna - podjęcie decyzji o ewentualnym rozpoczęciu projektu. Wykonawca szacuje, na podstawie wstępnej analizy, czy jest zainteresowany wykonaniem danego systemu informatycznego. Bierze pod uwagę koszty wytworzenia (w tym pracochłonność), proponowaną zapłatę i ewentualnie inne czynniki (np. prestiż).

Analiza - ustalamy, co tak naprawdę jest do zrobienia i zapisujemy to przy pomocy różnego rodzaju dokumentów (w tym diagramów UML). Ta faza raczej abstrahuje od aspektów technologicznych, np. języka programowania.

Projektowanie - decydujemy, w jaki sposób zostanie zrealizowany nasz system. W oparciu o wybraną technologię (w tym język programowania) wykonujemy możliwie dokładny projekt systemu. W idealnej sytuacji tworzymy diagramy opisujące jego każdy aspekt, działanie użytkownika, reakcję aplikacji itp. W praktyce, w zależności od dostępnego czasu oraz skomplikowania systemu, nie zawsze jest to tak szczegółowe.

Implementacja poświęcona jest fizycznemu wytworzeniu aplikacji. Innymi słowy, to właśnie tutaj odbywa się programowanie w oparciu o dokładny (mniej lub bardziej) projekt systemu.

Testowanie - jak sama nazwa wskazuje, testujemy owoce naszej pracy, mając nadzieję, że znajdziemy wszystkie błędy. Ze względu na różne czynniki zwykle to się nie udaje. Ale oczywiście dążymy do tego ideału.

Wdrożenie i pielęgnacja. Ta faza nie zawsze występuje w pełnej postaci. Wdrożenie polega na zainstalowaniu i zintegrowaniu aplikacji z innymi systemami klienta. Z oczywistych względów nie występuje w przypadku, gdy nasz program sprzedajemy w sklepie (wtedy zwykle wykonuje ją sam kupujący). Zadaniem pielęgnacji jest tworzenie poprawek i ewentualnych zmian. Dlatego też ta faza nigdy się nie kończy. A przynajmniej trwa dopóki klient używa naszego systemu.

Z punktu widzenia tej książki najmniej interesujące są dla nas fazy pierwsza (strategiczna) oraz ostatnia (wdrożenie i pielęgnacja). Z tego powodu raczej nie będziemy się nimi zajmować.

2.1 Wymagania klienta

Jak już wspomnieliśmy, książka ta będzie bazowała na wymyślonym przypadku biznesowym. Dzięki temu będziemy w stanie opisać na praktycznych przykładach sytuacje maksymalnie zbliżone do rzeczywistości.

Wśród analityków panuje przekonanie, że klient nie wie, czego chce. Zwykle chce wszystko, najlepiej za darmo i do tego na wczoraj. Po przeprowadzeniu fazy analizy, gdy już ustaliliśmy, czego tak naprawdę mu potrzeba, okazuje się, że to twierdzenie bardzo często jest prawdą. W związku z tym warto stosować się do kilku rad:

Zawsze dokumentuj wszelkie informacje otrzymane od klienta. Nawet jeżeli jesteście świetnymi kumplami (i oby tak pozostało do końca projektu) i rozmowę dotycząca wymagań odbyliście późnym wieczorem przy piwie bezalkoholowym, to następnego dnia należy wysłać mail i poprosić o potwierdzenie wcześniejszych ustaleń. Dzięki temu, gdy klient będzie chciał zmienić zdanie i jedną "drobną" decyzją rozłożyć cały projekt, to mamy dowód, że wcześniej były inne ustalenia.

Staraj się możliwie dokładnie o wszystko wypytywać. Nie wstydź się zadawać pytań i "męczyć" klienta. To jest właśnie twoja praca. Szybko się przekonasz, że z pozoru błahe pytania i wątpliwości mogą sprawić sporo problemów. A co dopiero kwestie, które już na pierwszy rzut oka są skomplikowane...

Przy tworzeniu projektu warto rozważyć zastosowanie jakiegoś narzędzia CASE (patrz też podrozdział 7.1.2). Ułatwi to znacznie wprowadzanie zmian (a te na pewno będą) oraz różne formy publikacji efektów naszej pracy.

Jak łatwo można sobie wyobrazić, proces ustalania wymagań na system nie jest zbyt prosty. Dla potrzeb tej książki załóżmy jednak, że udało nam się go przeprowadzić łatwo i bezboleśnie, a w efekcie otrzymaliśmy "historyjkę" (zamieszczoną w rozdziale 2.2) opisującą biznes naszego klienta. Celowo wybraliśmy bibliotekę 1, ponieważ w zaproponowanym kształcie posiada większość elementów występujących podczas modelowania systemów komputerowych.

2.2 Wymagania dla biblioteki

System ma przechowywać informacje o wszystkich klientach. Klient może być firmą lub osobą. Każdy klient "osobowy" jest opisany przez:

Imię,

Nazwisko,

Adres (ulica, numer domu, ewentualnie numer mieszkania, miasto, kod pocztowy),

Dane kontaktowe (nr telefonu, adres e-mail, opcjonalny adres internetowy).

Dla klienta firmowego przechowujemy następujące informacje:

Nazwa,

Adres (ulica, numer domu, ewentualnie numer mieszkania, miasto, kod pocztowy),,

NIP,

Dane kontaktowe (nr telefonu, adres e-mail, opcjonalny adres internetowy).

W przypadku klientów prywatnych, klientem biblioteki może zostać osoba, która ukończyła 16 lat.

System ma przechowywać informacje o wszystkich wypożyczanych podręcznikach, biografiach, ich autorach, tytułach, wydaniach itp.

Informacja o publikacji dotyczy:

Tytułu,

Roku stworzenia,

Liczby stron,

Autorów (może być ich więcej niż jeden),

Cyklu tematycznego razem z jego nazwą; nie każda publikacja musi być przypisana do cyklu,

Opłaty pobieranej za wypożyczenie egzemplarza (takiej samej dla wszystkich publikacji).

Publikacja powinna zawierać informacje o postaciach w niej występujących razem z ich opcjonalną rolą w konkretnej publikacji, np. postać negatywna, postać pozytywna itd. Postacie mogą być:

Osobami ludzkimi (pamiętamy imię i nazwisko),

jakimiś istotami (przechowujemy informację o ewentualnych cechach szczególnych).

Może istnieć wiele egzemplarzy z tą samą publikacją. Każdy egzemplarz posiada unikalny numer identyfikacyjny oraz datę zakupu.

Egzemplarz jest zawsze wystąpieniem konkretnego wydania publikacji. Przechowujemy następujące informacje:

numer wydania,

wydawca,

język,

uwagi,

numer ISBN.

Publikacje podzielone są według rodzaju na np. biografia, podręcznik, beletrystyka itd. System powinien być dostosowany do przechowywania informacji specyficznych dla poszczególnych rodzajów (niektóre z nich zostaną doprecyzowane w przyszłości):

Biografia - postać, której dotyczy biografia wybrana spośród postaci występujących w danej publikacji.

Podręcznik - informacje o tematyce, dla której jest przeznaczony.

Beletrystyka - informacje o kategoriach.

Innym kryterium podziału publikacji jest odbiorca docelowy: dziecko, młodzież, osoba dorosła, wszyscy. Dla dzieci musimy pamiętać kategorię wiekową (3, 5, 7, 9, 13 lat), a dla dorosłych przyczynę przynależności do tej kategorii wiekowej (są one z góry zdefiniowane, np. przemoc, wulgaryzmy).

Informacja o wypożyczeniu dotyczy daty wypożyczenia oraz opłaty za wypożyczenie.

Do jednego wypożyczenia może być przypisane kilka egzemplarzy, jednak nie więcej niż trzy. Każdy z pobranych egzemplarzy może być oddany w innym terminie.

Jednocześnie można mieć wypożyczonych maks. 5 egzemplarzy.

Egzemplarze wypożycza się standardowo na jeden dzień, płatne z góry. W przypadku przetrzymania egzemplarza opłata za każdy dzień przetrzymania zostaje zwiększona o 10% w stosunku do opłaty standardowej.

Jeśli fakt przetrzymania powtórzy się trzykrotnie, klient traci na zawsze prawo do korzystania z biblioteki.

Jeśli klient oddał uszkodzony egzemplarz, jest zobowiązany do zwrócenia kosztów nowego.

Publikacje przeznaczone wyłącznie dla osób dorosłych może wypożyczyć osoba, która ukończyła 18 lat.

Klient musi mieć możliwość samodzielnego przeglądania informacji o publikacjach oraz stanu swojego konta.

Codziennie opracowuje się raport dzienny o wydarzeniach w bibliotece, tzn. o:

Liczbie nowych wypożyczeń,

Liczbie zwrotów,

Liczbie dzisiaj wypożyczonych egzemplarzy,

Dziennym utargu.

Co jakiś czas opracowuje się raport okresowy (za zadany okres - okresy mogą się nakładać), który zawiera informacje o:

Najczęściej wypożyczanej publikacji,

Najpopularniejszej kategorii,

Najpopularniejszej postaci.

Raporty są uporządkowane chronologicznie.

Jak można się zorientować, powyższe wymagania odpowiadają mniej więcej typowej bibliotece (może oprócz postaci występujących w publikacji). I jak również łatwo się zorientować, nie są całkowicie precyzyjne. Na pewno brakuje tam pewnych informacji, o czym będziesz mógł się przekonać, drogi Czytelniku, w trakcie lektury pozostałych rozdziałów tej książki. Jest to zabieg celowy: po prostu nie chciałem tworzyć osobnej książeczki poświęconej tylko opisowi wymagań na system. W rzeczywistości należy zebrać jak najwięcej informacji. A co gdy jednak o coś zapomnieliśmy zapytać? Czy robimy tak jak uważamy, że będzie dobrze? Oczywiście, w żadnym wypadku nie! Kontaktujemy się z naszym zleceniodawcą i ustalamy szczegóły (np. uwzględniając porady z rozdziału 2.1).

Warto również zwrócić uwagę na to, że wymagania mogą się zmieniać w trakcie realizacji projektu. W takim przypadku należy zastosować odpowiednie procedury zmiany wymagań, które zazwyczaj są opisane w umowie zawartej z klientem.

2.3 Przypadki użycia

Diagramy przypadków użycia są bardzo ważnym elementem analizy. Ich głównym zadaniem jest zdefiniowanie funkcjonalności tworzonego systemu. Pojęcie funkcjonalności określa, co system ma robić (jakie funkcje może realizować). Ich odzwierciedleniem będą odpowiednie komendy w menu, wyświetlające dedykowane elementy GUI (Graphical User Interface - graficznego interfejsu użytkownika). Ewentualnie, w przypadku tzw. Aplikacji konsolowych (pozbawionych graficznego interfejsu użytkownika) będą miały przełożenie na odpowiednie parametry tekstowe, np. "/remove" lub "-add". Jednakże na tym etapie tworzenia oprogramowania nie interesuje nas dokładne mapowanie przypadków użycia (funkcji) na konkretne pozycje menu czy przyciski. Nie zajmujemy się też określaniem, jak te funkcje mają być dokładnie wykonywane - to jest przedmiotem fazy projektowania.

Przeprowadzenie dokładnej analizy jest o tyle istotne, że rzutuje na dalsze prace projektowe. Wyobraźmy sobie sytuację, że na tym etapie zapomniano o uwzględnieniu jakiejś funkcji. Oznacza to, że nie zostanie ona uwzględniona przy projektowaniu, implementacji oraz testowaniu. Po prostu jej nie będzie w tworzonej aplikacji! Naturalnie, zawsze można ją dodać na późniejszym etapie (bo nie będziemy mieli innego wyjścia - klient nie zechce systemu, który nie ma wymaganych funkcji). Należy jednak pamiętać, że tworzony system komputerowy jest trochę jak naczynia połączone. Zmiana elementu w jednym miejscu skutkuje zmianami w innym. W efekcie coś, co dobrze działało, może przestać działać lub, co gorsza, zacząć działać źle (złe działanie jest gorsze od braku działania, ponieważ trudniej jest to zaobserwować; gdy coś nie działa, to widzimy to od razu). Generalnie, w inżynierii oprogramowania uważa się, że im błąd wcześniej popełniony, tym ma gorsze skutki dla całego projektu. Jest to też zgodne ze zdrowym rozsądkiem: niezauważenie konieczności dodania funkcji na etapie analizy jest dużo poważniejsze niż jakiś błąd typowo programistyczny, który, gdy występuje w sposób deterministyczny (wiemy, co trzeba zrobić, aby wystąpił), możemy stosunkowo łatwo naprawić.

W większości przypadków, zamiast rysować diagram przypadków użycia (use case diagram) możemy wypisać poszczególne funkcje w punktach. Pod względem zawartości te dwa podejścia są prawie równoważne. Wydaje się, że główne zalety diagramu to:

możliwość zobaczenia wszystkich funkcji "z lotu ptaka",

łatwość zaobserwowania powiązań pomiędzy przypadkami użycia (czyli funkcjami systemu),

szansa na wychwycenie funkcji, które są wspólne (powtarzają się) i możemy je zrealizować w bardziej ogólny sposób.

Diagramy przypadków użycia są stosunkowo proste. Ich najważniejsze elementy to (zobacz rysunek 2.2):

Rysunek 2.2: Przykładowy diagram przypadków użycia służący jako ilustracja notacji.

Aktor - uosabia użytkownika danego przypadku użycia. Może nim być konkretna osoba lub instytucja. Nazwa aktora jest raczej nazwą roli (np. pracownik), a nie konkretnego użytkownika (np. Kowalski). W przypadku analizy jest to ktoś (lub coś), kto fizycznie obsługuje system. Przykładowo, dla przypadku użycia opisującego sprzedaż w supermarkecie będzie to kasjer/kasjerka, a nie klient robiący zakupy. Natomiast w tym samym supermarkecie klient może być aktorem dla przypadku użycia sprawdzania ceny w skanerze (bo to rzeczywiście klient obsługuje system).

Przypadek użycia - jest po prostu funkcją systemu (np. dodanie towaru). Nazwy powinny być tworzone z punktu widzenia systemu (np. sprzedaż towaru, a nie zakup towaru - bo to jest punkt widzenia klienta).

Przypadki użycia mogą być powiązane ze sobą na dwa sposoby:

<<include>> oznacza, że przypadek użycia A zawsze korzysta (wywołuje, uruchamia) przypadek użycia B,

<<extend>> formalnie oznacza, że przypadek użycia A jest rozszerzany przez przypadek użycia C. Mówiąc po polsku: A czasami używa C. Nie mamy informacji jak często. Jeżeli dodatkowo wyspecyfikowaliśmy punkt rozszerzalności (extension point), to wiemy, kiedy to ewentualne użycie zachodzi. Warto zwrócić uwagę na zwrot strzałek: mówiąc, że A czasami używa C, pokazujemy z C do A (wzięło się to z tego, że C "rozszerza" A).

2.3.1 Ogólny diagram przypadków użycia dla Biblioteki

Diagramy przypadków użycia można rysować na różnym poziomie szczegółowości. Zwykle robi się jeden (lub więcej dla bardzo rozbudowanych systemów) ogólny diagram i uszczegóławia się go na oddzielnych diagramach. Nasuwa się pytanie: jak bardzo należy wchodzić w szczegóły? Nie ma jednej precyzyjnej odpowiedzi. Przeważnie robimy to tak, aby dało się z niego odczytać dość ogólne informacje. Do przedstawiania bardziej szczegółowych i precyzyjnych danych służą inne diagramy (np. aktywności - podrozdział 2.5, stanów - podrozdział 2.6; warto także zajrzeć do książki [25]).

Rysunek 2.3: Diagram przypadków użycia dla biblioteki.

Rysunek 2.3 zawiera przykładowy, ogólny (zawierający wszystkie funkcje) diagram dla naszej biblioteki. Słowo przykładowy nie jest przypadkowe. Inny analityk (np. ty, Czytelniku) mógłby narysować (trochę) inny diagram. Jego kształt oraz zakres pokazanych funkcji zależy od dwóch głównych czynników:

Uwarunkowania biznesowe, czyli co ten system ma robić (jakie ma mieć funkcje). Część z nich staje się jasna dopiero po zadaniu wielu pytań naszemu zleceniodawcy. Niektórzy analitycy je zadadzą, inni nie. Może to sugerować po prostu błędny diagram. Owszem może, ale nie musi - patrz następny punkt (ktoś mógł uznać, że pewne informacje są zbyt szczegółowe).

Sposobu modelowania (postrzegania pewnych zjawisk) konkretnego analityka. Nawet mając te same dane wejściowe (sytuację biznesową), różni analitycy mogą stworzyć różne diagramy. Naturalnie część z nich może być błędna (mniej lub bardziej), ale też może istnieć wiele poprawnych (też mniej lub bardziej) i zarazem różnych rozwiązań. Te różnice są szczególnie widoczne w przypadku bardziej skomplikowanych diagramów (np. klas - patrz rozdział 2.4). Można w takim razie zapytać; jakie są kryteria poprawności - który z nich jest lepszy, a który gorszy? i znowu nie ma jednoznacznej odpowiedzi - po prostu różni ludzie widzą te same sprawy w różny sposób (i jak pewnie nieraz mogłeś się przekonać, drogi Czytelniku, nie dotyczy to tylko modelowania). Na pewno błędnym diagramem będzie taki, który nie przykrywa pewnej funkcjonalności, tworzy nową, o której zleceniodawca nie wspominał (tak - i w tym przypadku nadgorliwość jest wadą) albo po prostu utrudnia jej zrealizowanie.

Poniżej w punktach omówimy niektóre decyzje projektowe podjęte przy okazji tworzenia tego diagramu:

Aktorzy. Jak łatwo się zorientować, ich liczba oraz zakres "obsługiwanych" funkcji nie wynika wprost z wymagań na system (patrz podrozdział 2.2, ). Przykładowo, funkcje systemu związane z dodawaniem informacji o nowych publikacjach oraz egzemplarzach mógłby pełnić dedykowany aktor. W prawdziwym systemie tego typu decyzje są uzależnione od czynników biznesowych (np. wielkości firmy). Na potrzeby tej książki chcieliśmy ograniczyć ich liczbę. Załóżmy, że w toku długich rozmów z klientem doszliśmy do wniosku, że właśnie tacy aktorzy będą obsługiwać nasz system.

Zwróćmy też uwagę na umieszczenie aktorów na diagramie. Zwykle są oni zlokalizowani na krańcach diagramu, aby podkreślić fakt, iż nie są częścią analizowanego systemu; czasami przypadki użycia dodatkowo oddziela się graficznie, otaczając je prostokątem, a aktorzy pozostają na zewnątrz tego prostokąta.

Przypadki "Wyszukanie informacji o postaciach" oraz "Wyszukanie informacji o publikacjach" są wzajemnie powiązane przy pomocy <<extend>>. Sens tego jest taki, że szukając informacji o publikacjach (a właściwie konkretnej publikacji), możemy chcieć zobaczyć postacie, które tam występują. Możliwa jest też sytuacja odwrotna: mając konkretną postać, możemy chcieć zobaczyć "jej" publikacje. Ponieważ możemy to zrobić, ale nie zawsze będziemy korzystali z tej funkcji, zastosowaliśmy <<extend>>, który jak wcześniej powiedzieliśmy, oznacza opcjonalne wykorzystanie.

Przypadek użycia "Wyszukiwanie informacji o egzemplarzach" rozszerza "Wyszukiwanie informacji o publikacjach". Oznacza to, że czasami, wyświetlając informacje o publikacji, chcemy zobaczyć listę egzemplarzy, na których się znajduje.

"Wypożyczenie egzemplarza" oraz "Przyjęcie egzemplarza są przypisane do aktora Pracownik. Jest tak dlatego, że to właśnie pracownik obsługuje system w momencie wypożyczania/zwracania. Gdybyśmy chcieli zrobić system dla biblioteki samoobsługowej, to właściwym aktorem byłby klient.

Grupa przypadków użycia "Zarządzanie informacjami o ...". Bierzemy tu pod uwagę dodawanie, usuwanie i edycję. Oczywiście można dyskutować, czy nie powinniśmy do tego włączyć też wyszukiwania: raczej nie, ponieważ wyszukiwanie jest dostępne dla klienta, a dodawanie/usuwanie pewnie nie powinno.

No i ostatni przypadek użycia, czyli "Generowanie raportów". Jest on przypisany dla Kierownika, a nie Pracownika. Zakładamy, że w trakcie wywiadu nasz zleceniodawca postanowił ograniczyć wgląd do raportów dla zwykłych pracowników. Drugą kwestią dyskusyjną jest to, czy należy wypisywać (pokazywać na diagramie) te wszystkie rodzaje raportów. Ja uznałem, że nie (bo na tym etapie szczegółowości jest ważny dla nas sam fakt tworzenia raportów; i tak nie będziemy w stanie pokazać tutaj konkretnych informacji, które mają się tam znaleźć), ale inny analityk (np. Ty, drogi Czytelniku) mógłby chcieć stworzyć dwa różne przypadki użycia (po jednym dla każdego rodzaju raportu) lub rozszerzyć (<<extend>>) ten, który ja dodałem.

Warto zwrócić też uwagę na nazewnictwo przypadków użycia. Nazwy powinny odpowiadać nazwom funkcjonalności, np. dodanie klienta lub, ewentualnie, dodaj klienta. Raczej unikamy nazw typu dodawanie klienta (to jest dobra nazwa dla stanu na diagramie stanów). A na pewno całkowicie złą nazwą jest: klient (bo co niby miałoby to oznaczać? Niestety takie nazwy też widywałem).

Uważny czytelnik (mam nadzieję, że to jesteś właśnie Ty), mógłby się zdziwić: chwileczkę, ale jak to - Kierownik nie ma dostępu do funkcji zwykłego pracownika czy wręcz klienta (analogicznie z funkcjami pracownika oraz klienta)? Faktycznie trochę dziwne. Zastanówmy się, jakie mamy możliwości, aby to zmienić:

Pierwsza to połączenie wszystkich przypadków użycia klienta z aktorem Pracownik. Analogicznie łączymy wszystkie przypadki pracownika z Kierownikiem. W rezultacie mamy straszną gmatwaninę linii na diagramie (a diagram z definicji powinien być czytelny).

Narysować oddzielny diagram pokazujący zależności pomiędzy aktorami (lub nanieść je na istniejący diagram - tak też można). I taki właśnie diagram jest na rysunku 2.4. Specjalny znaczek (strzałka z trójkątnym grotem) oznacza dziedziczenie. Czyli wszystkie przypadki użycia nad-aktora są dostępne dla pod-aktora, odpowiednio: Klient i Pracownik oraz Pracownik i Kierownik. Rozwiązanie z dziedziczeniem jest czytelniejsze oraz umożliwia łatwe wprowadzanie zmian (modyfikujemy tylko hierarchię dziedziczenia aktorów, co oznacza zmiany dostępnych przypadków użycia dla wszystkich zaangażowanych aktorów).

Rysunek 2.4: Diagram dziedziczenia dla aktorów.

2.3.2 Szczegółowy diagram przypadków użycia

Jak już wcześniej ustaliliśmy, nie ma jednoznacznych reguł, czy i jak organizować różne stopnie szczegółowości dla diagramów przypadków użycia. Każdy z analityków może mieć własny pogląd na tę sprawę. Generalnie zakłada się, że umieszczanie zbyt wielu szczegółów zmniejsza czytelność tych diagramów i dlatego należy tego unikać.

Rysunek 2.5: Przykładowy, uszczegółowiony diagram przypadków użycia.

Rysunek 2.5 zawiera uszczegółowioną wersję jednego z przypadków użycia ("Wypożyczenie egzemplarza") przedstawionych ogólnie na rysunku 2.3. Poniżej zamieszczono jego skrótową analizę:

"Identyfikacja klienta" - zanim będziemy mogli wypożyczyć egzemplarz, musimy wiedzieć, którego klienta ta operacja dotyczy. Warto zwrócić uwagę, że nazwa tego przypadku użycia jest dość ogólna i nie precyzuje sposobu identyfikacji. Naturalnie jest to celowy zabieg. Z punktu widzenia biblioteki nie interesuje nas, jak to się będzie odbywało - czy wykorzystamy jakiś rodzaj podawania danych (np. numer klienta), czy coś bardziej zaawansowanego jak czytnik kart magnetycznych (zakładając, że nasza biblioteka jest aż tak nowoczesna). Można zwiększyć stopień szczegółowości tego diagramu poprzez dodanie przypadków użycia rozszerzających (zastosujemy <<extend>>, ponieważ prawdopodobnie będziemy korzystali tylko z jednego naraz) "Identyfikacja klienta" o np. "Identyfikacja kartą" oraz "Identyfikacja numerem".

Następnie musimy być pewni, że dany klient (uprzednio zidentyfikowany) może coś wypożyczać. W wymaganiach na system, które z takim trudem ustaliliśmy, są podane pewne warunki (patrz m.in. punkt 12 i dalsze), które muszą być spełnione.

Opisując powyższe przypadki użycia zastosowaliśmy sformułowania takie jak "następnie", "uprzednio". Jest to naturalne, ponieważ chcemy zachować pewien porządek, który zwiększy nasze szanse na niepominięcie niczego istotnego. W związku z tym należy się zastanowić, w jakiej kolejności umieszczamy przypadki użycia na diagramie? Od prawej do lewej, z góry na dół? Pytanie jest podchwytliwe - otóż diagramy przypadków użycia nie uwzględniają kolejności. Dlatego kolejność dołączania poszczególnych elementów ("pod-przypadków" użycia) jest dowolna. Zwykle robimy to zgodnie z kierunkiem ruchu wskazówek zegara, ale jest to tylko zdroworozsądkowy zwyczaj.

Na pewno zwróciłeś uwagę na to, że część elementów jest dołączona korzystając z <<include>>, a inne przy użyciu <<extend>>. Jak mówiliśmy wcześniej, użycie <<include>> oznacza, że coś jest zawsze wykonywane, a <<extend>>, że tylko czasami. Tutaj jest tak samo. Zastanowienie może budzić tylko kwestia, dlaczego, w świetle powyższego rozumowania, "Wyszukanie informacji o publikacjach" oraz "Wyszukanie informacji o egzemplarzach" są wykonywane czasami. Przecież witalną częścią biblioteki jest wyszukiwanie powyższych informacji. Oczywiście, że tak, ale może wystąpić kilka scenariuszy:

Klient przychodzi i mówi, że chce książkę "Wiedźmin tom I". W takiej sytuacji musimy odnaleźć egzemplarz, który ją zawiera.

Klient przychodzi i mówi, że chce po prostu fajną książkę fanstasy. System przedstawia kilka propozycji (ponieważ beletrystyka należy do jakieś kategorii - patrz punkt 9). Ale klientowi żaden z nich nie odpowiadał, więc nic nie wypożyczył - czyli nie musieliśmy szukać egzemplarza.

Wreszcie, może być tak, że klient chce wypożyczyć egzemplarz o numerze 6341. Wtedy nie musimy wyszukiwać publikacji.

Jak widać, możliwości jest dość dużo. I dlatego wybraliśmy najbardziej uniwersalne rozwiązanie: obydwa przypadki użycia są wykorzystywane opcjonalnie. Rysując diagram przypadków użycia, należy założyć pewien scenariusz, który określa, co się stanie, jakie decyzje podejmie klient itp. W przeciwnym wypadku większość naszych przypadków użycia wyglądałaby podobnie: najpierw identyfikacja, którą przeprowadzamy zawsze (<<include>>), a później same połączenia z <<extend>>'ami (ponieważ zawsze może nie dojść do wykonania przypadku użycia).

"Naliczenie opłaty" 2 polega po prostu na wyliczeniu należności za wypożyczane egzemplarze. Ponieważ wykorzystaliśmy <<include>, więc łatwo się domyślić, że zakładamy, iż klient wybrał coś do wypożyczenia.

"Rozliczenie opłaty" jest nazwą dość ogólną. Analogicznie jak przy "Identyfikacji klienta", specjalnie nie chcemy wnikać w szczegóły, np. rozliczenie może być za pomocą karty, gotówki czy po prostu dopisania do rachunku opłacanego przelewem raz w miesiącu.

"Wydanie egzemplarza" oznacza, że cały proces wypożyczania został szczęśliwie zakończony i powinien zostać zapamiętany w systemie. Celowo nie piszemy czegoś w rodzaju "zatwierdzenie transakcji w bazie danych", ponieważ przypadki użycia z definicji unikają wszelkiego "technicyzowania" (odwołań do technicznych aspektów implementowanego systemu).

2.4 Diagram klas

Diagram klas jest uznawany chyba za najważniejszy z diagramów wykorzystywanych w czasie tworzenia oprogramowania. I nie jest tak bez przyczyny: informacje, które się na nim znajdują, są bezpośrednio wykorzystywane przy implementacji (naturalnie, jeżeli korzystamy z obiektowego języka programowania takiego jak Java, C# czy C++). Definiują sposób przechowywania informacji w naszym systemie, a poprzez umieszczenie metod pełnią rolę swoistego szkieletu umożliwiającego zdefiniowanie zachowania się tworzonej aplikacji (patrz uwaga 2.1). Tak naprawdę możemy mówić przynajmniej o dwóch rodzajach diagramów klas:

Uwaga 2.1: Metody na diagramie klas?

Wiemy, że notacja UML pozwala na umieszczanie metod na diagramie klas. Czy zawsze powinniśmy to robić? Wbrew pozorom odpowiedź nie jest jednoznacznie twierdząca:

aby to właściwie zrealizować na etapie analizy, czy nawet projektowania, trzeba mieć dobre rozeznanie programistyczne, co nie zawsze jest oczywiste,

w przypadku dość prostych projektów, funkcjonalności mogą być umieszczane bezpośrednio w metodach modelu i w takim przypadku, faktycznie diagram klas nam to umożliwi,

w zależności od zastosowanej technologi, metody realizujące wymagania funkcjonalne nie muszą być implementowane w klasach modelu; co więcej w wielu współczesnych sytuacjach tak się nie robi, np. wzorzec MVC gdzie stosujemy np. kontrolery i serwisy (patrz rozdział 7.3)

ewentualnie można umieścić tam proste metody, np. obliczające wiek osoby.

Możemy też traktować metody z diagramu klas jako ogólne "przypomnienie" wymagań funkcjonalnych, które muszą być zrealizowane. Nie jestem jednak przekonany, czy to rzeczywiście będzie bardzo pomocne.

Analityczny diagram klas. Tworzony na etapie analizy (i tym zajmiemy się w tym rozdziale). W tym przypadku możemy korzystać z dowolnych konstrukcji występujących w obiektowości oraz wykorzystywanej notacji (w naszym przypadku jest to notacja UML).

Projektowy diagram klas. Tworzony na etapie projektowania (ten będzie przedmiotem naszego zainteresowania w rozdziale 3), a wykorzystywanym na etapie programowania. Projektując system, musimy wziąć pod uwagę możliwości środowiska, w którym będzie odbywała się implementacja. Z tego powodu wszystkie konstrukcje umieszczone na etapie analizy, a niewystępujące w danym języku (np. w Java), należy odpowiednio zmodyfikować (zastąpić je właściwymi odpowiednikami).

Czytając opis powyższego podziału, można się zastanawiać, po co na etapie analizy stosować pewne konstrukcje, skoro później trzeba będzie je zastąpić czymś innym? Tradycyjnie już nie będę w stanie podać odpowiedzi, która wszystkich zadowoli, ale spróbujmy przedstawić pewne argumenty przemawiające za takim sposobem modelowania:

Teoretycznie na etapie analizy nie znamy naszego środowiska implementacyjnego, a co za tym idzie, nie wiemy, które konstrukcje są dozwolone, a które nie. Słowo "teoretycznie" jest tutaj w pełni uzasadnione, ponieważ w praktyce w większości przypadków nasz docelowy język programowania będziemy wybierali spomiędzy kilku najbardziej popularnych: Java, C#, C++. Niezależnie od różnic pomiędzy nimi wszystkie mają podobne (takie same?) ograniczenia dotyczące (nie)występowania pewnych konstrukcji z dziedziny obiektowości. Czyli, dla większości sytuacji, ten argument nie wydaje się bardzo zasadny.

Mocniejszym argumentem jest to, że te "szczególne" (niewystępujące w popularnych językach programowania) konstrukcje są bardzo użyteczne z punktu widzenia analizy, oraz co ważniejsze, istnieją proste techniki ich obejścia. W związku z tym, z kwestią zastąpienia jednych konstrukcji innymi nie powinno być większego problemu.

Spotyka się też podejście, polegające na niewykorzystywaniu tych "trudnych" konstrukcji. Pewną korzyścią będzie łatwiejsze projektowanie oraz implementacja, ale bardzo istotną wadą takiego postępowania może być nadmierne skomplikowanie fazy analizy (m.in. poprzez trudniejsze "odczytywanie" diagramu). Czyli, innymi słowy, to co przeanalizowaliśmy, łatwiej zaprojektujemy, ale zwiększamy prawdopodobieństwo popełnienia błędów we wcześniejszej fazie (analizy). A jak pamiętamy z poprzednich rozdziałów, im błąd wcześniej popełniony, tym większe problemy może sprawić. Dlatego, ja osobiście przestrzegam przed unikaniem tych konstrukcji na etapie analizy.

Myślę, że teraz jest właściwy moment, abyśmy przeanalizowali poszczególne elementy występujące na diagramie klas. Jedna drobna uwaga: jak pisaliśmy wcześniej, książka ta jest poświęcona głównie przejściu od fazy analizy, poprzez projektowanie do implementacji. Nie będziemy się koncentrowali na bardzo dokładnym i szczegółowym omówieniu pojęć związanych z obiektowością jako taką. Więcej informacji na ten temat można znaleźć w [32], [118] czy [25].

2.4.1 Obiekt

Każda z osób, które zetknęły się z programowaniem w jednym ze współczesnych języków programowania (np. Java, C# czy C++), ma jakieś własne, intuicyjne wyobrażenie obiektu. Spróbujmy je doprecyzować, na razie zapominając o językach programowania (bez obawy - wrócimy do nich w rozdziale 3 "Projektowanie"). Nasza definicja będzie oparta na tej pochodzącej z [25] (z niewielkimi zmianami):

Definicja 2.1: Obiekt

byt, który posiada dobrze określone granice i własności oraz jest wyróżnialny w analizowanym fragmencie dziedziny problemowej.

Obiekt jest również nazywany instancją klasy - patrz dalej.

Z pojęciem obiektu związane jest zagadnienie jego tożsamości. Zakłada się, że obiekt jest rozpoznawany na podstawie samego faktu istnienia, a nie korzystając z jakiejś kombinacji jego cech. Warto zauważyć, że jest to definicja niebiorąca pod uwagę specyfiki systemów komputerowych. Wrócimy do tej kwestii w rozdziale 3. Przykładami obiektów mogą być: krzesło, budynek, faktura, student, osoba itd.

2.4.2 Klasa

Jak sama nazwa wskazuje, podstawowym elementem znajdującym się na diagramie klas są klasy. Cóż to takiego jest klasa? Naukowcy zajmujący się problemami obiektowości nie są do końca zgodni, ale myślę, że jedną z najlepszych definicji może być definicja pochodząca z [25]:

Definicja 2.2: Klasa

nazwany opis grupy obiektów o podobnych własnościach.

Zgodnie z tą definicją, klasa nie jest zbiorem obiektów (do tego jest oddzielne pojęcie). Jest to bardzo ważne stwierdzenie, ponieważ często jest to mylone. Innymi słowy, cytując z pamięci [36]: klasa jest jakby przepisem na obiekt. Opisuje jego cechy (atrybuty), zachowanie (metody) oraz budowę (w kontekście języków programowania).

Nazwa klasy powinna być rzeczownikiem w liczbie pojedynczej (np. Pracownik). Można użyć liczby mnogiej, jeżeli pojedyncza instancja klasy (obiekt) opisuje wiele elementów (np. nazwa Pracownicy w przypadku gdy przechowujemy informacje o ich grupach). Dozwolone jest też używanie kilku słów, jeżeli ma to biznesowe uzasadnienie.

Ważnym pojęciem związanym z klasą jest jej ekstensja. Spójrzmy na definicję:

Definicja 2.3: Ekstensja klasy

zbiór aktualnie istniejących wszystkich wystąpień (innymi słowy: instancji lub jak kto woli: obiektów) danej klasy.

Dla pewności potwierdźmy: tak - w działającym systemie komputerowym będzie wiele ekstensji klas, w przybliżeniu (patrz dalej uwagę o dziedziczeniu) po jednej dla każdej istniejącej klasy biznesowej, np. Publikacja, Osoba, Egzemplarz itp.

Tak naprawdę powyższa definicja nie jest do końca precyzyjna (ktoś wie dlaczego?), ponieważ nie określa zależności w stosunku do obiektów z podklas oraz nadklas (wrócimy do tego przy okazji dziedziczenia - patrz podrozdział 2.4.5.2).

Klasy możemy podzielić na:

Konkretne,

Abstrakcyjne.

Aby dobrze zrozumieć ten podział, konieczne jest wprowadzenie pojęcia dziedziczenia. Dlatego wrócimy do tego tematu w podrozdziale 2.4.5.

Rysunek 2.6: Notacja dla klas.

Rysunek 2.6 zawiera różne warianty notacji służącej do przedstawiania klasy na diagramie:

Część (a) pokazuje kompletną klasę (razem z atrybutami oraz metodami),

Część (b) przedstawia klasę bez pokazanych metod oraz atrybutów. Notacja zezwala na ukrywanie niepotrzebnych (brakujących) elementów.

2.4.2.1 Atrybuty

Atrybuty służą do opisu własności obiektów należących do pewnej klasy. Przyjmuje się, że ich wartości znajdują się wewnątrz konkretnych obiektów i nie są współdzielone z innymi obiektami. Oznacza to, że każdy obiekt ma swój indywidualny zestaw, nawet jeżeli w wielu z nich znajdują się te same wartości (szczególnym przypadkiem są atrybuty klasowe - patrz dalej). Oczywiście wartości atrybutów mogą być zmieniane w trakcie życia obiektu.

Poniżej znajdują się różne rodzaju atrybutów z uwzględnieniem kryteriów podziału:

budowa:

prosty; przechowuje atomowe (niepodzielne) wartości, m. in.: liczba lub napis. Przykłady: pensja, nazwisko, nazwa koloru, waga;

złożony; Składa się z atrybutów prostych lub innych atrybutów złożonych. Przykłady: adres (może się składać z ulicy, numeru domu, miasta, kodu pocztowego), współrzędne położenia w przestrzeni 3D (składają się z wartości dla osi x, y, z).

liczność:

pojedynczy; posiada jedną wartość. Przykłady: data urodzenia, płeć;

powtarzalny; posiada jedną lub więcej wartości. Przykłady: imię (bo ktoś może mieć wiele imion).

przynależność:

obiektu; każdy obiekt w klasie może mieć własną wartość. Przykłady: dla klasy Pracownik przykładami mogą być: nazwisko, imię, data urodzenia.

klasowy; Ma tę samą wartość dla wszystkich obiektów danej klasy. Przykłady: Dla klasy Pracownik przykładami mogą być: minimalny wiek, maksymalna pensja.

ustalanie wartości:

konkretny; wartość atrybutu jest przechowywana bezpośrednio w obiekcie. Przykłady: Data urodzenia.

wyliczalny; Wartość atrybutu może być wyliczona na podstawie innych danych (np. atrybutów). Czasami przechowujemy tę wyliczoną kopię. Przykłady: wiek (obliczony na podstawie daty urodzenia oraz aktualnej daty systemowej).

obowiązkowość:

wymagany; Jeżeli nie oznaczono inaczej to atrybut musi mieć wartość. Przykłady: nazwisko w klasie pracownik.

opcjonalny; Atrybut może nie mieć wartości. Przykłady: Nazwisko panieńskie w klasie pracownik (mężczyźni zwykle nie mają nazwiska panieńskiego).

Spójrzmy jeszcze na dodatkowy komentarz dotyczący różnych rodzajów atrybutów w klasie:

Atrybut powtarzalny powinien mieć nazwę w liczbie pojedynczej, np. Imię. Jeżeli nazwiemy go imiona, to będzie oznaczało, że mamy wiele imion (wnioskujemy to z jego nazwy, a zatem znaczenia). Jeżeli dodatkowo oznaczymy go jako powtarzalny, to całość będzie oznaczała jakąś listę imion, z której każdy element będzie miał wiele wartości (w tym przypadku imion). Czyli będzie to lista list, a raczej nie o to nam chodziło.

Jak widać z przykładu dotyczącego atrybutu obiektu, nie ma przeszkód, aby wiele (lub wszystkie) obiekty danej klasy miały taką samą wartość danego atrybutu (w przykładzie jest data urodzenia, która dla odpowiednio dużej liczby osób na pewno się powtórzy).

Zaletą korzystania z atrybutu klasowego (ta sama wartość dla wszystkich obiektów danej klasy) jest to, że przechowujemy go tylko raz ("w klasie", a nie "w obiekcie"), co oszczędza pamięć (tak, pamiętam, że mieliśmy abstrahować od języków programowania, ale to ważny argument) i znacząco ułatwia zmiany jego wartości (ponieważ atrybut klasowy nie musi mieć stałej wartości - to też jest czasami mylone). Oprócz tego korzystamy z niego w kontekście klasy (a nie obiektu), co oznacza, że możemy go używać nawet jeżeli nie istnieje żaden obiekt danej klasy.

Atrybut wyliczalny budzi, przynajmniej na początku, sporo wątpliwości. Po co przechowywać dane dwa razy? Dla jasności: jego istnienie niekoniecznie musi oznaczać dosłowne przechowywanie. Często możemy wyliczać jego wartość w razie potrzeby (pewnie zrobilibyśmy tak z wiekiem). W innych sytuacjach, rozsądniej będzie zapamiętać w systemie tę obliczoną wartość (czyli wystąpi jakaś forma redundancji danych3). Kryterium decyzyjnym jest oczywiście koszt obliczenia oraz częstość dostępu:

ustalenie wieku nie kosztuje zbyt dużo czasu procesora,

gdybyśmy jednak mieli atrybut, np. średnia wartość towarów sprzedanych przez handlowca, to w przypadku dużej firmy jego obliczenie (na podstawie faktur) może zająć trochę czasu. W takiej sytuacji rozsądne wydaje się przechowywanie jego kopii. W efekcie pewne informacje są zapisane w systemie wielokrotnie i w przypadku aktualizacji należy zadbać o zmianę wszystkich "egzemplarzy". Dlatego stosujemy specjalną notację, która nas ostrzega: "uwaga - redundancja danych"!

Może to zabrzmi dość trywialnie (ale widziałem sporo takich błędów), ale gdy jakiś atrybut oznaczymy jako wyliczalny, to musimy się upewnić, że w systemie są dane potrzebne do ustalenia jego wartości, np. umieszczenie wyliczalnego atrybutu wiek, bez przechowywania daty urodzenia, nie jest najlepszym pomysłem.

Pewne rodzaje atrybutów można łączyć (po jednym z każdego kryterium), np. powtarzalny, klasowy i do tego wyliczalny. Pamiętajmy jednak, że nie wszystkie kombinacje mają biznesowy sens.

Dodatkowo, każdy z atrybutów może być oznaczony specjalnym ograniczeniem Unique4. Taki atrybut posiada unikalną wartość w ramach ekstensji, czyli może istnieć tylko jeden obiekt należący do danej klasy, który ma atrybut z daną wartością. Wykorzystujemy to wtedy, gdy chcemy, aby to system dbał o unikalność danych, np. mamy klasę Osoba i atrybut PESEL.

Atrybut opcjonalny może nie mieć wartości. Jest to użyteczne co najmniej w dwóch przypadkach:

Wartość nie ma sensu z biznesowego punktu widzenia, np. w firmie mamy pracowników będących mężczyznami oraz kobietami, ale z jakichś powodów nie chcemy dla nich tworzyć oddzielnych klas. Dlatego wystąpienia (obiekty) klasy Pracownik opisujące mężczyzn nie będą przechowywały wartości dla atrybutu Nazwisko panieńskie (ewentualnie dla obu płci możemy przechowywać nazwisko rodowe). Oczywiście można to obejść i zapamiętywać tam np. pusty ciąg tekstowy, ale z punktu widzenia systemu będzie tam wartość, tylko że specyficzna.

Nasze dane są niepełne i w związku z tym dla niektórych obiektów i ich niektórych atrybutów nie mamy wszystkich informacji, np. część pracowników nie podała daty swojego urodzenia.

Rysunek 2.7: Notacja do oznaczania atrybutów.

Rysunek 2.7 zawiera notację UML służącą do oznaczania różnych rodzajów atrybutów. Pominięto sposób oznaczania typu atrybutu - będzie o tym przy okazji projektowania. Kilka uwag:

Atrybut powtarzalny oznaczamy pokazując jego liczności. Zapis [0...*] oznacza dowolną liczność, a np. [1...4] mówi, że atrybut może mieć od 1 do 4 wartości. Tak naprawdę, brak informacji o liczności oznacza po prostu liczność [1].

Brak specjalnych symboli zakłada, że mamy atrybut z licznością [1], czyli wymagany, obiektu i konkretny.

Oznaczenie atrybutu wyliczalnego.

Atrybut klasowy. Tutaj mamy na myśli minimalny wiek, powyżej którego osoba może zostać naszym pracownikiem.

Adres jest przykładem atrybutu złożonego. Jak widać, ten rodzaj nie ma żadnych specjalnych oznaczeń.

W ten sposób mówimy systemowi, że ten atrybut ma mieć unikalną wartość wśród obiektów danej klasy.

2.4.3 Metody

Metody służą do opisu zachowania obiektu. Każda z nich może zwracać jakąś wartość (ale nie musi) i pobierać parametry (również nie musi). W przeciwieństwie do atrybutów, gdzie zróżnicowanie było spore, tutaj mamy dość prosty podział:

Rysunek 2.8: Notacja do oznaczania metod.

Metody obiektu. Operują na konkretnym obiekcie. Mają dostęp do jego atrybutów oraz innych metod tego obiektu (a dokładniej: innych metod zdefiniowanych w klasie, do której należy ten obiekt). Nie mamy możliwości bezpośredniego odwołania się do innego obiektu (w tym jego zawartości) - nawet z tej samej klasy. Można powiedzieć w uproszczeniu, że pojedynczy obiekt nie jest świadomy istnienia innych obiektów danej klasy (chyba że dostanie się do nich poprzez ekstensję). Przykłady to (z dokładnością do nazewnictwa):

PodajNazwisko():String, zwracamy nazwisko konkretnego pracownika (tego, na rzecz którego wywołaliśmy tę metodę).

ZmienPensje(NowaPensja):void, zmieniamy pensję konkretnego pracownika.

CzyJestKobieta():boolean, pytamy czy konkretny pracownik jest kobietą.

Metody klasowe. Mają dostęp do całej ekstensji, a zatem do wszystkich obiektów należących do danej klasy. Mogą na nich operować, ale nie muszą: może być np. metoda klasowa, która zwróci jakąś wartość bez "zaglądania" do ekstensji (dlatego stwierdzenie, że metody klasowe operują na całej ekstensji, nie jest do końca precyzyjne). Analogicznie jak w przypadku atrybutów, metody klasowe wywołujemy na rzecz klasy, a nie konkretnego obiektu. Dzięki temu można z nich korzystać nawet, gdy nie ma jeszcze obiektów danej klasy. Przykłady:

PodajNajlepszegoPracownika():Pracownik,

ZmienPensje(NowaPensja:Real):void, analogiczny przykład był podany dla metod obiektu, ale tutaj ma inne znaczenie; tam zwiększaliśmy pensję jednemu pracownikowi, tutaj wszystkim (całej ekstensji).

Rysunek 2.8 pokazuje notację służącą do umieszczania metod na diagramie (pominięto informacje o parametrach oraz zwracanym typie, pokazano również atrybuty). Jak widać, oznaczenie metody klasowej jest analogiczne do oznaczenia atrybutu klasowego: podkreślona nazwa.

2.4.4 Asocjacje

Rysunek 2.9: Przykład ilustrujący wykorzystanie asocjacji.

Asocjacje służą do łączenia klas, które mają jakieś biznesowe zależności. Dzięki temu możemy np. zapamiętać, że Pracownik (klasa) pracuje w (asocjacja) Firmie (klasa). Sytuację tę ilustruje rysunek 2.9. Nazwa asocjacji powinna być tak dobrana, aby w połączeniu z nazwami klas utworzyła sensowne wyrażenie, np. Pracownik pracuje w Firmie. Domyślnie nazwa asocjacji ma poprawne znaczenie przy czytaniu od lewej do prawej. Jeżeli w powyższym przykładzie wolimy utworzyć nazwę z punktu widzenia firmy, wtedy brzmiałaby ona zatrudnia i należałoby ją czytać od prawej do lewej. Sytuacje tę ilustruje rysunek 2.10. Warto zwrócić uwagę na mały trójkącik przy nazwie pokazujący właściwy kierunek czytania. Podkreślmy: kierunek czytania nazwy, a nie kierunek asocjacji. Jeżeli nie zaznaczono inaczej, to asocjacja jest dwukierunkowa: w naszym przykładzie możemy przejść od firmy do pracownika oraz od pracownika do firmy.

Rysunek 2.10: Przykład ilustrujący wykorzystanie asocjacji. W stosunku do rysunku 2.9 zmieniono nazwę oraz jej kierunek czytania

Umieszczenie nazwy to nie jedyny sposób opisania asocjacji. Zamiast niej można wykorzystać nazwy ról (patrz rysunek 2.11). Należy pamiętać, że na diagramie są umiejscowione przy klasie docelowej, np. rola Pracodawca w klasie Pracownik. Przy takim podejściu mamy kompleksowy opis: z punktu widzenia każdej z klas. Jest to szczególnie użyteczne, gdy na dalszych etapach będziemy przekształcali diagram do wersji implementacyjnej, korzystając z narzędzia CASE. Więcej o tym będzie w kolejnych rozdziałach.

Rysunek 2.11: Ilustracja wykorzystania ról asocjacji

Jak zapewne zauważyli uważni czytelnicy, oprócz własności asocjacji opisanych powyżej diagram zawiera coś jeszcze. W powyższych przykładach są to znaczki "*". Oznaczają one liczności asocjacji - spójrzmy na diagram 2 12. Notacja służąca do zdefiniowania liczności ma taki sens:

Konkretny obiekt klasy A jest powiązany z Y obiektów klasy B,

Konkretny obiekt klasy B jest powiązany z X obiektów klasy A.

Zamiast X, Y wstawiamy jedną z poniższych wartości (w starszej wersji UML 1.x dopuszczano też ich kombinacje):

1,

0..1,

* oznacza zero lub więcej,

1...* oznacza jeden lub więcej

Konkretną liczbę, np. 4.

W starszej wersji UML (1.x) informacje o licznościach można było dowolnie ze sobą łączyć, korzystając z powyższej notacji, np. "0...4, 7, 8, 13, 45...*". Oczywiście nie zawsze miało to biznesowy sens. Dlatego w UML2 zrezygnowano z takich "kombinowanych" liczności. Jeżeli nie podano żadnej informacji, to znaczy, że liczność wynosi 1. Zwykle stosuje się liczności "1", "0, 1", "1...*".

Rysunek 2.12: Liczności asocjacji

Można się zastanawiać, po co w ogóle podawać informacje o licznościach. Przecież można by się umówić, że wszędzie jest "*" i możemy zapamiętać każdą sytuację biznesową. Głównym powodem jest chęć pilnowania pewnych reguł, np. jeżeli gdzieś występuje liczność "1", to nie będziemy mogli stworzyć obiektu ("system" na to nie pozwoli), który nie będzie powiązany z dokładnie jednym obiektem. Dodatkowym powodem są kwestie implementacyjne, ponieważ inaczej się zapamiętuje liczności typu "jeden", a inaczej "wiele".

Jak, mam nadzieję, pamiętamy, wystąpieniem (instancją) klasy jest obiekt. Podobna zależność jest też dla asocjacji - spójrzmy na definicję:

Definicja 2.4: Powiązanie

wystąpienie asocjacji pomiędzy konkretnymi obiektami, np. informacja, że pracownik "Kowalski" pracuje w firmie "Microsoft".

2.4.4.1 Asocjacja binarna

Asocjacja binarna jest to taka asocjacja, która występuje pomiędzy dokładnie dwoma klasami. Tak naprawdę już znamy ten rodzaj asocjacji, ponieważ to, co do tej pory napisaliśmy o asocjacjach, charakteryzuje właśnie ją.

Przydatną kwestią związaną z licznościami asocjacji binarnych jest tzw. redukcja liczności. Patrząc na klasy połączone asocjacją i jej liczności, można wyróżnić trzy główne kategorie:

1 do 1,

1 do wiele,

Wiele do wiele.

Czasami (np. gdybyśmy chcieli przejść na model relacyjny) ta ostatnia kategoria może sprawić nam spory problem. Dlatego może zastosować przejście pokazane na rysunku 2.13. Wprowadzamy klasę pośredniczącą C i zamieniamy jedną asocjację "wiele-do-wielu" (pomiędzy klasami A i B) na dwie "jeden-do-wielu" (pomiędzy A-C oraz C-B). Całe przejście jest automatyczne i zawsze wykonywane według tego samego schematu. Warto zwrócić uwagę na pozorny błąd w licznościach: na nowym diagramie widzimy, że liczności X oraz Y zamieniły się miejscami. Sprawdźmy: z punktu widzenia klasy A występowała liczność Y, a z punktu widzenia klasy B była liczność X. Okazuje się, że nadal tak jest (proszę sprawdzić!).

Rysunek 2.13: Redukcja liczności dla asocjacji

Nie zawsze da się wymyślić sensowną nazwę dla klasy C, dlatego czasami jej nazwę stanowi zlepek nazw sąsiadujących klas (np. zamiast C mogliśmy napisać AB).

2.4.4.2 Asocjacja skierowana

Asocjacja skierowana to taka asocjacja, która ma określony kierunek. W poprzednich przykładach asocjacje były dwukierunkowe, co oznacza, że można było przejść od jednej klasy do drugiej i odwrotnie. W przypadku asocjacji skierowanej nie jest to możliwe. Zwykle chcielibyśmy móc przejść w obu kierunkach. W rzadkich wypadkach, gdy chcemy zaznaczyć, że przejście jest możliwe tylko w jedną stronę, stosujemy asocjację skierowaną. Innym powodem mogą być kwestie wydajnościowe, gdy zależy nam na oszczędzaniu pamięci. Niemniej, biorąc pod uwagę, moc współczesnych komputerów, ten powód zwykle nie ma zastosowania. W notacji UML, asocjację skierowaną oznaczamy strzałką, która wskazuje kierunek przejścia (rysunek 2.14). Zauważmy, że zdefiniowaliśmy tylko jedną liczność (ponieważ nie ma połączenia w drugą stronę).

Rysunek 2.14: Przykład asocjacji skierowanej

2.4.4.3 Asocjacja n-arna

Ten rodzaj asocjacji sprawia sporo problemów i z tego powodu w praktyce jest rzadko wykorzystywany. Powiem więcej, raczej odradzam korzystanie z tej konstrukcji, tym bardziej, że bez problemu można ją zastąpić rozwiązaniem równoważnym (patrz podrozdział 3.7.8).

Problemy biorą się ze sposobu określania liczności oraz biznesowego wyobrażenia sobie danej sytuacji. Najczęściej zakłada się, że asocjacja n-arna powinna mieć wszędzie liczność "wiele", a procedura ustalania tych liczności nawiązuje do asocjacji binarnych. Szacowany koniec asocjacji "konfrontujemy" z pozostałymi końcami połączonymi w jedną, wirtualną całość. Rysunek 2.15 przedstawia przykładową asocjację n-arną opisującą zależność pomiędzy muzykiem, koncertem oraz instrumentem. Nazwijmy ją (zależność oraz asocjacje) "granie". Zakładamy, że:

na konkretnym koncercie może wystąpić wielu muzyków grających na różnych instrumentach,

konkretny muzyk może wystąpić na wielu koncertach, grając na wielu instrumentach,

konkretny instrument jest wykorzystywany przez wielu muzyków grających na wielu koncertach.

Rysunek 2.15: Przykładowa asocjacja n-arna

Pozornie mogłoby się wydawać, że wystarczy połączyć koncert z muzykiem oraz koncert z instrumentem za pomocą dwóch asocjacji binarnych. W rzeczywistości tak nie jest (patrz rysunek 2.16. Przy takim podejściu co prawda wiedzielibyśmy, którzy muzycy grali na danym koncercie i jakie instrumenty wykorzystano, ale nie wiedzielibyśmy, który muzyk grał na którym instrumencie (w ramach tego koncertu). Jak widać z powyższego wywodu, musimy zastosować asocjację o trzech krańcach, ponieważ angażuje ona więcej niż dwie klasy (i dlatego asocjacja binarna nie wystarczy). Na szczęście istnieje prawidłowy sposób zastąpienia asocjacji n-arnej - poznamy go w rozdziale 3.7.8 (). Jeżeli jest to biznesowo uzasadnione, stopień asocjacji (n) może być wyższy i równy 3, 4, 5 itd.

Rysunek 2.16: Ilustracja problemu z asocjacją n-arną

2.4.4.4 Asocjacja kwalifikowana

W odróżnieniu od asocjacji n-arnej asocjacja kwalifikowana jest dość chętnie stosowana - czasami nawet zbyt często i bez zrozumienia (a szczególnie jej implementacyjna inkarnacja - patrz podrozdział 3.7.7). W klasycznej asocjacji binarnej, aby znaleźć konkretny obiekt znajdujący się "po drugiej stronie", musimy przeglądać wszystkie powiązania, aż znajdziemy obiekt spełniający nasze kryteria (np. pracownik z określonym numerem PESEL). I tutaj właśnie z pomocą przychodzi nam asocjacja kwalifikowana, a szczególnie kwalifikator. Spójrzmy na definicję:

Definicja 2.5: Kwalifikator

atrybut (lub kombinacja atrybutów) umożliwiający jednoznaczne zidentyfikowanie obiektu docelowego.

Rysunek 2.17 składa się z dwóch części:

Rysunek 2.17: Asocjacja binarna (a) oraz kwalifikowana (b)

(a) opisuje związek pracownika z firmą za pomocą klasycznej asocjacji. Chcąc odnaleźć pracownika z konkretnym numerem PESEL, musimy przeglądać wszystkich pracowników, dostępnych poprzez powiązania (jak pamiętamy, powiązanie jest wystąpieniem asocjacji) i dla każdego z nich sprawdzamy, czy posiada interesujący nas PESEL.

(b) również opisuje związek pracownika z firmą, ale za pomocą asocjacji kwalifikowanej. Dzięki temu rozwiązaniu, mając "w ręku" konkretną wartość kwalifikatora (numer PESEL), otrzymujemy odpowiadający mu obiekt (pracownika). Warto zwrócić uwagę na dwie rzeczy:

Atrybut będący kwalifikatorem jest usuwany z pola atrybutów "swojej" klasy (na diagramie) i przenoszony obok klasy docelowej,

Ulega zmianie liczność (często też się o tym mówi redukcja liczności). Jest to spowodowane tym, że myślimy w kategoriach pary (w tym przypadku PESEL i Firma): dla konkretnej firmy i konkretnego numeru PESEL mamy powiązanie tylko do jednego pracownika; w drugą stronę: konkretny pracownik może być powiązany z wieloma takimi parami (liczność "*"), ponieważ może pracować w wielu firmach.

Aby kwalifikator dobrze wypełniał swoją rolę, musi być unikatowy - mówiliśmy już o tym. Ale ta unikatowość, nie musi być tak mocna jak w przypadku numeru PESEL, który jest unikalny w całej ekstensji pracowników (bo każda osoba-pracownik ma własny numer). Wystarczy, że kwalifikator będzie unikalny z punktu widzenia obiektu źródłowego (w naszym przykładzie: Firmy). Innymi słowy: mając konkretną firmę, musimy zadbać, aby jej pracownicy mieli unikatowe numery identyfikacyjne. Nic się nie stanie, jeżeli inny pracownik, ale pracujący w innej firmie będzie miał też taki numer (bo pracownika identyfikujemy z punktu widzenia konkretnej firmy).

2.4.4.5 Asocjacja rekurencyjna (zwrotna)

Asocjacja rekurencyjna (zwana też zwrotną) polega na tym, że występuje w ramach tej samej klasy. Aby móc prawidłowo zidentyfikować znaczenie każdego z końców asocjacji, obowiązkowo musimy zastosować nazwy ról. Rysunek 2.18 pokazuje klasyczny przykład umożliwiający przechowywanie informacji o zależnościach służbowych w przedsiębiorstwie (w analogiczny sposób można modelować np. informacje o stosunkach własnościowych: firma jest właścicielem innych firm).

Uwaga 2.2: Nazwy asocjacji, a role

Zastanówmy się jeszcze przez chwilę, dlaczego asocjacja rekurencyjna wymaga dwóch ról, a w zwykłej wystarczy nam jedna nazwa? Odpowiedź jest prosta: w zwykłej asocjacji mamy do czynienia z dwiema różnymi klasami, a więc pełnione role możemy wywnioskować na podstawie przynależności obiektów do tych klas. W przypadku rekurencyjnej asocjacji mamy do czynienia z jedną klasą, a więc nie mamy takiej możliwości. Dlatego musimy ręcznie określić, co oznacza każdy z końców asocjacji.

Rysunek 2.18: Przykładowa asocjacja rekurencyjna (zwrotna)

Ten rodzaj asocjacji zwykle nie sprawia problemów koncepcyjnych. Należy tylko zadbać o właściwe liczności (dopuszczające "0"): w naszym przykładzie, gdzieś na szczycie hierarchii, będzie pracownik, który nie ma szefa (ponieważ szef wszystkich szefów nie ma już szefa). Analogicznie, na samym dole będzie pracownik, który nie ma podwładnych.

Warto jeszcze podkreślić, że asocjacja rekurencyjna, co prawda występuje w ramach tej samej klasy, ale zwykle łączy dwa różne obiekty - patrz przykładowy diagram obiektów na rysunku 2.19).

Uwaga 2.3: Diagram obiektów, a diagram klas

Diagram obiektów, w odróżnieniu od diagramu klas, zawiera już konkretne wystąpienia - obiekty; notacja jest wzorowana na notacji dla diagramu klas i może pokazywać wartości atrybutów oraz powiązania, a nie asocjacje.

Rysunek 2.19: Przykładowy diagram obiektów dla asocjacji rekurencyjnej z rysunku 2.18

2.4.4.6 Asocjacja z atrybutem (klasa asocjacji)

W niektórych sytuacjach zachodzi potrzeba przechowania dodatkowych informacji na temat konkretnego powiązania (wystąpienia asocjacji). Przypomnijmy sobie przykład z rysunku 2.17 część (a). Jak widzimy, ten diagram umożliwia nam zapamiętanie informacji o miejscach pracy poszczególnych pracowników. Załóżmy, że chcielibyśmy mieć informacje o okresie czasu, który spędzili pracując w poszczególnych firmach. Jak możemy to zrobić? Najprostszym pomysłem, jaki przychodzi do głowy, jest wstawienie odpowiednich atrybutów do którejś z klas:

Pracownik. Załóżmy, że tak zrobiliśmy i w klasie mamy atrybuty "Data rozpoczęcia" oraz "Data zakończenia". Wygląda dobrze? Niestety nie, ponieważ pracownik może pracować w wielu miejscach, a powyższe atrybuty mogą przechować informacje tylko dla jednej pracy. W takim razie spróbujmy zastosować atrybuty powtarzalne (umożliwiają przechowywanie wielu wartości). Wygląda na to, że problem jest rozwiązany. I znowu nie: co prawda będziemy w stanie zapamiętać wiele dat rozpoczęcia i zakończenia, ale nie mamy możliwości powiązania ich z konkretnymi wystąpieniami asocjacji (ponieważ nie ma gwarancji, że kolejność poszczególnych wartości będzie stała). Innymi słowy, będziemy wiedzieli, kiedy pracownik zaczynał oraz kończył pracę, ale nie będziemy mogli tego połączyć z konkretną firmą.

Firma. Jak zapewne wszyscy się już domyślili, wstawienie atrybutów do klasy Firma spowoduje analogiczne problemy jak w przypadku klasy Pracownik.

Czyżby nie dało się tego zrobić? Oczywiście, że się da, ale musimy zastosować nową konstrukcję: klasę asocjacji (rysunek 2.20; dodaliśmy też atrybuty do istniejących klas). Cóż to takiego jest? Jest to zwykła klasa (ma takie same cechy jak każda inna), ale "podłączona" do asocjacji. Wystąpienia klasy asocjacji niosą dodatkowe informacje dotyczące konkretnego powiązania (wystąpienia asocjacji). Można o niej myśleć jak o "karteczce" przyczepionej do nitki obrazującej powiązanie.

Specjalną cechą klasy asocjacji jest ewentualny brak nazwy. Jest to o tyle korzystne, że wtedy na pierwszy rzut oka widać, iż traktowana jest ona tylko jako dodatkowa informacja dla asocjacji (która jest głównym elementem odpowiedniego fragmentu modelu). Można też do niej podłączać asocjacje oraz inne elementy charakterystyczne dla klasy. Jednakże, myślę, że należy tego unikać. Jeżeli jest to biznesowo uzasadnione i chcemy podłączyć jakąś asocjację do klasy asocjacji, to warto się zastanowić, czy nie zrobić z niej "normalnej" klasy.

Rysunek 2.20: Zastosowanie klasy asocjacji

Uwaga 2.4: Stosowanie klasy asocjacji

Co właściwie spowodowało, że musieliśmy użyć tej nowej konstrukcji? Głównym powodem są liczności "wiele" występujące po obu stronach asocjacji. Gdyby gdzieś było "1" lub "0..1", to moglibyśmy zastosować atrybuty w jednej z klas. W naszym przypadku, aby zapamiętać informacje o okresie pracy, musieliśmy zastosować klasę asocjacji.

W związku z tym, umieszczając klasę asocjacji na diagramie, upewnij się, że liczności są odpowiednie: "* - *. W naszym przypadku mamy do czynienia z taką sytuacją, więc wszystko jest OK.

Aby ułatwić zrozumienie mechanizmu klasy asocjacji spójrzmy na rysunek 2.21, który zawiera diagram obiektów (a nie klas). Widzimy dwa wystąpienia klasy Pracownik, trzy wystąpienia klasy Firma oraz cztery instancje klas asocjacji. Na podstawie diagramu możemy powiedzieć, że:

Jan Kowalski pracował w trzech firmach:

"Speedo" od 2015-05-21 do 2020-06-14,

"Fasto" od 2020-06-15 do 2021-06-14,

"Medio" od 2021-06-15 do 2023-06-14

Wojciech Nowak pracował tylko w firmie "Medio" od 2020-05-21 do 2023-06-14. Zwróćmy też uwagę, że jest to ta sama firma w której pracował też Jan Kowalski. Innymi słowy, konkretny obiekt może być połączony z wieloma innymi obiektami. Naturalnie tylko wtedy gdy liczności na to pozwalają - w naszym przykładzie mamy "wiele do wielu" (rysunek 2.20), więc wszystko jest OK.

Rysunek 2.21: Diagram obiektów ilustrujący wykorzystanie klasy asocjacji

2.4.4.7 Agregacja i kompozycja

Agregacja i kompozycja są szczególnymi rodzajami asocjacji (głównie binarnych). Ich dodatkowym zadaniem, oprócz modelowania jakiejś zależności, jest podkreślanie związku typu część-całość. Rysunek 2.22 przedstawia przykładową agregację opisującą grupę składająca się z pracowników. Symbol rombu, oznaczający właśnie agregację, umieszczamy przy "całości". Możemy również podać informację dotyczącą liczności. Jeżeli chodzi o nazwę, to zwykle ją pomijamy, ponieważ wykorzystanie agregacji umożliwia odczytywanie tego związku jako: "składa się", "zawiera", "jest częścią" itp.

Rysunek 2.22: Przykładowa agregacja

Mocniejszą formą agregacji (a więc również asocjacją) jest kompozycja. O ile zamodelowanie agregacji nie niesie żadnych specjalnych konsekwencji, to wykorzystanie kompozycji oznacza, że:

Część nie może być współdzielona (można to zapamiętać, myśląc, że kompozycja jest "zazdrosna"),

Część nie może istnieć bez całości,

Usunięcie całości oznacza też usunięcie części. Natomiast usunięcie części nie musi oznaczać usunięcia całości.

Rysunek 2.23: Przykładowa kompozycja

Warto zwrócić uwagę, że powyższe cechy skutkują pewnymi konkretnymi licznościami. Rysunek 2.23 zawiera przykładową kompozycję (zamalowany romb) opisującą budynek oraz jego pokoje. Ten przykład jest o tyle dosłowny, że z fizycznego punktu widzenia nie może istnieć pokój bez budynku. Nie zawsze tak musi być: można również zastosować kompozycję w przykładzie z rysunku 2.22, jeżeli tylko mamy do tego biznesowe uzasadnienie:

Pracownik należy tylko do jednej grupy. Warto zwrócić uwagę, że jeżeli chcielibyśmy trzymać historię przydziałów do grup, to docelowa liczność "1" nam na to nie pozwoli. Innymi słowy: nie będzie mógł istnieć obiekt klasy Pracownik połączony z wieloma obiektami klasy Grupa.

W modelowanej firmie nie może być pracownika nie przypisanego do grupy,

Usunięcie grupy oznacza też usunięcie jej pracowników.

Uwaga 2.5: Stuprocentowa kompozycja

Niewiele jest przykładów takich bezdyskusyjnych kompozycji (Budynek-Pokój), które w każdym systemie powinny być tak przedstawiane. Przykładowo samochód-silnik może być kompozycją, gdy sprzedajemy tylko kompletne samochody z silnikami, a agregacją w sytuacji, gdy można kupić również sam silnik (bo silnik mógłby istnieć bez samochodu).

2.4.5 Dziedziczenie

Dziedziczenie jest jednym z najważniejszych pojęć w obiektowości i oznacza pewną zależność pomiędzy klasami. Owa zależność umożliwia utworzenie nowej klasy (mówimy "podklasy") na podstawie "starej" (nadklasy). Podklasa posiada wszystkie cechy swojej nadklasy plus, ewentualnie, swoje własne. Z punktu widzenia modelowania podklasa jest szczególnym przypadkiem nadklasy, czyli można dziedziczyć klasę Pracownik z klasy Osoba (bo pracownik też jest osobą, chyba że opisujemy fabrykę robotów). W świetle tego błędne jest dziedziczenie, np. pokoju z domu (bo pokój nie jest szczególnym przypadkiem domu).

Następne podrozdziały zawierają krótkie omówienie poszczególnych rodzajów dziedziczenia i tematów z tym związanych. Bardziej szczegółowe informacje można znaleźć w [25], [32] czy [118].

(fragment książki)

Więcej informacji

W pierwszej edycji książki zajmowaliśmy się wypożyczalnią wideo, ale ten rodzaj biznesu praktycznie już nie występuje i mógłby być zbyt egzotyczny dla współczesnych czytelników.

Oczywiście biblioteka, która wymaga opłat jest dość rzadkim zjawiskiem, ale pewnie można takie znaleźć.

Redundancja danych jest terminem z zakresu baz danych i w skrócie oznacza przechowywanie wielu kopii tych samych informacji. Powodami takiego podejścia mogą być m.in. bezpieczeństwo (w razie awarii mamy "zapasową" kopię) lub wydajność (każdorazowe obliczanie danej wartości jest czasochłonne).

Ograniczenia są jednym z mechanizmów rozszerzalności UML i ogólnie rzecz biorąc, umożliwiają umieszczenie na diagramie informacji, których nie jesteśmy w stanie wyrazić przy pomocy np. klas czy atrybutów. Mogą być zapisywane zwykłym tekstem, korzystając ze specjalnego języka OCL czy wyrażeń matematycznych. Umieszczamy je w nawiasach klamrowych. Więcej w rozdziale 2.4.6.

Zapewne to nie przypadek sprawił, że znalazł się w tej książce...

Bibliografia

[1] Accessing JPA Data with REST. b.d. Https://spring.io/guides/gs/accessing-data-rest.
[2] ADO.NET Overview. b.d. Https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ado-net-overview.
[3] Angular - JavaScript Framework. b.d. Https://angular.dev/.
[4] Apache Commons. b.d. Https://commons.apache.org/.
[5] Apache CouchDB documentation. b.d. Https://couchdb.apache.org/.
[6] Apache Derby - open source relational database implemented entirely in Java. b.d. Https://db.apache.org/derby/.
[7] Apache HttpComponents. b.d. Https://hc.apache.org/.
[8] Apache Log4j. b.d. Https://logging.apache.org/log4j.
[9] Apache Maven project. b.d. Https://maven.apache.org/.
[10] Apache NetBeans IDE. b.d. Https://netbeans.apache.org/.
[11] Apache OpenJPA project. b.d. Https://openjpa.apache.org/.
[12] Bruno is a Git-integrated, fully offline, and open-source API client. b.d. Https://www.usebruno.com/.
[13] BugSnag - Application insights your developers need without the noise. b.d. Https://www.bugsnag.com/.
[14] C# Documentation comments. b.d. Https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/documentation-comments.
[15] Candase Hokanson, Karl Wiegers i. 2024. Specyfikacja wymagań oprogramowania. Kluczowe praktyki analizy biznesowej. Wydawnictwo Helion.
[16] Cassandra: Manage massive amounts of data, fast, without losing sleep. b.d. Https://cassandra.apache.org/.
[17] curl - command line tool and library for transferring data with URLs. b.d. Https://curl.se/.
[18] Cypress - Test. Automate. Accelerate. b.d. Https://www.cypress.io/.
[19] DataNucleus - Java persistence and retrieval. b.d. Https://www.datanucleus.org/products/accessplatform_6_0/.
[20] Doxygen. b.d. Https://www.doxygen.nl/.
[21] Eckel, Bruce. 2006. Thinking in Java. Edycja polska. Wydanie IV. Wydawnictwo Helion.
[22] EclipseLink - Comprehensive open-source Java persistence solution. b.d. Https://eclipse.dev/eclipselink/.
[23] EclipseLink Overview. b.d. Https://eclipse.dev/eclipselink/.
[24] Entity Framework documentation hub. b.d. Https://learn.microsoft.com/en-us/ef/.
[25] Ewa Stemposz, Jacek Płodzień i. 2005. Analiza i projektowanie systemów informatycznych. Wydanie drugie rozszerzone. Wydawnictwo PJWSTK.
[26] Exploring the New HTTP Client in Java. b.d. Https://www.baeldung.com/java-9-http-client.
[27] Farley, David. 2023. Nowoczesna inżynieria oprogramowania. Stosowanie skutecznych technik szybszego rozwoju oprogramowania wyższej jakości. Helion.
[28] Figma - build great products. b.d. Https://www.figma.com/.
[29] Firebase Crashlytics. b.d. Https://firebase.google.com/docs/crashlytics.
[30] Fitts's law. b.d. Https://en.wikipedia.org/wiki/Fitts%27s_law.
[31] Flyway by Redgate - Database Migrations Made Easy. b.d. Https://github.com/flyway/flyway.
[32] Fowler, Martin. 2004. UML Distilled: A Brief Guide to the Standard Object Modeling Language (3rd Edition). Addison Wesley.
[33] Fowler, Martin. 2019. Refaktoryzacja. Ulepszanie struktury istniejącego kodu. Wydanie II. Helion.
[34] Google Guava. b.d. Https://github.com/google/guava.
[35] Grails - A powerful Groovy-based web application framework. b.d. Https://grails.org/.
[36] Grębosz, Jerzy. 1996. Symfonia C++. Oficyna Kallimach.
[37] Gson Project. b.d. Https://github.com/google/gson.
[38] Guide to Internationalization in Spring Boot. b.d. Https://www.baeldung.com/spring-boot-internationalization.
[39] Guide To Java Optional by Baeldung. b.d. Https://www.baeldung.com/java-optional.
[40] H2 - the Java SQL database. b.d. Https://www.h2database.com/.
[41] Hibernate - Everything data. b.d. Https://hibernate.org/.
[42] Hibernate ORM User Guide. b.d. Https://docs.hibernate.org/orm/current/userguide/html_single/.
[43] HSQLDB - 100% Java Database. b.d. Https://hsqldb.org/.
[44] HTTP Semantics (RFC 9110), section 15: Status Codes. b.d. Https://www.rfc-editor.org/rfc/rfc9110.html.
[45] IBM Db2. b.d. Https://www.ibm.com/db2.
[46] Instabug- AI-Powered Insights. b.d. Https://www.instabug.com/.
[47] IntelliJ IDEA IDE. b.d. Https://www.jetbrains.com/idea/.
[48] Introduction to Javadoc. b.d. Https://www.baeldung.com/javadoc.
[49] Jackson Project. b.d. Https://github.com/FasterXML/jackson.
[50] Java Coding conventions. b.d. Https://cwiki.apache.org/confluence/display/CLOUDSTACK/Coding+conventions.
[51] JPA Query Methods. b.d. Https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html.
[52] JSON Hypertext Application Language (HAL). b.d. Https://datatracker.ietf.org/doc/html/draft-kelly-json-hal-11.
[53] JUnit testing framework. b.d. Https://junit.org/.
[54] Kamil Rybiński, Śmiałek Michał i. 2023. Inżynieria oprogramowania w praktyce. Od wymagań do kodu z językiem UML. 1st wyd. Helion.
[55] Karl Wiegers, Joy Beatty. 2014. Specyfikacja oprogramowania. Inżynieria wymagań. Wydanie III. Helion.
[56] Krug, Steve. 2014. Nie każ mi myśleć! O życiowym podejściu do funkcjonalności stron internetowych. Wydanie III. Helion.
[57] Laws of UX. b.d.-a. Https://lawsofux.com/.
[58] Laws of UX. b.d.-b. Https://www.smashingmagazine.com/.
[59] Lech Banachowski, Krzysztof Matejewski, Agnieszka Chądzynska. 2004. Relacyjne bazy danych. Wykłady i ćwiczenia. Wydawnictwo PJWSTK.
[60] Lucidchart. b.d. Https://www.lucidchart.com/pages/.
[61] Luke Avedon, Garry Cabrera. 2025. ASP.NET Core 9 Web API Cookbook. Packt Publishing.
[62] MapStruct. b.d. Https://mapstruct.org/.
[63] MariaDB Server: the innovative open source database. b.d. Https://mariadb.org/.
[64] Martin, Robert C. 2015. Czysty kod. Podręcznik dobrego programisty. Helion.
[65] Microsoft SQL documentation. b.d. Https://learn.microsoft.com/en-us/sql/.
[66] Milligram - a minimalist CSS framework. b.d. Https://milligram.io/.
[67] Mockito - Tasty mocking framework for unit tests in Java. b.d. Https://site.mockito.org/.
[68] Model Mapper. b.d. Https://modelmapper.org/.
[69] MongoDB: The Developer Data Platform. b.d. Https://www.mongodb.com/.
[70] MS Visual Studio. b.d. Https://visualstudio.microsoft.com/pl/.
[71] New Relic - Monitor, Debug and Improve Your Entire Stack. b.d. Https://newrelic.com/.
[72] NHibernate - open source object-relational mapper for the .NET framework. b.d. Https://nhibernate.info/.
[73] Nielsen, Jakob. b.d. Why You Only Need to Test with 5 Users. Https://www.nngroup.com/articles/why-you-only-need-to-test-with-5-users/.
[74] NN/g - Your source for UX guidance and training. b.d. Https://www.nngroup.com/.
[75] NUnit - a unit-testing framework for all .NET languages. b.d. Https://nunit.org/.
[76] Oracle Database Technologies. b.d. Https://www.oracle.com/database/technologies/.
[77] Pathak, Kailash. 2024. Web Automation Testing Using Playwright. BPB Publications.
[78] PenPot - Design and code beautiful products. b.d. Https://penpot.app/.
[79] Pico - Minimal CSS Framework for Semantic HTML. b.d. Https://picocss.com/.
[80] Playwright enables reliable end-to-end testing for modern web apps. b.d. Https://playwright.dev/.
[81] PostgreSQL: The world's most advanced open source database. b.d. Https://www.postgresql.org/.
[82] Postman is the single platform for designing, building, and scaling APIs. b.d. Https://www.postman.com/.
[83] Projekt Lombok. b.d. Https://projectlombok.org/.
[84] Querydsl - Unified Queries for Java. b.d. Http://querydsl.com/.
[85] React - The library for web and native user interfaces. b.d. Https://react.dev/.
[86] REST Client (wtyczka do VSCode). b.d. Https://marketplace.visualstudio.com/items?itemName=humao.rest-client.
[87] Rider IDE. b.d. Https://www.jetbrains.com/rider/.
[88] Robot Framework. b.d. Https://robotframework.org/.
[89] Ruby on Rails. b.d. Https://rubyonrails.org/.
[90] Securing a Web Application using Spring. b.d. Https://spring.io/guides/gs/securing-web.
[91] Selenium automates browsers. b.d. Https://www.selenium.dev/.
[92] Sentry - Application monitoring software. b.d. Https://sentry.io/.
[93] Simple Logging Facade for Java (SLF4J). b.d. Https://www.slf4j.org/index.html.
[94] Skeleton - a dead simple, responsive boilerplate. b.d. Http://getskeleton.com/.
[95] Smith, Jon P. 2021. Entity Framework Core in Action, Second Edition. Manning.
[96] Spring Boot framework. b.d. Https://spring.io/projects/spring-boot.
[97] Spring Caching. b.d. Https://docs.spring.io/spring-boot/reference/io/caching.html.
[98] Spring framework. b.d. Https://spring.io/.
[99] Spring initializr. b.d. Https://start.spring.io/.
[100] Spring WebFlux. b.d. Https://docs.spring.io/spring-framework/reference/web/webflux.html.
[101] SQL Injection. b.d. Https://www.w3schools.com/sql/sql_injection.asp.
[102] SQLite documentation. b.d. Https://www.sqlite.org/.
[103] Swagger - API Development forEveryone. b.d. Https://swagger.io/.
[104] TestCafe - Modern web testing, simplified. b.d. Https://testcafe.io/.
[105] Testing Spring Boot. b.d. Https://docs.spring.io/spring-boot/reference/testing/index.html.
[106] TestNG - a testing framework. b.d. Https://testng.org/.
[107] The Java Stream API Tutorial. b.d. Https://www.baeldung.com/java-8-streams.
[108] Thunder Client (wtyczka do VSCode). b.d. Https://marketplace.visualstudio.com/items?itemName=rangav.vscode-thunder-client.
[109] Thymeleaf - a modern server-side Java template engine for both web and standalone environments. b.d. Https://www.thymeleaf.org/index.html.
[110] Tibi, Adam. 2025. Pragmatic Test-Driven Development in C# and .NET. Helion.
[111] Tudose, Catalin. 2023. Java Persistence with Spring Data and Hibernate. Manning.
[112] UMLet. b.d. Https://umlet.com/.
[113] UXtweak - Recruit, conduct, analyze, and share UX research. b.d. Https://www.uxtweak.com/.
[114] Validating Form Input using Spring. b.d. Https://spring.io/guides/gs/validating-form-input.
[115] Visual Paradigm. b.d. Https://www.visual-paradigm.com/.
[116] Visual Studio Code IDE. b.d. Https://code.visualstudio.com/.
[117] Vue.js - The Progressive JavaScript Framework. b.d. Https://vuejs.org/.
[118] Wrycza, Stanisław, Bartosz Marcinkowski, i Krzysztof Wyrzykowski. 2005. Język UML 2.0 w modelowaniu systemów informatycznych. Helion.
[119] xUnit.net - community-focused unit testing tool for the .NET Framework. b.d. Https://xunit.net/.
[120] Zarnekow, Ruediger, i Walter Brenner. 2005. "Distribution of Cost over the Application Lifecycle - a Multi-case Study." stycznia, 68-79.

Spis listingów

Listing 3.1: Ilustracja błędu związanego z brakiem nawiasówListing 3.2: Kod klasy w języku JavaListing 3.3: Implementacja zarządzania ekstensją w ramach tej samej klasyListing 3.4: Kod testujący implementację ekstensji w ramach tej samej klasyListing 3.5: Implementacja zarządzania ekstensją w ramach klasy zewnętrznejListing 3.6: Kod testujący implementację ekstensji w ramach klasy zewnętrznejListing 3.7: Przykładowy atrybut prosty dla klasy PublicationListing 3.8: Przykładowy atrybut złożony dla klasy PublicationListing 3.9: Przykładowy atrybut pochodny dla klasy PublicationListing 3.10: Przykład wykorzystania właściwości w języku MS C\#Listing 3.11: Przykładowy kod metody w języku JavaListing 3.12: Przykład wykorzystania przeciążania metodListing 3.13: Przykładowa implementacja "ręcznej" trwałości danych - zapis i odczyt stanu obiektuListing 3.14: Przykładowa implementacja "ręcznej" trwałości danych - zapis i odczyt ekstensjiListing 3.15: Kod testujący zapis oraz odczyt przykładowej ekstensjiListing 3.16: Implementacja interfejsu SerializableListing 3.17: Utrwalenie ekstensji za pomocą serializacjiListing 3.18: Wykorzystanie serializacji do zapisu/odczytu ekstensjiListing 3.19: Implementacja ekstensji klasy przy pomocy ObjectPlusListing 3.20: Alternatywny sposób zapisu konstruktora klasy ObjectPlusListing 3.21: Wykorzystanie klasy ObjectPlusListing 3.22: Wykorzystanie klasy ObjectPlusListing 3.23: Implementacja asocjacji za pomocą identyfikatorówListing 3.24: Przykład ilustrujący wykorzystanie asocjacji z identyfikatoramiListing 3.25: Wykorzystanie pojemnika mapującego w celu przyspieszenia wyszukiwania obiektówListing 3.26: Implementacja asocjacji dla liczności 1 - 1Listing 3.27: Implementacja asocjacji dla liczności 1 - *Listing 3.28: Implementacja asocjacji dla liczności * - *Listing 3.29: Implementacja asocjacji z powiązaniem zwrotnymListing 3.30: Przykład ilustrujący wykorzystanie asocjacji z informacją zwrotnąListing 3.31: Implementacja asocjacji rekurencyjnejListing 3.32: Implementacja asocjacji kwalifikowanejListing 3.33: Implementacja asocjacji kwalifikowanejListing 3.34: Implementacja kompozycji - część 1Listing 3.35: Implementacja kompozycji - część 2Listing 3.36: Tworzenie obiektu klasy wewętrznejListing 3.37: Implementacja kompozycji przy pomocy klas wewnętrznychListing 3.38: Implementacja klasy ObjectPlusPlus - część pierwszaListing 3.39: Implementacja klasy ObjectPlusPlus - część drugaListing 3.40: Implementacja klasy ObjectPlusPlus - część trzeciaListing 3.41: Klasy służące do zaimplementowania sytuacji biznesowej z rysunku \ref{fig:impl-objectplusplus-02}Listing 3.42: Wykorzystanie klas biznesowych do implementacji zadania z rysunku \ref{fig:impl-objectplusplus-02}Listing 3.43: Przykładowa implementacja dziedziczenia rozłącznegoListing 3.44: Kod ilustrujący przesłanianie metodListing 3.45: Kod ilustrujący polimorficzne wołanie metodListing 3.46: Przesłanianie metod na przykładzie systemowej metody toString()Listing 3.47: Implementacja dziedziczenia overlapping za pomocą grupowaniaListing 3.48: Implementacja dziedziczenia overlapping za pomocą grupowania - metodyListing 3.49: Implementacja dziedziczenia overlapping za pomocą kompozycjiListing 3.50: Kod przykładowej metody prawidłowo zwracającej dochody osób w przypadku dziedziczenia overlappingListing 3.51: Implementacja wielodziedziczenia za pomocą interfejsówListing 3.52: Implementacja propagacji operacji w ramach wykorzystania interfejsówListing 3.53: Implementacja "sprytnego" kopiowania obiektów jako sposobu obejścia dziedziczenia dynamicznegoListing 3.54: Implementacja "sprytnego" kopiowania obiektów jako sposobu obejścia dziedziczenia dynamicznegoListing 3.55: Modyfikacja konstruktora klasy ObjectPlus uwzględniająca zaiweranie się ekstensji na skutek dziedziczeniaListing 3.56: Sprawdzenie zarządzania ekstensjami nadklas w ObjectPlusListing 3.57: Implementacja ograniczenia dla atrybutuListing 3.58: Implementacja metody isLink w ObjectPlusPlusListing 3.59: Implementacja ograniczenia \{subset\} w ObjectPlus4 \{subset\}Listing 3.60: Wykorzystanie ograniczenia \{ordered\} - sposób automatycznyListing 3.61: Wykorzystanie ograniczenia \{ordered\} - sposób ręcznyListing 3.62: Implementacja ograniczenia \{xor\} dla klasy ObjectPlus4Listing 4.1: Przykład mapowania atrybutu złożonego na format XMLListing 4.2: Kod SQL tworzący dane testoweListing 4.3: Przykład wykorzystania JDBCListing 4.4: Przykładowy plik mapujący stosowany w HibernateListing 4.5: Przykładowy plik konfiguracyjny stosowany w HibernateListing 4.6: Przykładowa klasa POJO służąca do przechowywania informacji o publikacjiListing 4.7: Przykładowa klasa Hibernate służąca do przechowywania informacji o publikacjiListing 4.8: Przykładowa klasa Hibernate ilustrująca zastosowanie identyfikatoraListing 4.9: Przykładowa klasa Hibernate ilustrująca zastosowanie adnotacji @BasicListing 4.10: Przykładowa klasa Hibernate ilustrująca zastosowanie adnotacji transientListing 4.11: Przykładowa klasa Hibernate ilustrująca mapowanie typu enumListing 4.12: Przykładowa klasa Hibernate służacą do zdefiniowania atrybutu złożonegoListing 4.13: Przykładowa klasa Hibernate z atrybutem złożonymListing 4.14: Fragment klasy Author z atrybutem powtarzalnymListing 4.15: Przykład użycia atrybutu powtarzalnegoListing 4.16: Przykładowy kod do pracy z HibernateListing 4.17: Przykładowy kod do pracy z HibernateListing 4.18: Pobieranie obiektów z bazy danych (zapytanie HQL)Listing 4.19: Metoda pobierająca obiekty podanego typu (zapytanie Criteria API)Listing 4.20: Pobieranie obiektów z bazy danych (zapytanie Criteria API)Listing 4.21: Pobieranie pojedynczego obiektu z bazy danych w HibernateListing 4.22: Klasa Publication ze wsparciem dla asocjacji skierowanejListing 4.23: Fragment klasy Publication ze wsparciem dla asocjacji dwukierunkowejListing 4.24: Fragment klasy Author ze wsparciem dla asocjacji dwukierunkowejListing 4.25: Użycie asocjacji dwukierunkowej w HibernateListing 4.26: Nadklasa Publication korzystająca ze strategii SINGLE\_TABLEListing 4.27: Podklasy dziedziczące z Publication korzystające ze strategii SINGLE\_TABLEListing 4.28: Użycie dziedziczenia korzystającego ze strategii SINGLE\_TABLEListing 4.29: Kody mapowanych klas dla strategii TABLE\_PER\_CLASSListing 4.30: Zapytanie SQL wygenerowane przez Hibernate przy użyciu strategii TABLE\_PER\_CLASS (wszystkie podklasy)Listing 4.31: Zapytanie SQL wygenerowane przez Hibernate przy użyciu strategii TABLE\_PER\_CLASS (konkretna podklasa)Listing 4.32: Kody mapowanych klas dla strategii JOINEDListing 4.33: Zapytanie SQL wygenerowane przez Hibernate przy użyciu strategii JOINED (wszystkie podklasy)Listing 4.34: Zapytanie SQL wygenerowane przez Hibernate przy użyciu strategii JOINED (konkretna podklasa)Listing 7.1: Kod inicjalizujący loggerListing 7.2: Kod konfiguracyjny dla biblioteki Lombok (build.gradle)Listing 7.3: Prosta klasa ilustrująca użycie adnotacji LombokListing 7.4: Kod ilustrujący zastosowanie klasy korzystającej z LombokListing 7.5: Biblioteki wymagane przez Spring RESTful Web ServiceListing 7.6: Główna klasa aplikacji Spring uruchamiająca serwerListing 7.7: Klasa opisująca autoraListing 7.8: Klasa opisująca autora (wersja z Lombok)Listing 7.9: Repozytorium dla obiektów klasy AuthorListing 7.10: Kod generujący dane przykładowe w aplikacji SpringListing 7.11: Niezalecane stosowanie adnotacji @Query w repozytorium JPAListing 7.12: Repozytorium Spring z dodaną metodąListing 7.13: Kod prostego kontrolera aplikacji SpringListing 7.14: Kod kontrolera aplikacji Spring dla klasy AuthorListing 7.15: Kod serwisu aplikacji SpringListing 7.16: Kod repozytorium aplikacji SpringListing 7.17: Fragment implementacji generycznego serwisu SpringListing 7.18: Fragment pliku "build.gradle" dodający obsługę biblioteki ModelMapperListing 7.19: Kod konfigurujący komponent ModelMapperListing 7.20: Fragment implementacji generycznego serwisu Spring - pobieranie danychListing 7.21: Fragment implementacji generycznego serwisu Spring - tworzenie i usuwanie obiektuListing 7.22: Fragment implementacji generycznego serwisu Spring - aktualizacja obiektuListing 7.23: Fragment implementacji generycznego kontrolera SpringListing 7.24: Fragment implementacji generycznego kontrolera - GETListing 7.25: Fragment implementacji generycznego kontrolera - POST i DELETEListing 7.26: Fragment implementacji generycznego kontrolera - PUT i PATCHListing 7.27: Klasa DTO kompatybilna z modelem AuthorListing 7.28: Klasa DTO zrealizowana jako recordListing 7.29: Kod serwera API korzystający z generycznego kontrolera i serwisuListing 7.30: Rozszerzenie serwisu o metodę wyszukującąListing 7.31: Rozszerzenie serwisu o metodę wyszukującąListing 7.32: Wpis w pliku "build.gradle" do pracy ze Swagger'emListing 7.33: Prosty kontroler obsługujący widokiListing 7.34: Prosty widok kompatybilny z ThymeleafListing 7.35: Główny szablon układu stronyListing 7.36: Nagłówek strony do wstawienia do głównego szablonuListing 7.37: Stopka strony do wstawienia do głównego szablonuListing 7.38: Strona z merytoryczną zawartością (treścią) z odwołaniem do szablonu układuListing 7.39: Finalny kod strony po przekształceniach przez silnik szablonówListing 7.40: Kontroler do obsługi strony startowejListing 7.41: Dedykowany kontroler do obsługi błędówListing 7.42: Kod widoku do wyświetlenia informacji o błędzieListing 7.43: Klasa modelu danych AuthorListing 7.44: Klasa modelu danych PublicationListing 7.45: Kod serwisu generującego dane przykładoweListing 7.46: Wywołanie serwisu generującego dane testoweListing 7.47: Konfiguracja ModelMapper do pracy z klasami Author i PublicationListing 7.48: Kod interfejsu IdAndLabelDtoListing 7.49: Kod klasy AuthorDTO z kolekcją IdAndLabelDtoListing 7.50: Kod klasy PublicationDTO z kolekcją IdAndLabelDtoListing 7.51: Poprawny kod klasy konfigurującej ModelMapperListing 7.52: Kod usługi dla klasy AuthorListing 7.53: Fragment dedykowanego kontrolera widoku dla klasy Author - cz. 1Listing 7.54: Kod widoku z listą autorówListing 7.55: Kod widoku z informacjami o wybranym autorzeListing 7.56: Fragment dedykowanego kontrolera widoku dla klasy Author - cz. 2Listing 7.57: Kod widoku tworzenia nowego autoraListing 7.58: Fragment dedykowanego kontrolera widoku dla klasy Author - cz. 3Listing 7.59: Problematyczny fragment dedykowanego kontrolera widoku dla klasy AuthorListing 7.60: Kod generycznego kontrolera pracującego z widokamiListing 7.61: Kod interfejsu IGenericDtoListing 7.62: Kontroler AuthorController2 korzystający z generycznej nadklasyListing 8.1: Klasa przeprowadzająca testy integracyjne kontroleraListing 8.2: Klasa przeprowadzająca testy integracyjne serwisuListing 8.3: Klasa przeprowadzająca testy integracyjne repozytoriumListing 8.4: Wpisy w pliku "build.gradle" do integracji z SeleniumListing 8.5: Nadklasa na potrzeby testów E2E SeleniumListing 8.6: Klasa realizująca test E2E (Selenium)

Spis rysunków

Rysunek 2.1: Typowe fazy wytwarzania oprogramowania.Rysunek 2.2: Przykładowy diagram przypadków użycia służący jako ilustracja notacji.Rysunek 2.3: Diagram przypadków użycia dla biblioteki.Rysunek 2.4: Diagram dziedziczenia dla aktorów.Rysunek 2.5: Przykładowy, uszczegółowiony diagram przypadków użycia.Rysunek 2.6: Notacja dla klas.Rysunek 2.7: Notacja do oznaczania atrybutów.Rysunek 2.8: Notacja do oznaczania metod.Rysunek 2.9: Przykład ilustrujący wykorzystanie asocjacji.Rysunek 2.10: Przykład ilustrujący wykorzystanie asocjacji. W stosunku do rysunku 2.9 zmieniono nazwę oraz jej kierunek czytaniaRysunek 2.11: Ilustracja wykorzystania ról asocjacjiRysunek 2.12: Liczności asocjacjiRysunek 2.13: Redukcja liczności dla asocjacjiRysunek 2.14: Przykład asocjacji skierowanejRysunek 2.15: Przykładowa asocjacja n-arnaRysunek 2.16: Ilustracja problemu z asocjacją n-arnąRysunek 2.17: Asocjacja binarna (a) oraz kwalifikowana (b)Rysunek 2.18: Przykładowa asocjacja rekurencyjna (zwrotna)Rysunek 2.19: Przykładowy diagram obiektów dla asocjacji rekurencyjnej z rysunku 2.18Rysunek 2.20: Zastosowanie klasy asocjacjiRysunek 2.21: Diagram obiektów ilustrujący wykorzystanie klasy asocjacjiRysunek 2.22: Przykładowa agregacjaRysunek 2.23: Przykładowa kompozycjaRysunek 2.24: Przykładowa hierarchia dziedziczeniaRysunek 2.25: Ilustracja wykorzystania klasy abstrakcyjnejRysunek 2.26: Hierarchia dziedziczenia z dodanymi metodamiRysunek 2.27: Hierarchia dziedziczenia z dodanymi metodami zwracającymi dochodyRysunek 2.28: Przykładowa hierarchia wielodziedziczeniaRysunek 2.29: Dziedziczenie typu overlappingRysunek 2.30: Przykładowe dziedziczenie wieloaspektoweRysunek 2.31: Przykład dziedziczenia dynamicznegoRysunek 2.32: Ilustracja ograniczenia subsetRysunek 2.33: Ilustracja ograniczenia {ordered}Rysunek 2.34: Ilustracja ograniczenia {bag}Rysunek 2.35: Ilustracja ograniczenia {xor}Rysunek 2.36: Tworzenie diagramu klas dla biblioteki - krok 1Rysunek 2.37: Tworzenie diagramu klas dla biblioteki - krok 2Rysunek 2.38: Tworzenie diagramu klas dla biblioteki - krok 3Rysunek 2.39: Tworzenie diagramu klas dla biblioteki - krok 4Rysunek 2.40: Tworzenie diagramu klas dla biblioteki - krok 5Rysunek 2.41: Tworzenie diagramu klas dla biblioteki - krok 6Rysunek 2.42: Tworzenie diagramu klas dla biblioteki - krok 7Rysunek 2.43: Tworzenie diagramu klas dla biblioteki - krok 8Rysunek 2.44: Tworzenie diagramu klas dla biblioteki - krok 9Rysunek 2.45: Tworzenie diagramu klas dla biblioteki - krok 10Rysunek 2.46: Tworzenie diagramu klas dla biblioteki - krok 11Rysunek 2.47: Tworzenie diagramu klas dla biblioteki - krok 12Rysunek 2.48: Tworzenie diagramu klas dla biblioteki - krok 13Rysunek 2.49: Tworzenie diagramu klas dla biblioteki - krok 14Rysunek 2.50: Tworzenie diagramu klas dla biblioteki - krok 15Rysunek 2.51: Tworzenie diagramu klas dla biblioteki - krok 16Rysunek 2.52: Ilustracja możliwości przechowywania informacji o rodzaju (np. samolotu)Rysunek 2.53: Tworzenie diagramu klas dla biblioteki - krok 17Rysunek 2.54: Tworzenie diagramu klas dla biblioteki - krok 18Rysunek 2.55: Tworzenie diagramu klas dla biblioteki - krok 19Rysunek 2.56: Tworzenie diagramu klas dla biblioteki - krok 20Rysunek 2.57: Tworzenie diagramu klas dla biblioteki - krok 21Rysunek 2.58: Tworzenie diagramu klas dla biblioteki - krok 22Rysunek 2.59: Tworzenie diagramu klas dla biblioteki - krok 23Rysunek 2.60: Tworzenie diagramu klas dla biblioteki - krok 24Rysunek 2.61: Tworzenie diagramu klas dla biblioteki - krok 25Rysunek 2.62: Tworzenie diagramu klas dla biblioteki - krok 26Rysunek 2.63: Tworzenie diagramu klas dla biblioteki - krok 27Rysunek 2.64: Tworzenie diagramu klas dla biblioteki - krok 28Rysunek 2.65: Tworzenie diagramu klas dla biblioteki - krok 29Rysunek 2.66: Tworzenie diagramu klas dla biblioteki - krok 30Rysunek 2.67: Tworzenie diagramu klas dla biblioteki - krok 31Rysunek 2.68: Tworzenie diagramu klas dla biblioteki - krok 32Rysunek 2.69: Tworzenie diagramu klas dla biblioteki - krok 33Rysunek 2.70: Diagram klas dla biblioteki - wersja finalnaRysunek 2.71: Diagram aktywności przedstawiający dodawanie nowego klientaRysunek 2.72: Diagram aktywności przedstawiający wypożyczenie publikacji (egzemplarza)Rysunek 2.73: Przykładowy diagram stanów dla obiektów klasy EgzemplarzRysunek 3.1: Przykład ilustrujący powiązania pomiędzy obiektami.Rysunek 3.2: Wykorzystanie kontenera mapującego do przechowania wielu ekstensji.Rysunek 3.3: Wykorzystanie identyfikatorów do implementacji asocjacji.Rysunek 3.4: Przykład asocjacji skierowanejRysunek 3.5: Przykładowy diagram klas pokazujący zastosowanie asocjacji rekurencyjnej (zwrotnej)Rysunek 3.6: Przekształcenie asocjacji z atrybutemRysunek 3.7: Implementacja asocjacji z atrybutem - typy danychRysunek 3.8: Asocjacja kwalifikowanaRysunek 3.9: Zamiana asocjacji n-arnej na asocjacje binarneRysunek 3.10: ilustracja wyjaśniająca sposób przechowywania informacji w klasie ObjectPlusPlusRysunek 3.11: Przykład ilustrujący powiązania pomiędzy obiektami.Rysunek 3.12: Obejście dziedziczenia overlapping za pomocą kompozycjiRysunek 3.13: Ilustracja problemu związanego z wywoływaniem metod w przypadku dziedziczenia typu overlappingRysunek 3.14: Diagram ilustrujący dziedziczenie niekompletneRysunek 3.15: Przekształcenie wielodziedziczenia z wykorzystaniem interfejsówRysunek 3.16: Delegowanie funkcjonalności do innej klasyRysunek 3.17: Obejście problemu dziedziczenia wieloaspektowego z pomocą grupowania atrybutówRysunek 3.18: Obejście problemu dziedziczenia wieloaspektowego z pomocą kompozycjiRysunek 3.19: Obejście dziedziczenia dynamicznego za pomocą kompozycjiRysunek 3.20: Ilustracja dla potrzeb "zadania domowego"Rysunek 3.21: Hierarchia klas (a) oraz zawieranie się jej ekstensji (b)Rysunek 3.22: Generowanie metod w środowisku IntelliJ IDEARysunek 3.23: Ograniczenie typu {subset}Rysunek 3.24: Ograniczenie typu {bag} w asocjacji z atrybutemRysunek 3.25: Efekt przekształcenia asocjacji z atrybutem pokazanej na rysunku 3.24Rysunek 3.26: Ilustracja ograniczenia {xor}Rysunek 4.1: Przykład osadzenia składników atrybutu złożonego bezpośrednio w tabeliRysunek 4.2: Przykład mapowania atrybutu złożonego za pomocą oddzielnej tabeliRysunek 4.3: Asocjacja opisująca zależność pomiędzy pracownikiem a grupąRysunek 4.4: Przykładowy model relacyjny dla klas z rysunku 4.3Rysunek 4.5: Zmodyfikowana wersja diagramu z rysunku 4.3Rysunek 4.6: Diagram przedstawiający efekt zastąpienia asocjacji "* - *"Rysunek 4.7: Przykładowy model relacyjny dla klas z rysunku 4.6Rysunek 4.8: Asocjacja z atrybutemRysunek 4.9: Wprowadzenie klasy pośredniczącejRysunek 4.10: Przykładowy model relacyjny dla diagramu klas z rysunku 4.9Rysunek 4.11: Przykładowa asocjacja kwalifikowanaRysunek 4.12: Przykładowe przedstawienie asocjacji kwalifikowanej w modelu relacyjnymRysunek 4.13: Przykładowa asocjacja n-arnaRysunek 4.14: Asocjacja n-arna po przekształceniuRysunek 4.15: Przykładowy model relacyjny dla asocjacji n-arnejRysunek 4.16: Trzy różne sposoby obejścia dziedziczeniaRysunek 4.17: Zmodyfikowany schemat relacyjny umożliwiający zapamiętywanie atrybutów powtarzalnychRysunek 4.18: Schemat danych wygenerowany przez Hibernate dla asocjacji skierowanejRysunek 4.19: Schemat wygenerowany przez Hibernate dla strategii TABLE_PER_CLASSRysunek 4.20: Schemat wygenerowany przez Hibernate dla strategii JOINEDRysunek 5.1: Tworzenie projektowego diagramu klas - krok 1Rysunek 5.2: Tworzenie projektowego diagramu klas - krok 2Rysunek 5.3: Tworzenie projektowego diagramu klas - krok 3Rysunek 5.4: Tworzenie projektowego diagramu klas - krok 4Rysunek 5.5: Tworzenie projektowego diagramu klas - krok 5Rysunek 5.6: Tworzenie projektowego diagramu klas - krok 6Rysunek 5.7: Tworzenie projektowego diagramu klas - krok 7Rysunek 5.8: Tworzenie projektowego diagramu klas - krok 8Rysunek 5.9: Tworzenie projektowego diagramu klas - krok 9Rysunek 5.10: Projektowy diagramu klas dla biblioteki (nazwy asocjacji)Rysunek 5.11: Projektowy diagramu klas dla biblioteki (role asocjacji)Rysunek 5.12: Diagram sekwencji ilustrujący dodawanie nowego klientaRysunek 5.13: Projekt formularza umożliwiającego wypożyczanie publikacjiRysunek 5.14: Projekt okna umożliwiającego wyświetlenie informacji o publikacjiRysunek 6.1: Zależność pomiędzy liczbą testerów a zidentyfikowanymi problemami. Źródło: [73]Rysunek 7.1: Spring initializr w czasie pracyRysunek 7.2: Podgląd plików projektu w Spring initializrRysunek 7.3: Konsola bazy danych H2Rysunek 7.4: Różne sposoby podziału odpowiedzialności w serwerze APIRysunek 7.5: Domyślny widok w SwaggerRysunek 7.6: Widok dostępnych operacji kontrolera w SwaggerRysunek 7.7: Widok konkretnej operacji (DELETE) kontrolera w SwaggerRysunek 7.8: Uproszczona architektura MVCRysunek 7.9: Prosta strona internetowa wygenerowana zgodnie z widokiem (bez parametru)Rysunek 7.10: Prosta strona internetowa wygenerowana zgodnie z widokiem (z parametrem)Rysunek 7.11: Uproszczony diagram sekwencji ilustrujący przetwarzanie żądania HTTP przez kontrolerRysunek 7.12: Przykładowy układ szablonów widoku w ThymeleafRysunek 7.13: Strona internetowa po konsolidacjiRysunek 7.14: Strona internetowa po konsolidacji i zastosowaniu CSSRysunek 7.15: Główna strona aplikacjiRysunek 7.16: Domyślny widok z komunikatem błędu 404Rysunek 7.17: Dedykowany widok z komunikatem błęduRysunek 7.18: Widok z listą autorówRysunek 7.19: Widok z informacjami o wybranym autorzeRysunek 7.20: Widok tworzenia nowego autoraRysunek 8.1: Okno IDE umożliwiające uruchomienie testu kontrolera (IntelliJ Idea)

Spis uwag

Uwaga 2.1: Metody na diagramie klas?Uwaga 2.2: Nazwy asocjacji, a roleUwaga 2.3: Diagram obiektów, a diagram klasUwaga 2.4: Stosowanie klasy asocjacjiUwaga 2.5: Stuprocentowa kompozycjaUwaga 2.6: Wielodziedziczenie, a wspólna nadklasaUwaga 2.7: Po co nam mechanizmy rozszerzalności?Uwaga 2.8: Kierunek strzałki w ograniczeniu {subset}Uwaga 2.9: Jak przechować informacje o rodzaju czegoś?Uwaga 2.10: Ograniczenie {incomplete}, a ellipsisUwaga 2.11: Kolejność wartości oraz powiązańUwaga 2.12: Gdy brak pomysłu na nazwę asocjacjiUwaga 2.13: Czytelność diagramów (klas)Uwaga 2.14: Decyzje na diagramach aktywnościUwaga 2.15: Trwałość stanówUwaga 3.1: Nazwy dla leniwychUwaga 3.2: API (ang. Application Programming Interface)Uwaga 3.3: RefaktoringUwaga 3.4: Brakująca ekstensja klasy?Uwaga 3.5: Pakiety w JavaUwaga 3.6: Enkapsulacja i hermetyzacjaUwaga 3.7: Automatyzacja w programowaniuUwaga 3.8: Automatyczne wnioskowanie o typeUwaga 3.9: Semantyka, a implementacja atrybutów prostych i złożonychUwaga 3.10: Jak pojemnik/kontener wybrać?Uwaga 3.11: Metoda obiektu, a klasowaUwaga 3.12: Typ danych dla walutUwaga 3.13: Po co nam klasa ObjectPlus?Uwaga 3.14: Refleksja (ang. reflection)Uwaga 3.15: Łączenie stringów tekstowychUwaga 3.16: Systemy wbudowane (ang. embedded systems)Uwaga 3.17: Zarządzanie pamięcią w JavaUwaga 3.18: Referencja do nadkalsy/podklasyUwaga 3.19: Rozróżnianie elementów (enum i stałe)Uwaga 3.20: Interfejsy programistyczneUwaga 4.1: Dlaczego bazy relacyjne?Uwaga 4.2: Klucze w relacyjnych bazach danychUwaga 4.3: Prespektywy w bazach danychUwaga 4.4: Więzy integralności i wyzwalaczeUwaga 4.5: Parametry w zapytaniach SQLUwaga 4.6: Czy zawsze używać maperów obiektowo-relacyjnych (ORM)?Uwaga 4.7: Adnotacje w językach programowaniaUwaga 4.8: Repozytoria MavenUwaga 4.9: Lokalizacja adnotacji HibernateUwaga 4.10: Strategia pobierania połączonych obiektów w HibernateUwaga 5.1: Metody na projektowym diagramie klasUwaga 5.2: Atrybuty złożone na diagramie klasUwaga 5.3: Czy powinniśmy korzystać z diagramów sekwencji?Uwaga 5.4: Czy warto samemu robić projekt GUI?Uwaga 7.1: Czy używać asystentów AI?Uwaga 7.2: REST APIUwaga 7.3: HAL (Hypertext Application Language)Uwaga 7.4: Kody statusów HTTPUwaga 7.5: SQL w repozytoriach JPAUwaga 7.6: Obiekty transferowe (DTO)Uwaga 7.7: Widoki 'edycja' oraz 'nowy'Uwaga 7.8: Konwencja nad konfiguracjąUwaga 7.9: Rozwiązania generyczne/uniwersalneUwaga 8.1: Ciągła integracja i Ciągłe dostarczanieUwaga 8.2: Identyfikacja elementów GUI w testach E2EUwaga 8.3: Podejście 'vibe coding'Uwaga 8.4: Dług technologiczny