Słownikowa definicja słowa encapsulation mówi o opakowaniu, zamknięciu czegoś w kapsule. Robimy w miarę podobne rzeczy w języku obiektowym – opakowujemy dane i zachowania w kapsule jaką jest klasa.

Thinking in Java, Bruce Eckel napisał:

„Access control is often referred to as implementation hiding. Wrapping data and methods within classes in combination with implementation hiding is often called encapsulation. However, people often refer to implementation hiding alone as encapsulation

Effective Java, Joshua Bloch pisze:

“(…) This concept, known as information hiding or encapsulation, is one of the fundamental tenets of software design

W polskim tłumaczeniu tego fragmentu słowo encapsulation jest przetłumaczone jako hermetyzacja. Nikt się nie obrazi, a i będzie łatwiej się żyło, jeśli użyjemy stwierdzenia:

enkapsulacja = hermetyzacja = ukrywanie informacji = ukrywanie implementacji

Martin Fowler ujął to jeszcze tak:

“For me, the point of encapsulation isn’t really about hiding the data, but in hiding design decisions, particularly in areas where those decisions may have to change”

Brzmi rozsądnie. Zatem przyjmijmy, że:

enkapsulacja = hermetyzacja = ukrywanie informacji = ukrywanie implementacji = ukrywanie decyzji projektowych = ukrywanie wiedzy

Wróćmy do naszego przykładu opracowania modelu, którym było konto.

Rys.1 Model Account

Zdefiniowaliśmy model (abstrakcyjny byt/obiekt) o nazwie Account. Na etapie zdobywania wiedzy o procesie dowiedzieliśmy się, że nasze konto posiada ważne zachowanie tj.: możliwość deaktywacji. Daje nam to do myślenia, że będzie to obiekt stanowy, z przynajmniej dwoma stanami Active/Inactive.

Zdobyliśmy również wiedzę na temat reguł rządzących deaktywacją konta.

Konto może zostać zdeaktywowane ręcznie w przypadku, gdy administrator uzna, że użytkownik złamał regulamin, przy czym nie da się zdeaktywować konta, które już jest nieaktywne. Dodatkowo deaktywacja wiąże się z przestawieniem statusu konta na Inactive oraz powinniśmy także ustawić datę deaktywacji konta. Let’s do it.

Rys.2 Przykład enkapsulacji wiedzy w metodzie deactivate()

Zrobione.

Na Rys. 2 użyliśmy enkapsulacji. Opisane reguły biznesowe ukryliśmy w metodzie o jednoznacznie brzmiącej metodzie deactivate. Brzmi fajnie. Używamy nawet tego samego słownictwa co nasz zleceniodawca i użytkownicy systemu. Będziemy mogli się z pewnością łatwiej dogadać. Na pierwszy rzut oka widać za co ta metoda odpowiada, a co więcej, wiemy jakie reguły rządzą deaktywacją. Kod jest krótki i niezwykle czytelny. Z zewnątrz klasy nie jesteśmy w stanie dobrać się do prywatnych pól klasy Account (przynajmniej w teorii, kliknij tu) co za tym idzie nie jesteśmy w stanie naruszyć stanu obiektu reprezentującego konto (nie jesteśmy w stanie zmienić na przykład tylko daty deaktywacji bez zmiany wartości pola status). Aby zdeaktywować konto z poziomu np.: serwisu aplikacyjnego, nie musimy mieć żadnej wiedzy na temat reguł, które rządzą deaktywacją. Wołamy po prostu metodę deactivate na obiekcie klasy Account. Możemy spać spokojnie.

Ale po co to wszystko? Moglibyśmy przecież mieć w nosie hermetyzację i użyć modelu Account z publicznymi polami lub nasiekać setterów (w prostych, płytkich i mało złożonych biznesowo aplikacjach jest to jak najbardziej akceptowalne rozwiązanie). Przecież pół życia tak pisałem. Wyglądałoby to mniej więcej tak:

Rys. 3  Anemiczny model Account            

A teraz wyobraź sobie, że przychodzisz do nowego projektu i masz do czynienia z kodem przedstawionym na rysunku nr 3. Twoim zadaniem jest napisanie serwisu aplikacyjnego, który pobierze konto z bazy danych na podstawie jakiegoś identyfikatora, następnie je zdeaktywuje i zapisze nowy stan obiektu w bazie danych. Czyli de facto, to co znajduje się w metodzie main na rysunku nr 3 (pomijając nieistotny dla nas w tej chwili odczyt i zapis). Na chłopski rozum, komuś kto nie zna reguł biznesowych mogłoby się wydawać, że wystarczy ustawić pole state na Inactive i voila. My pamiętamy jednak, że reguła deaktywacji konta mówi jeszcze o ustawieniu daty deaktywacji. Poprzez swobodny dostęp do pól klasy Account możemy bardzo łatwo wprowadzić obiekt w niepożądany stan, a następnie zapisać go np.: do bazy danych. Jest to szczególne niebezpieczne, gdy mamy większy obiekt, który daje więcej możliwości operowania na danych (polach instancyjnych) obiektu niż tylko nasza przykładowa, niewinna deaktywacja.

Enkapsulacja umożliwia nam także łatwiejsze wprowadzanie zmian w naszych regułach biznesowych i chroni przed powielaniem kodu (tj.: wiedzy na temat reguł biznesowych). Załóżmy, że reguły biznesowe deaktywacji się zmieniają i oprócz statusu daty deaktywacji trzeba jeszcze ustawić pole reason (niech to będzie powód deaktywacji). Teraz wyobraź sobie, że w dwóch serwisach w Twojej aplikacji musisz dokonać deaktywacji konta, a do tej pory robiłeś to w taki sposób jak na rysunku nr 3. Kiepsko. Musisz tej zmiany dokonać w dwóch miejscach. Szkoda gdybyś zapomniał tego zrobić w którymś serwisie.

Jeśli natomiast byśmy naszą implementację reguł biznesowych mieli umieszczoną w naszym modelu Account, w metodzie deactivate, zmiana zaszłaby jedynie w tej jednej metodzie, a serwisy wywołujące metodę deactivate zostałyby bez zmian. Znacznie lepiej.

Nie jest tak, że jedynym poprawnym podejściem zatwierdzonym przez panów z brodą jest hermetyzacja i modelowanie naszych klas według rozpoznanych na ważnych spotkaniach reguł biznesowych. Jeśli piszesz aplikację, która np.: wyświetla na stronie WWW tylko dane pobrane z bazy danych, a reguł biznesowych w zasadzie nie ma, to możliwe, że mamy do czynienia z architekturą dwuwarstwową, zatem programowania ukierunkowanego na enkapsulację reguł biznesowych nie będziemy stosować.

Przykład sexy VS ugly encapsulation na githubie w module encapsulation.

https://github.com/wprostychslowach/samples

Leave a Reply

Your email address will not be published. Required fields are marked *