Tag Archive : dziedziczenie

Wprowadzenie

Tym razem krótka recenzja pewnej książki z roku 2006, a raczej jej polecenie każdemu projektantowi i architektowi dzisiaj. Na końcu polecam także kolejną nowszą pozycję jako uzupełnienie. Adresatami tej recenzji są głównie analitycy i projektanci, jednak adresuję ten wpis także do developerów, zakładam że dla nich nie jest to “coś nowego”, ale być może mają jakieś rady dla projektantów.

Warto także podkreślić, że pomiędzy OOP a OOAD jest coraz większa różnica i podział na role: analiza i projektowanie oraz implementacja, a także postępująca separacja tych ról, stają się standardem w inżynierii oprogramowania [zotpressInText item=”{5085975:NZCQDD79}”]:

Programming is not solely about constructing software, programming is about designing software.

[zotpressInText item=”{5085975:NZCQDD79}”]

Kolejna warta zwrócenia uwagi rzecz: projektowanie integracji systemów w organizacji to nic innego model systemu zorientowany na interfejsy (patrz wcześniejszy wpis: Integracja systemów ERP jako źródło przewagi rynkowe).

Zanim jednak wczytacie się Państwo z detale, polecam krótki referat Martina Fowlera z 2015 roku:

Making Architecture Matter – Martin Fowler Keynote

Recenzja

Interface-Ofriented Design [zotpressInText item=”{5085975:DQ85BDLN}”], to książka o architekturze i jej projektowaniu. We wstępie autor pisze (oryg):

[zotpressInText item=”{5085975:DQ85BDLN}”]

Autor, na wielu przykładach pokazuje jak można projektować (tworzyć) oprogramowanie budując je ze współpracujących komponentów.

Wiele się mówi o programowaniu obiektowym a mało o projektowaniu zorientowanym na komponenty (moduły). Ważna rzecz: projektowanie zorientowane na interfejsy koncentruje się na komponentach i ich interfejsach, komponenty te mogą, ale nie muszą, być implementowane za pomocą języków obiektowych. Projekty, które skupiają się na komponentach i ich interfejsach mają ogromną zaletę: precyzyjnie opisują co ma program robić a jednocześnie nie narzucają narzędzia implementacji.

Kolejna rzecz to fakt, że obecnie znacznie bardziej niż w czasach gdy książka była pisana (ukazała się w 2006 roku), nabiera znaczenia rozproszona architektura: realnie nie ma już monolitów, mamy integracje systemów między partnerami handlowymi, z rejestrami publicznymi, między lokalnymi i chmurowymi infrastrukturami. Rozproszone przetwarzanie to architektury zorientowane na usługi, które kładą szczególny nacisk na interfejsy.

Interfejsy mogą być zorientowane na procedury (takie jak zdalne wywołania funkcji) lub na dokumenty (serwisy internetowe i integracje z protokołem RESTful API). Autor opisuje także wzorce i metody projektowania bazujące na luźnych powiązaniach, które są kluczowe dla budowania systemów rozproszonych. Więcej o interfejsach zorientowanych na dokumenty w moich projektach,

Ważna rzecz, na którą autor także zwraca szczególną uwagę: dziedziczenie jest często trudną do osiągnięcia techniką, jest to także często jedna z najbardziej nadużywanych cech języków obiektowych. Nacisk autora na interfejsy może wydawać się nieco ekstremalny, jednak (świeże w 2006 roku) spojrzenie na projektowanie architektury zorientowane na komponenty i ich odpowiedzialność, daje zaskakująco dobre efekty, a pamiętajmy, że generalnie liczy się cykl życia aplikacji (patrz także artykuł Znaczenie na cykl życia…) czyli koszty jej utrzymania i rozwoju, a nie samo jej wytworzenie. Warto pamiętać, że dziedziczenie z zasady łamie zasadę hermetyzacji i zalecane jest zastępowane go stosowaniem szablonów lub po prostu rezygnuje się z tej techniki na rzecz typów danych i kompozycji.

Podsumowując: Kluczowym przesłaniem autora jest “odejście” od “programowania obiektowego” (orientacja kodu na dziedziczenie oraz łączenie funkcji i danych w obiekty, OOP) na rzecz “projektowania zorientowanego na niezależne, luźno powiązane komponenty” (polimorfizm i hermetyzacja), cechującego się pełną separacją komponentów, luźnymi powiązaniami (tylko wywołania operacji) i pojedynczą odpowiedzialnością (OOAD).

Autor zwraca uwagę na sprawdzone wzorce, kluczowe: to fabryka (zwana także metodą wytwórczą, jest to separacja metod tworzenia obiektów od ich utrwalania), adapter (separacja współpracujących komponentów o niedopasowanych interfejsach). Co do zasady też (wzorce) separujemy przetwarzanie danych od ich samych (wzorzec repozytorium [zotpressInText item=”{5085975:ZIIDU6A9},{5085975:NVN9AR49},{5085975:ZBEHPADF},{5085975:8P6M5STE}”]). Dzisiaj dominujące są więc mikro serwisy i mikro aplikacje, natomiast łączenie danych i metod je przetwarzających w jednej klasie to antywzorzec.

Początek lat 2000 to nie tylko manifest Agile, to także już kilka lat po nim, nawoływanie sygnatariuszy Agile do porządku w inżynierii oprogramowania [zotpressInText item=”{5085975:P5PE3C3R}”]. Poza omawianą tu książką pojawiły się, w tamtym okresie, między innymi opis budowy komponentowej architektury [zotpressInText item=”{5085975:NSJBENX9}”], opis projektowania zorientowanego na odpowiedzialność komponentów [zotpressInText item=”{5085975:VNQDV6CQ}”].

Autor zwraca także szczególną uwagę na dokumentowe modele budowy interfejsów i integracji. Dokumentowe czyli zorientowane na przekazywanie między komponentami całych agregatów danych (zwanych dokumentami) zamiast wyników działania poszczególnych funkcji. Znakomicie upraszcza to architekturę, powoduje mniejsze uzależnienia, w konsekwencji cykl życia takiego systemu jest znacznie tańszy. O tym aspekcie architektury integracji pisał także znany autor Martin Fowfer [zotpressInText item=”{5085975:3BRMMXGI}”].

Zachęcam do lektury tej książki, porządkuje wiedzę, być może wielu z Was znajdzie coś nowego. Od siebie powiem, że podejście takie stosuję od czasów lektury Systemów zorientowanych komponentowe Souzy, czyli od ponad 15 lat…

A architekturze

Doskonałym i aktualnym uzupełnieniem tej książki jest napisana później Czysta architektura [zotpressInText item=”{5085975:QEGGUWKX}”]:

Dobrzy architekci ostrożnie oddzielają szczegóły od reguł biznesowych, robiąc to tak dokładnie, że reguły te nie mają żadnej wiedzy o szczegółach i w żaden sposób od nich nie zależą. Dobrzy architekci tak projektują reguły systemu, żeby wszystkie decyzje dotyczące szczegółów można było podejmować tak późno, jak to tylko możliwe.

Przy tej okazji polecam także prezentację opartą na treści książki [zotpressInText item=”{5085975:IWBHW8XL}”], szczególnie część o interfejsach, głębokości i płytkości klas: A Philosophy of Software Design | John Ousterhout | Talks at Google

A Philosophy of Software Design | John Ousterhout | Talks at Google

OOP i OOAD czyli co dalej?

[2023-01-07]

Od wielu lat obserwuję rynek i projekty z zakresu inżynierii oprogramowania. Pojęcia OOP (programowanie obiektowe) oraz OOAD (analiza obiektowa i projektowanie) oddalają się od siebie. Techniki organizacji kodu rodem z C++/Java (mają współny rodowód) zdeterminowały pojmowanie pojęcia “programowania obiektowego”. Są to ciężkie metody pracy, oparte na starych wodospadowych założeniach (monolityczna architektura oparta na relacyjnym modelu danych) sprawdzają się tam, gdzie zmienność wymagań jest mała.

C++ znajduje zastosowanie w tworzeniu systemów operacyjnych (np. Windows XP czy Vista), a także podczas budowy aplikacji desktopowych (pakietu Office bądź produktów Adobe). Z wykorzystaniem C++ można spotkać się także podczas budowy baz danych oraz serwerów. Popularność C++ zdecydowanie nie słabnie wśród twórców gier. Sprawdza się świetnie nie tylko podczas produkcji prostych projektów 2D, ale także gier typu AAA.

Język Java stosuje się przede wszystkim w backendowej części budowy internetowych aplikacji. Wykorzystuje się go także w projektowaniu aplikacji desktopowych, korporacyjnych, a nawet całych serwerów aplikacji. Język Java stanowi podstawę działania aplikacji mobilnych oraz gier dla systemu Android. Java stosowana jest także w systemach bankowych i giełdowych.

źr.: https://work4.dev/blog/28/Czy-C-i-Java-sa-do-siebie-podobne.html

Systemy określane jako “biznesowe” to zupełnie inna klasa oprogramowania, to aplikacje budowane w oparciu o reguły biznesowe i odrębne dokumenty. Jedne i drugie szybko sią zmieniają, stosowaniu tu metod i narzędzi adekwatnych do budowy gier i systemów operacyjnych (one się zmieniają rzadko i nie służą do zarządzania danymi) prowadzi to do powstawania bardzo kosztownych w utrzymaniu i rozwoju systemów. Dlatego równolegle rozwijają się takie języki jak JavaScript, Ruby, PHP, Pyton, HTML czy Perl.

Analiza i projektowanie “obiektowe”, pierwotnie oparte na idei hermetyzacji, luźnych powiązaniach i interfejsach, tak na prawdę sprowadza się do poprzedzania kodowania projektowaniem architektury, a to “tylko” komponenty i ich współpraca, bez wnikania w technologie ich powstania, tym bardziej, że wiele z nich (komponenty) można kupić jako COTS (ang. Commercial off-the-shelf, gotowe komponenty z półki) bez wiedzy o ich wewnętrznej strukturze (czyli hermetyzacja).

Developerzy często posługują sie pojęciem klasy mając na myśli konstrukcje znane im z C++ czy Java. Poniekąd słusznie, bo faktycznie ich używają tworząc implementacją (pisząc kod). Na etapie analiz i projektowania detale kodu nie mają znaczenia, liczy się realizowana logika i architektura.

A gdzie tu UML? Mityczne generowanie kodu z UML nie wyszło poza mury akademickich entuzjastów tego pomysłu. Więc gdzie? Po oczyszczeniu z nadmiaru (redukcja UML), jest to doskonałe narzędzie do modelowania systemów i tworzenia sformalizowanych schematów blokowych. Czym jest klasa w UML? Wszystkim, a to co jest klasą w C++/Java to malutka część tego “wszystkiego”. Czy na etapie projektowania (model PIM) mamy na myśli klasy w rozumieniu konstrukcji kodu C++/Java? Nie, na tym etapie mamy komponenty (ale w UML wszystko jest klasą, komponent też), w zasadzie czarne skrzynki z interfejsami, które trzeba (wystarczy) opisać. To co opisze projektant zależy od niego: tam gdzie uzna, że daje swobodę deweloperowi poprzestanie na komponentach, ich interfejsach i procedurach (algorytmach) realizowanych przez te komponenty. Tam gdzie uzna, że to ważne, narzuca wybrane szczegóły (patrz Kto jest programistą).

Dlatego od bardzo dawna (patrz opisywana wyżej książka) mówi się i pisze, że projektowanie systemów to właśnie projektowanie zorientowane na komponenty i ich interfejsy. Implementacja jest zawsze wtórna. A to co można nadal spotkać w wielu podręcznikach i analizach pod nazwą “diagram klas”, to często poprawne i zarazem bezwartościowe diagramy w UML (Patrz UML dla programistów Java).

Na zakończenie ciekawa prezentacja: najpierw projektowanie, kod na końcu.

Design First APIs and Domain-Driven Design – Ljubica Lazarevic – DDD Europe 2022

Źródła

[zotpressInTextBib style=”apa” sortby=”author” sort=”ASC” cite=”yes”]

W dwóch ubiegłorocznych artykułach pisałem o modelach pojęciowych oraz o związkach w UML. Opisałem je od strony notacyjnej. Dzisiaj o ich zastosowaniu. 

Generalnie zagadnienia modeli pojęciowych, abstrakcji i metamodeli oraz związków między nimi są dość trudne (wbrew pozorom, większość ludzi ma problem z abstrakcyjnym myśleniem), jako narzędzia są bardzo przydatne w analizie i projektowaniu.

Rzecz w tym, że systemy analizowane istnieją, co znaczy ni mniej ni więcej tylko to, że “są dobre bo są i działają”.  Gorzej jest gdy system, nie raz nietrywialny, jest na etapie projektowania. Wtedy o tym jest “dobry” przekonamy się dopiero gdy go stworzymy (lub podejmiemy taką próbę).  Odkrycie, że jednak nie jest dobry bywa kosztowne.

Najczęstszymi wadami projektów systemów są wewnętrzna niespójność, niekompletność i wewnętrzne sprzeczności. Jak ich uniknąć na etapie projektowania? Jest rada: projektować od ogółu do szczegółu, stale przestrzegać śladowania, pracować na abstrakcjach i metamodelach. To pozwoli zapanować nad złożonością projektu (systemu). Nie da się tego robić “ręcznie” dlatego bez dobrego oprogramowania CASE takie projekty są raczej skazane na niepowodzenie lub znaczne dodatkowe koszty. 

Przykład

Na prostym przykładzie pokażę jak i kiedy używać różnych, bardzo ważnych związków i poziomów abstrakcji. Przykładem będzie pies i kojec. Najpierw model  pojęciowy, który zagwarantuje jednoznaczność całości oraz zrozumienie problemu. 

Bardzo często popełnianym błędem jest “przeciążanie” diagramów i (lub) mieszanie kontekstów. Polega to na mieszaniu na jednym diagramie modeli pojęciowych i strukturalnych (pisałem też o tym w artykule o diagramach klas). Model pojęciowy to słownik pojęć i zależności pomiędzy pojęciami (w notacji SBVR są to wyłącznie fakty łączące te pojęcia w jeden kontekst). W tym przykładzie mamy taki oto model:

Tworzenie modelu pojęciowego ma jedno kluczowe zadanie: zagwarantować spójność i jednoznaczność nazewnictwa w reszcie projektu. A nazwy dostają: klasy, atrybuty, operacje, aktorzy, wartości atrybutów klas, obiekty, itp.. Utrzymanie tu porządku, wbrew pozorom, nie jest łatwe. Na modelach pojęciowych stosujemy wyłącznie dwa związki: dziedziczenie czyli związek uogólnienia/specjalizacji (wskazuje typy) oraz asocjacje czyli nazwane związki między pojęciami. Tak więc typy ras to owczarek, pudel, ratler zaś pomiędzy psem a rasą jest taki związek, że nazwa rasy opisuje pewne cechy psa. Mamy także typy legowisk (buda, kojec) i związek psa z legowiskiem. Każda z tych nazw (pojęć) powinna mieć ścisłą definicję (w dokumentacji osobna tabela, zestawienie).

Teraz pora na projekt.

Projekt jest prosty wiec jego elementy zmieściły się na jednym diagramie. Tu mamy diagram obiektów (można na nim umieszczać także klasy). Większe projekty to większa liczba diagramów bardziej specjalizowanych. 

Tak więc metamodel to klasyfikatory i związki między nimi. Klasyfkator (klasa) zastępuje np. setki psów i ich kojców. Metamodel pokazuje w postaci uogólnionej związki między obiektami naszego świata, tu pokazano, że zwierze korzysta z kojca. Jak widać, użyto nazwy kojec a nie legowisko, dlaczego? Bo z jakiegoś powodu wiemy, że to jednak kojec i użyto tej nazwy do nazwania klasy.  Konkretna sytuacja to pies rasy ratler korzystający z kojca w postaci poduszki (u konkretnych Kowalskich). Burek (tak sie wabi pies) to ratler, jest instancją klasy zwierzę. Poduszka u Kowalskich to instancja kojca. Rynkowy produkt Kojec ALFADOG to realizacja Kojca, którego konkretnym przypadkiem wykonania jest ów kojec u Kowalskich. Klasa Kojec ALFADOG reprezentuje konkretny typ kojca (produkt rynkowe) na bazie specyfikacji wymagań jaką jest tu klasa Kojec na metamodelu. 

Już taki prosty model to nie mało zależności:

Diagram obrazuje związki pomiędzy diagramami i obiektami na nich, można poprzestać na związkach pomiędzy klasami, obiektami itp. Jednak pełny obraz śladowania i kontrola tego śladowania pozwala zapewnić spójność, kompletność i niesprzeczność dowolnie dużego projektu. Czy warto to robić? Ręcznie nie ma sensu z powodu pracochłonności, mając dobre narzędzie CASE nasza godzina pracy nad korektą modeli to ekwiwalent nie raz dziesiątek godzin developera po wykryciu niespójności w tworzonym oprogramowaniu lub wdrażanej zmianie organizacyjnej w firmie. Powyższy diagram to analiza wpływu obiektu Burek na pozostałe elementy projektu. Narzędzie CASE robi to automatycznie w kilka sekund… Aktualizacja całego projektu po zmianie jednego z nich także wykonywana jest automatycznie. 

Na zakończenie 

Takie analizy i projekty to (u mnie) ok. 70% to projektu programistyczne, pozostałe to analizy dotyczące projektowania reorganizacji, tworzenia wdrażania nowych reguł biznesowych (zarządzania zarządu, tworzenie prawa a kraju). To co tu opisałem, to tak na prawdę szeroko pojęta analiza systemowa.  Modelowanie to jedyna możliwość oderwania się od tysięcy detali świata realnego. Bez tego człowiek nie jest w stanie niczego skutecznie analizować czy projektować z uwagi na zbyt duży poziom złożoności. UML (i nie tylko) to narzędzie do tworzenia abstrakcji i metamodeli, bez czego żadna dobra analiza sie obejść nie może. 

Wstęp

Tym razem o kilku powszechnie popełnianych błędach w korzystaniu z UML. Chodzi o pojęcia abstrakcji, metamodeli i zależności oraz o związki między elementami na diagramach. Kluczową, moim zdaniem, przyczyną tworzenia “złych” modeli obiektowych jest używanie notacji UML do tworzenia modeli strukturalnych, nie mających z obiektowym paradygmatem nic wspólnego. Druga to niezrozumienie pojęcia paradygmatu obiektowego. Ogromna ilość diagramów wykonanych z  użyciem symboli notacji UML, z UML i paradygmatem obiektowym ma niewiele wspólnego.

Najpierw kilka definicji i pojęć.

Paradygmat programowania (ang. programming paradigm) ? wzorzec programowania komputerów przedkładany w danym okresie rozwoju informatyki ponad inne lub ceniony w pewnych okolicznościach lub zastosowaniach. (Źródło: Paradygmat programowania ? Wikipedia, wolna encyklopedia)

No to teraz obiektowy paradygmat:

Programowanie obiektowe (ang. object-oriented programming) ? paradygmat programowania, w którym programy definiuje się za pomocą obiektów ? elementów łączących stan (czyli dane, nazywane najczęściej polami) i zachowanie (czyli procedury, tu: metody). Obiektowy program komputerowy wyrażony jest jako zbiór takich obiektów, komunikujących się pomiędzy sobą w celu wykonywania zadań. Podejście to różni się od tradycyjnego programowania proceduralnego, gdzie dane i procedury nie są ze sobą bezpośrednio związane. (Źródło: Programowanie obiektowe ? Wikipedia, wolna encyklopedia)

albo:

Programowanie obiektowe lub inaczej programowanie zorientowane obiektowo (ang. object-oriented programing, OOP) to paradygmat programowania przy pomocy obiektów posiadających swoje właściwości jak pola (dane, informacje o obiekcie) oraz metody (zachowanie, działania jakie wykonuje obiekt). Programowanie obiektowe polega na definiowaniu obiektów oraz wywoływaniu ich metod, tak aby współdziałały wzajemnie ze sobą. (Źródło: Programowanie obiektowe ? Encyklopedia Zarządzania

Słownik języka polskiego mówi:

współdziałać
1. ?działać wspólnie z kimś?
2. ?przyczyniać się do czegoś razem z innymi czynnikami?
3. ?o mechanizmach, narządach itp.: funkcjonować w powiązaniu z innymi?

wywołać ? wywoływać
1. ?wołaniem skłonić kogoś do wyjścia skądś?
2. ?przypomnieć sobie lub innym coś?
3. ?spowodować coś lub stać się przyczyną czegoś?
4. ?oznajmić coś publicznie?
5. ?oddziałać na kliszę, błonę lub papier fotograficzny środkami chemicznymi w celu uwidocznienia obrazu utajonego na materiale światłoczułym?

Tak więc stwierdzenie, że “obiekty z sobą współdziałają” oznacza, że “wywołują się” wzajemnie w celu spowodowania czegoś konkretnego. Innymi słowy obiekty tworzące program (system) są od siebie wzajemnie uzależnione. Dlatego podstawowym związkiem w procesie analizy i projektowania zorientowanego obiektowo jest wzajemna “zależność”, nazywana w oryginalnej specyfikacji UML związkiem “usługodawca-usługobiorca”.

Zależności

Związek ten należy utożsamiać z każdą wzajemną zależnością. Z uwagi na to, że mamy wiele typów zależności, wskazujemy konkretny typ z pomocą stereotypu (<<[nazwa_typu]>>).

Najczęściej występujący typ zależności to związek użycia. Oznacza on, że jeden obiekt wywołuje umiejętności (operacje) innego czyli “używa go” do realizacja swojego zadania. Związek taki oznaczany stereotypem <<use>>:

uml-diagram-klas-zwiazek-uzycia

F używa (jest zależny od) E (co do zasady tu F nie jest samodzielny “w tym co robi”).

Formą zależności jest także dość rzadko wykorzystywana abstrakcja (<<Abstraction>>). Związek ten jest wykorzystywany do łączenia dwóch pojęć, z których jedno jest abstrakcją drugiego. Inna często wykorzystywana zależność to <<instanceOf>> oznacza zależność modelu (obiektu) od jego metamodelu (klasyfikatora) a także od metametamodelu.

Tak więc model oprogramowania w notacji UML z użyciem obiektowego paradygmatu powinien wyglądać mniej więcej tak:

Wzorzec BCE

Na diagramie użyto symboli klas (graficzna reprezentacja stereotypów) zaczerpniętych wzorca architektonicznego BCE.

Realizacja i kompozycja

Dokumentowanie detali struktury elementów np. repozytorium, wymaga stworzenia kolejnego diagramu: modelu struktury obiektów  reprezentujących np. złożoną strukturę obiektu niosącego informacje (np. z formularza).

uml-diagram-klas-zwiazki-realizacja

Korzystamy tu ze związku realizacja i związku kompozycja. Realizacja to związek pomiędzy specyfikacją a jej realizacją (projektem, implementacją, itp.), wskazuje zależność implementacji od jej modelu. C2 jest specyfikacją (opisem, dokumentacją) realizacji D2. Najczęściej związek ten jest stosowany pomiędzy specyfikacją interfejsu a jego implementacją a także generalnie pomiędzy abstrakcją a jej realizacją. Typowym przykładem użycia tego związku jest np. pokazanie na jednym diagramie abstrakcji bytu w repozytorium i realizacji jej struktury:

model-dziedziny-realizacja-repozytorium

Na diagramie użyto także związku “całość-część”: linia zakończona wypełnionym rombem na jednym końcu. Romb wskazuje na “całość”: element nadrzędny drzewiastej struktury konstrukcji. Jest to specjalny związek oznaczający trwałe (konstrukcyjne) powiązanie pomiędzy detalami składającymi się na określoną całość. UWAGA! Nie ma on nic wspólnego z pojęciem “relacji” znanym z teorii relacyjnych baz danych!

Z uwagi na to, że związek kompozycji stosowany jest do “rzeczy materialnych” (np. odwzorowywanie powyższego w postaci struktury klas w kodzie, OOP, pokazanie struktury materialnych konstrukcji, np. samochodu), na modelach oprogramowania, do modelowania struktury formularzy i komunikatów (dane) stosujemy związek zawierania (ang. member w oryg. UML):

Modelowanie struktury formularzy/komunikatów: po lewej diagram struktur złożonych UML, po prawej identyczna struktura jako klasyczny diagram klas i związki zawierania (modelowanie agregatów danych).

Generalizacja i asocjacja

Oprócz modeli struktur, powstają często modele pojęciowe. Są to odrębne diagramy. Ich celem jest dokumentowanie związków semantycznych i syntaktycznych pomiędzy kluczowymi pojęciami wykorzystywanymi w projekcie. Stanowią one nazwy obiektów i ich typy (atrybuty klas to także obiekty). Wykorzystywane są tu dwa rodzaje związków: generalizacja i asocjacja. Są to związki reprezentujące WYŁĄCZNIE logiczne powiązania pomiędzy pojęciami, nie modelują one struktur a ni implementacji!

Jeżeli jakieś pojęcie ma swoje specjalizowane typy, lub z drugiej strony, grupa pojęć daje się uogólnić innym nadrzędnym pojęciem (generalizowanie), stosujemy związek generalizacji. Związek ten (korzystanie z niego) ma sens tylko gdy liczba typów to co najmniej dwa, co do zasady związek generalizacji służy do grupowania. Jeżeli pomiędzy pojęciami istnieje związek wynikający z dziedziny problemu (np. związek pomiędzy zwierzęciem a karmą dla niego) modelujemy go linią ciągłą łączącą powiązane tak pojęcia. Poniżej graficzny przykład użycia tych związków:

zwiazki-generalizacji-i-asocjacje-w-modelu-pojeciowym

B1 i B2 to konkretne typy pojęcia A (typem jest także pojęcie). Analogicznie B12 i B22 to typy pojęcia A3. Pomiędzy pojęciami A i A3 istnieje związek logiczny (można go nazwać, wpisując te nazwę na linii). Typy mogą mieć więcej niż jeden kontekst dlatego mogą zostać pogrupowane a każda grupa otrzymuje nazwę “nazwa grupy”  (oryg. Generalization Sets). Nie ma sensu związek generalizacji pomiędzy tylko jedną parą pojęć, bo oznacza wtedy po prostu tożsamość. Np. pojęcie “województwo” w Polsce ma obecnie szesnaście specjalizacji, mają one swoje nazwy, i to jest jedna grupa typów. Jednak województwa można podzielić także np. ze względu na wielkość  na np. trzy grupy: małe województwo, średnie województwo i duże województwo, i to będzie druga i niezależna grupa typów.

Modele…

Wszystkie powyższe przykłady to diagramy klas notacji UML, jednak jak widać każdy ma zupełnie inne przeznaczenie (jest modelem czego innego). Nie omawiałem tu zupełnie diagramów klas modelujących kod programów. Zaznaczę jedynie, że są to kolejne modele dokumentowane z użyciem diagramów klas notacji UML, i omówione powyżej związki dziedziczenia i asocjacje na modelach pojęciowych mają tam zupełnie inne znaczenie.

Modele mogą być różne i dotyczyć różnych rzeczy (patrz Modele….). Tu chcę zwrócić uwagę na bardzo ważny aspekt: abstrakcyjne i rzeczywiste pojęcia w modelach (na diagramach). Dostrzegam ogromny bałagan nie tylko w dokumentach projektowych ale także i w literaturze, gdzie autorzy pokazują wiele różnych przykładów, które niestety są złe i pozbawione uzasadnienia.

Przede wszystkim modele dzielimy, jak już wspomniałem, na pojęciowe i te modelujące jakąś określoną rzeczywistość. Modele pojęciowe to modele pokazujące pojęcia oraz semantyczne i syntaktyczne związki miedzy nimi.  Modele pojęciowe służą do udokumentowania dziedzinowej taksonomii co z jednej strony pozwala utrzymać pełną jednoznaczność dokumentacji a z drugiej, na etapie implementacji, pozwala podejmować decyzje o typach danych. Służą one np. do budowania słowników i etykietowania np. pól formularzy (pole Nazwa województwa, którego zawartość będzie wybierana ze słownika zawierającego szesnaście nazw). Na tych modelach pojawiają się w zasadzie wyłącznie pojęcia stanowiące abstrakcje i typy, nie są to modele żadnego kodu, dziedziny itp. Tu przypomnę, że model dziedziny systemu to model opisujący mechanizm jego (systemu, aplikacji) działania, reprezentowany jest przez literkę M we wzorcu MVC, poważnym błędem jest uznawanie tych modeli za modele danych.

Modele struktury to modele opisujące określone “konstrukcje”, głównie na dwóch poziomach abstrakcji: jako model i jako metamodel. Z reguły, w projektach związanych z oprogramowaniem, ta konstrukcja to właśnie Model Dziedziny czyli mechanizm działania, tak zwana logika biznesowa/dziedzinowa aplikacji.

Podsumowanie

Tak więc nie ma czegoś takiego jak “diagram klas dla projektu”. Mamy dla danego projektu: model pojęciowy, modele logiki systemu, modele struktury obiektów, modele implementacji. To wszystko są diagramy klas ale każdy z nich do model “czegoś innego”. Paradygmat obiektowy jasno mówi: obiekty współpracują, więc standardowym związkiem w modelach logiki działania są związki użycia a nie asocjacje ani związki dziedziczenia czy kompozycji. Diagramy te nie są żadnymi modelami danych między innymi dlatego, że z zasady paradygmat obiektowy ukrywa je (hermetyzacja), “na zewnątrz” dostępne są wyłącznie publiczne operacje obiektów. Utrwalanie obiektów (zapis wartości atrybutów np. w bazie danych) to zadanie do rozwiązania dopiero na etapie implementacji, polegające na zagwarantowaniu “zachowania” stanów obiektów na czas wyłączenia zasilania, by nie “uleciały” z pamięci komputera, który jest środowiskiem wykonania programu. Na etapie analizy obiektowej i modelowania logiki systemu nie modelujemy żadnych danych.

Poniższy przykład, jakich wiele w sieci, jest wzorcowym antyprzykładem.

(https://www.wiedzanaplus.pl/programowanie/33-uml/68-jezyk-uml-projekt-sklepu-komputerowego.html)

Pierwsza zła cecha tego diagramu to częsty błąd nazywany popularnie “przeciążaniem obiektów”: tu obiekt Faktura ma operacje “sporządź”. Klasyczny błąd architektoniczny polegający na obciążaniu obiektu cudzą odpowiedzialnością. Jeżeli klasa Faktura reprezentuje np. faktury sprzedaży, to system mający w pamięci kolekcję setek faktur i każda z kodem (ma te operację) służącym do jej sporządzenia byłby masakrą zajętości pamięci. Dodam, że model taki nie ma nic wspólnego z rzeczywistością, bo faktury wystawia “ktoś” a nie “faktura”. Trzecia rzecz: faktura zakupu i faktura sprzedaży to niczym nie różniące się struktury, więc tworzenie takiego rozróżnienia  jest pozbawione sensu. To błędy pojęciowe i ten diagram ma masę takich błędów. Druga wada: błędne użycie związku kompozycji: powyższy diagram należałoby interpretować jako strukturę o jednej zwartej konstrukcji, np. takiej jak samochód składający się z setek podzespołów ale stanowiący jednak jedną całość. Brak modelu pojęciowego i słownika powoduje wiele niejednoznaczności. Np. związek pomiędzy Klientem a reklamacją wskazuje, że Reklamacje są integralną częścią Klienta (tak jak koła i silnik są integralną częścią samochodu) co nawet w świetle potocznego rozumienia tych słów kłóci się ze zdrowym rozsądkiem.

Użycie związków pojęciowych (asocjacja) jest zupełnie niezrozumiałe w tym przypadku. Nazwa asocjacji “zawiera” nie ma kierunku a więc nie wiadomo co jest zawarte w czym. Związki zależności także są niejasne: jak interpretować np. zapis mówiący, ze obiekty klasy użytkownicy zależą od obiektów klasy Raporty? Jeżeli autor miał na myśli “użytkownik używa raportów” to popłynął w sferę “mowy potocznej”, to chyba najczęściej spotykany błąd polegający na tym, że autor diagramu nadal pisze specyfikację prozą, ale z użyciem symboli (tu UML) zamiast słów.

Mogę się jedynie domyślać, że autor diagramu “miał w głowie” model relacyjny związków encji i użył ikon z notacji UML w całkowicie innych znaczeniach niż ta notacja definiuje. Takie diagramy nie powinny powstawać, są one niestety dowodem na to, że programiści mówiący “te dokumenty z UML nic nie wyjaśniają i są nieprecyzyjne, i tak musimy sami powtórzyć te analizę” mają rację. Są także dowodem, że są to jednak projekty strukturalne a nie obiektowe, a użycie notacji UML polegało na skorzystaniu z zestawu ikon tak się to robi tworząc niesformalizowane schematy blokowe z użyciem np. programów do tworzenia prezentacji takich jak PowerPoint. Zapewne poza autorem tego diagram, nikt nie ma pojęcia o co w nim chodzi….  Jeżeli autor miał na celu udokumentowanie “modelu danych” to powinien użyć notacji ERD. A tak to mamy schemat blokowy, w którym ktoś użył UML jako biblioteki symboli wprowadzając czytelnika w błąd.

Z przykrością muszę przyznać, że od takich błędów nie są wolne także niektóre podręczniki i materiały szkoleniowe… a także dokumenty tworzone przez pracowników wielu firm na rynku IT.

P.S.

Na przykłady poprawnych diagramów z uzasadnieniem zapraszam do mojej książki i na moje szkolenia i warsztaty.

Specyfikacja UML v.2.5

Niestety “sprawne” korzystanie z takich specyfikacji wymaga umiejętności czytania modeli pojęciowych (diagramów klas UML) opisujacych syntaktykę (syntax) jak wyżej (ich tworzenie też do łatwych nie należy…). Przytaczam to jako źródło tego o czym tu pisałem.

W UML “wszystko jest klasą”, związki między elementami diagramów także. Zostało to pokazane w specyfikacji UML v.2.5.:

uml-zaleznosci
uml-klacyfikator
uml-zwiazki

W UML 2.5 praktycznie znika w końcu dziedziczenie i agregacja. Uff… poniżej podsumowanie na jednym diagramie.

Dodatek [2022-07-22]

…chciałbym zapytać o dziedziczenie w UML. Brałem ostatnio udział w kilku rozmowach o pracę. Moi rozmówcy często utrzymują, że w UML jest to dziedziczenie. Mało tego, wykorzystują to dziedziczenie na diagramach klas, w czymś co nazywają modelem dziedziny (oczywiście bez operacji klas). Zajrzałem do specyfikacji UML i jestem trochę zdezorientowany, ponieważ pojęcie dziedziczenie dość często występuje w tej dokumentacji, choćby w tym fragmencie.

W jakim znaczeniu jest tu użyte pojęcie dziedziczenia? Czy jest gdzieś w specyfikacji wprost informacja o usunięciu dziedziczenia z UMLa?

Porównując ze specyfikacją 2.4. nie ma, pojęcie dziedziczenia nadal jest obecne w językach programowania, a w specyfikacji 2.5.xx słowo “inheritance” pojawia sie tylko 4 razy, i dotyczy wyłącznie komentarzy związanych z uogólnieniami.

Cytowany związek Generalizacji jest związkiem pojęciowym a nie strukturalnym czegoś istniejącego (struktura modelowanego systemu). Formalnie w UML mamy związek Generalizacji, i jest to związek pojęciowy,  ale nie ma w UML rozdziału “Dziedziczenie”. Inherit w j. angielskim to także “przejęcie cech po czymś” czyli związek generalizowania i specjalizowania. Generalziacja, jako związek ogólny-szczególny, pokazuje że szczególny przypadek, pojęcie i jego definicja, ma wszystkie cechy przypadku ogólniejszego, więc pies jako szczególny typ ssaka ma wszystkie cechy ssaka (chodzi o definicje tego czym jest ssak). Czyli:
1. ssak: kręgowiec, którego młode karmią się mlekiem matki,
2. pies: <ssak> który szczeka,
więc:

pies: <kręgowiec, którego młode karmią się mlekiem matki>, który szczeka,  

czyli pies (a konkretnie definicja psa) “dziedziczy” po ssaku (definicji ssaka) jego określone cechy, czyli oznacza to, że są one (cechy ssaka) np. dla psa i kota wspólne, a nie że, kot czy pies coś “dziedziczy” po ssaku :). 

Dlatego w UML teraz jest napisane, że mamy generalizację jako związek między klasami, ale nigdzie nie ma związku “dziedziczenia” między klasami.

Tekst rozdz. 9.2.3.2. tłumaczymy tak,  ze generalizacja oznacza, że generalizowana definicja (cechy czyli atrybuty i zachowania)  są wspólne (dziedziczą) dla specjalizowanych elementów. W modelu pojęciowym (namespace) tak to działa. I teraz: model struktury czegokolwiek istniejącego nie ma abstrakcji (a generalizowane pojęcia to abstrakcje, pojęcie ssak to pojęcie abstrakcyjne). Opisując więc traktor jako pojazd czy dokumenty w księgowości jako dokuenty, nie ma nad nimi “uogólnień”, po których dziedziczą (sa to jednak pojęcia słownikowe). Po trawie nie biegają “ssaki” tylko psy, koty, lwy…

Znakomita większość znanych mi ludzi w IT nie odróżnia modelu pojęciowego od modelu struktury systemu, który zbudowany jest (każdy) z realnych elementów. W miejsce “starego” dziedziczenia w UML wprowadzono pojęcia: szablon (template) oraz rola (realnym czymś w samochodzie jest “silnik”, pojęcie “napęd” to nazwa funkcji/roli określonego komponentu, ale nie piszemy: “<silnik> dziedziczy po <napędzie>” tylko “<silnik> pełni rolę <napędu>” tak samo jak nie powiemy “<pies> dziedziczy po <zwierzę domowe>” tylko “<pies> pełni rolę <zwierzęcia domowego>”.  

Języki programowania pozwalają na tak zwane re-użycie kodu, czyli wyciąganie pewnych wspólnych cech ponad konkretne komponenty/klasy (abstrakcja) po to by tylko raz pisać kod komponentów mających wspólne cechy. Jednak projektując “model dziedziny” nie ma w niej abstrakcji, mając model dziedziny (mechanizm działania aplikacji) zamieniamy generalizacje i “dziedziczenia” pojęciowe na atrybuty i typy realnych rzeczy, tak to opisano książce o modelowaniu systemów już w 2007 roku:  

Oczywiście programista może używać gdzie chce dziedziczenia (ma taką możliwość w każdym obiektowym języku programowania) ale to jedna z najgorszych praktyk, bo łamiących kluczową zasadę jaką jest hermetyzacja komponentów (“zakaz” współdzielenia czegokolwiek). To dlatego od wielu lat OOP (Object-oriented Programming, języki i programowanie obiektowe) i OOAD (Object-oriented Analysis and Design) to odrębne “dziedziny”.

Dzisiaj będzie bardzo krótki wpis, którym niemalże w całości będzie cytat z artykułu pewnego programisty. Każdy analityk i projektant powinien  przeczytać cały artykuł (nie jest długi) i koniecznie komentarze pod nim. Jeden komentarz zacytuje bo jest znamienny, choćby dlatego, że moje doświadczenia są dokładnie takie same:

Co Ty człowieku, życia nie znasz? Przecież w realu jakbyś poszedł do takiego i podzielił się takimi uwagami  to foch na pare tygodni co najmniej gwarantowany. Kiedyś miałem podobną sytuację (a raczej serię sytuacji, ale dotyczących rzeczy o mniejszej skali) to efekt był taki, że wolałem zmienić robotę 😉

A teraz zapraszam do lektury tekstu, który napisał nie analityk a programista (a kim jest to polecam jego CV na stronie):

Jak to z każdym językiem bywa, niekiedy jesteśmy w stanie wydobyć bardzo wiele nawet bez zbytniego wgłębiania się w szczegóły, po prostu wiemy, że niektóre rzeczy są ważniejsze niż inne. Tak jak w zdaniach, których spójnikiem jest ?ale?. Każdy wie, że wszystko to, co przed owym ?ale? się znajduje jest niewarte słuchania 😛 I właśnie kilka takich przypadków chciałbym dzisiaj opisać. Sytuacji, w których rozumienie całego kontekstu jest zbędne, wystarczy szybki rzut okiem na diagram, aby wiedzieć co jest najważniejsze.

W omawianych przeze mnie przypadkach chodzi o szybkie wyłapanie błędów projektowych, bez konieczności zagłębiania się w logikę, która kryje się za diagramami. (Programistyka: Diagram prawdę Ci powie)