Zacznijmy od czegoś prostego. Według słownika abstrakcja to:
“a general concept formed by extracting common features from specific examples”
“the action of removing something from something else”
“the situation in which a subject is very general and not based on real situations”
Wiemy zatem, że abstrakcja to pojęcie ogólne, którego opracowanie może polegać na wyodrębnianiu pewnych właściwości z jakiegoś obiektu. Abstrakcja może też polegać na usuwaniu ‘czegoś z czegoś’. Weźmy pod uwagę słowo abstrakt, które oznacza streszczenie jakiejś publikacji. W streszczeniu lektury szkolnej ujęci zostaną kluczowi bohaterowie oraz wydarzenia, a te mniej istotne zostaną ukryte.
Posłużmy się jeszcze innym przykładem. Trochę kultury.
Widziałeś na pewno kiedyś obraz, który przedstawiał, ogólnie mówiąc, abstrakcję. Myśląc o nurcie jakim jest abstrakcjonizm wyobrażam sobie figury geometryczne, nieregularne, kolorowe kształty i trudną do zrozumienia symbolikę obrazu. Można domniemywać, że w nurcie tym ważna jest niejednoznaczność i subiektywizm odbioru. Jednak w sztuce, w przeciwieństwie do programowania autor może pozwolić sobie, aby być rozumianym na wiele sposobów. Artysta malując obraz abstrakcyjny może usunąć (i jest to zgodne z definicją słownikową) na tyle dużo atrybutów, właściwości przedmiotu, że staje się on albo nie do rozpoznania przez obserwatora albo nabiera wieloznaczności. Każdy może interpretować taki obraz na swój sposób. Malarz tworząc obraz uogólnia i upraszcza. Jak to będzie wyglądać w programowaniu.
Poniżej trzy możliwości.
Jeden rzeczownik -> jeden model
Mamy nieraz do czynienia z sytuacją, że nie musimy dokonywać rozróżnienia czegoś / jakichś bytów w kontekście biznesowym. Już piszę co mam na myśli. Załóżmy, że piszemy aplikację Skarbiec. Pozwalamy naszym klientom przechowywać wartościowe rzeczy w naszych specjalnych skrytkach. Każda taka skrytka ma swój symbol. Np.: 2/3/4 – 2. regał, 3. rząd, 4. półka. Gdy klient przychodzi, aby odebrać swój skarb, mnie interesuje tylko gdzie ten skarb leży. Czyli w ramach modułu przechowywalnia interesuje mnie lokalizacja skarbu klienta. Moją encję nazwę Skarb. Nie jest istotne czy jest to gotówka, czy sygnet po przodku. Nie mam potrzeby, aby rozróżniać te rzeczy. Mamy do czynienia z uogólnieniem.
Jeden rzeczownik -> wiele modeli
Wyobraź sobie, że tworzysz aplikację pt.: “Sklep internetowy” dla firmy ProsteSłowa z o.o. Prężnie działająca księgarnia na jednym z radomskich osiedli. Książka jest słowem, które krąży po wszystkich działach firmy ProsteSłowa z o.o., na przykład w dziale zamówień oraz w magazynie. Jednak patrząc z perspektywy każdego z działów firmy, to słowo może być rozumiane w inny sposób. Słowo książka w codziennym życiu zawodowym pani X z działu zamówien ma inną charakterystykę niż pracownika magazynu. Panią X będzie interesować m.in.: cena zamówienia, a pracownika magazynu będzie interesował jakiś aspekt składowania tego zamówienia. Pani X będzie wykonywała inne czynności z książką, niż magazynier.
Jak w takim razie stworzyć model książki? Intuicja podpowiada, że stworzenie jednego, wspólnego / kanonicznego modelu nie jest najlepszym pomysłem. Skończyłoby się to prawdopodobnie źle. Zmiana na potrzeby wprowadzenia zmiany w magazynie, mogłaby spowodować że popsuliśmy działające wcześniej funkcje działu zamówień. Dramat, degrengolada. Całość byłaby bardzo nieczytelna. Gdyby kilka osób popatrzyło na taki model mogłyby one zinterpretować go na kilka różnych sposobów. Nie byłoby wiadomo z jaką częścią / aspektem przedsiębiorstwa mamy do czynienia. Bylibyśmy jak malarz abstrakcyjny, który swoje barwne uniesienia przedstawił w fikuśny sposób. Dla mnie obraz przedstawiałby kotka na parapecie, a dla Ciebie byłby to nie kotek, na nie parapecie. Jestem pewien, że serwis chcący pokryć zachowania takiej encji miałby kilka tysięcy linii.
Skoro nie jeden model, to może kilka modeli o tej samej nazwie i podzielenie naszej aplikacji na, w miarę możliwości, jak najmocniej niezależne moduły. Mówiąc moduł mam na myśli jednostkę, która realizuje jakieś funkcje biznesowe. Niech to będzie u nas pakiet znany z języka Java. Zatem w pakiecie zamówienia w modelu Book niektóre atrybuty, takie jak np.: numer półki, na której znajduje się książka zostaną ukryte, tzn.: po prostu ich tam nie będzie, a niektóre wyodrębnione jako te istotne np.: cena książki, jej tytuł i autor. Podobnie stanie się w pakiecie magazyn gdzie np.: numer półki będzie obecny, a cena zostanie ukryta, ponieważ w trakcie analizy projektu okazało się, że pracownik magazynu w żaden sposób nie wchodzi w interakcję z ceną książki. Innymi słowy, w magazynie nie mają miejsca żadne zachowania i reguły dotyczące ceny książki. To co będzie łączyć te książki, to unikalny identyfikator, który pozwoli na realizację pełnego procesu. Nie będziemy też *agregować* w naszych modelach nieistotnych aspektów rzeczywistego procesu. Mówiąc jeszcze inaczej, nie chcemy odwzorować rzeczywistości 1:1. Jeśli przed wysyłką do klienta, do książki dokładany jest jakiś gratis, np.: kubek, to nie znaczy, że nasz model musi mieć / agregować byt pt.: gratis. Nie pilnujemy / nie dbamy tutaj o żadną zasadę biznesową. Zatem nie musimy wyodrębniać w naszym projekcie takiego bytu bowiem ten etap procesu wysyłki jest robiony ręcznie przez człowieka.
Wiele rzeczowników – wiele modeli
Podczas szukania odpowiedniego słownictwa może się okazać, że tak w zasadzie, to w magazynie nie mamy do czynienia z książką. Żaden pracownik magazynu tak nie mówi (a jeśli mówi, to być może mówi błędnie bo ktoś mu kiedyś tak narzucił). Okazuje się, że w magazynie nie jest to ksiązka lecz egzemplarz. Czyli dochodzimy do wniosku, że każdy z działów może stanowić granicę lingwistyczną i może posiadać swój własny język. Język i model są powiązane, uprzoszczając, z działem firmy.
Poniżej o tym jakie typy abstrakcji możemy jeszcze znaleźć w literaturze.
Abstrakcja procesu
W opisie przebiegu procesu obliczania podatku przez księgową wyłuskaliśmy istotne informacje na temat procesu rozkładając go tym samym na czynniki pierwsze. Można wysnuć wniosek, że opracowanie abstrakcji pozwala rozbić złożony problem biznesowy na mniejsze części.
Krótkie przypomnienie. Pierwsza abstrakcja, którą wyodrębniliśmy i ubraliśmy w interfejs Javowy to pobranie danych klienta (ClientDataRetrieval). W realnym przebiegu tego procesu, bez użycia komputera, księgowa zapewne otworzyłaby teczkę i wyjęła fakturę z danymi klienta takimi jak.: NIP, nazwa firmy oraz kwota, na którą klient wystawił fakturę.
Jeśli Ty, jako programista rozmawiałbyś z księgową na temat aplikacji do obliczania podatku dochodowego, to nie będziecie mieli problemu, aby porozumieć się w kwestii pobrania danych klienta. Takie zdarzenie występuje niezaprzeczalnie i w tym zakresie świetnie się rozumiecie. Co prawda Twoja księgowa nie będzie wiedziała czy dane klienta pobrane zostały z relacyjnej bazy danych czy z jakiegoś zewnętrznego systemu. W końcu ona naciśnie tylko przycisk Pobierz dane klienta John Doe. Grunt, że jesteście w stanie się porozumieć dzięki identyfikacji abstrakcyjnego terminu o jasno brzmiącej nazwie. Na przykład stwierdzenie zaciągnąłem selectem informacje o customerze niekoniecznie będzie zrozumiałe dla księgowej. Księgowa może i zaciąga pośrednio selectem dane przy pomocy przycisku, ale w jej języku nie jest to customer tylko klient, a o żadnym słowie select w życiu nie słyszała. Po co więc utrudniać sobie i innym życie. Warto mówić wspólnym językiem za pomocą umownych terminów. Oczywiście sztuką jest je zidentyfikować.
Podobnie jest z abstrakcją obliczanie podatku dochodowego (interface TaxCalculator). Zarówno Ty – programista, Twój nietechniczny przełożony czy księgowa, niezależnie od kraju pochodzenia, przynajmniej słyszeliście termin obliczenie podatku dochodowego. Czyli wyodrębniliśmy ponownie ważny aspekt procesu biznesowego, pomijając w tej chwili jakiekolwiek szczegóły dotyczące implementacji. W realnym przebiegu procesu, jak już księgowa otworzyłaby teczkę i wyjęła fakturę z wszystkimi niezbędnymi danymi, chwyciłaby następnie za kalkulator, kawałek papieru i długopis. Detalem natomiast jest to, co by na tej kartce zostało napisane.
Przykłady przykładziki
Klasy (abstrakcyjne i nieabstrakcyjne) oraz interfejsy są elementami języka programowania, które pozwalają upraszczać problem biznesowy poprzez dzielenie go na mniejsze części. To już pi razy oko wiemy. Spróbujmy jeszcze kilka, może nieco bardziej namacalnych przykładów,
Abstrakcja opracowana za pomocą nieabstrakcyjnej klasy
Stworzyliśmy klasę OrderService w pakiecie com.order. Serwisik. Mmm. Klasa ta stanowi punkt dostępowy do procesu realizacji zamówienia w naszym systemie. To znaczy, że klasy z innych modułów (np.: z innego pakietu) mogą skomunikować się z pakietem com.order wyłącznie poprzez OrderService, będącą publicznym API. Stworzyłeś tym samym pewną abstrakcję poprzez wyodrębnienie istotnych, dostępnych na zewnątrz modułu zachowań. To co nie jest istotne w API (z punktu widzenia innych modułów) jest ukryte oraz niedostępne dla klienta.
Abstrakcja opracowana za pomocą abstrakcyjnej klasy
Jeśli stworzysz abstrakcyjną klasę zamówienie (abstract Order) i stanie się ona klasą bazową dla klas pochodnych np.: VipOrder oraz RegularOrder to w tym przypadku abstract Order stanowi uogólnienie bytu jakim jest zamówienie. Wyodrębnimy w niej wspólne atrybuty i zachowania dla klas pochodnych. Natomiast klasy VipOrder oraz RegularOrder będą kolejnym poziomem abstrakcji. Go deeper and deeper. Czy to dobry pomysł, czy nie oraz kiedy to już inna para kaloszy.
Abstrakcja opracowana za pomocą interfejsu
Tworząc interfejs Javowy TaxCalculator tworzysz uogólniony byt z wyodrębnionymi zadaniami, które będą następnie realizowane przez klasy implementujące ten interfejs. Za pomocą interfejsu mówimy jedynie co a nie jak możemy zrobić. Abstrakcja będzie dobra, jeśli nie będziemy musieli czytać implementacji, żeby dowiedzieć się co możemy osiągnąć wywołując metodę interfejsu. Nie chcemy być jak malarz, który pozostawia nam dowolną liczbę możliwości interpretacji swojej pracy. Owszem, tak jak on ukrywamy jedne aspekty i wyodrębniamy inne, pamiętając jednak przy tym, że nasze intencje oraz słownictwo użyte w projekcie powinny być dla wszystkich zrozumiałe i, w miarę możliwości, jak najlepiej opisywać swoje przeznaczenie. Pamiętajmy też o zasadzie ISP z SOLIDu, będącą powiedzmy taką zasadą SRP dla interfejsów.
Gdy skończyłem ten wpis trafiłem na bardzo fajny artykuł. Warto przeczytać. Autor również skupił się na semantyce i również odnosi się do abstrakcji w sztuce. Porusza też kilka innych ciekawych tematów.