sobota, 4 kwietnia 2015

Filary programowania obiektowego - Hermetyzacja

Hermetyzacja jest częściej znana pod angielskim odpowiednikiem "encapsulation" - enkapsulacja.

Czym jest? W prostych słowach to osłonięcie pewnej części lub całego obiektu przed wścibskimi oczami innych obiektów/odbiorców. To co jest za zasłoną to struktura obiektu, jego dane w postaci zmiennych oraz funkcjonalności w postaci metod/procedur. Poza zasłoną pozostawione powinno być tylko to co jest potrzebne - jakkolwiek enigmatycznie to brzmi, w istocie jest proste - nie pokazuj tego co nie potrzebne/nie używane/nie potrzebne przez innych.

Po co? Stosowanie w odpowiedni sposób hermetyzacji po części zwalnia programistę z konieczności pamiętania lub znajomości implementacji, ale moim zdaniem przede wszystkim pozwala na zachowanie kontroli, spójności funkcjonalności oraz uniknięciu wielu błędów. Jeżeli nie pozwolimy innym obiektom modyfikować właściwości obiektu hermetycznego i pozwolimy korzystać tylko z danych funkcjonalności możemy być pewni że obiekt zachowa się w sposób przewidywalny. Co więcej  obiekt hermetyczny jest bardzo łatwy w obsłudze i utrzymaniu przez innych programistów, a to z punktu widzenia utrzymania projektu i jego rozwoju w przyszłości jest już wyraźną wartością dodaną.

Jak ją uzyskać? Najprostszym sposobem jest użycie odpowiednich modyfikatorów dla pól i metod. W przypadku Javy takimi modyfikatorami są: private, protected, public i modyfikator pakietowy (domyślny). Są to już konkretne funkcjonalności języka programowania, który jeżeli chce nazywać się obiektowym musi taką funkcjonalność zapewniać. Nie wchodźmy na razie w szczegóły danego języka.

Często w mojej pracy zawodowej pisałem pewne klasy od podstaw i nie raz zastanawiałem się czy po prostu nie wygodniej byłoby uczynić wszystkie zmienne publicznymi. Będzie się wtedy łatwiej wszystko ustawiać, nie tracąc czasu na pisanie metod które pozwalają na dostanie się do danych zmiennych. Spójrzmy na przykład.

class Samochód {

       public Silnik silnik;
       public Kierownica kierownica;

       public void jedź() {
            // Tutaj mamy jakąś logikę odpowiedzialną za jazdę
            // która oczywiście wykorzystuje silnik i kierownicę
       }
}

Czyż nie łatwo w takim samochodzie wymienić silnik i kierownicę? Wszystkie obiekty mają taką możliwość i robią to bez problemowo. Zastanówmy się nad konsekwencją takiego działania.

Raz wywołana metoda jedź() pozwoli na jazdę samochodowi. Ale skoro wszystkie obiekty mają łatwość zmiany silnika i kierownicy, możemy założyć że jakiś "złoczyńca" ustawi nam kierownicę na jakąś nie ciekawą wartość (np. w Javie na null).

Kolejna próba wywołania metody jedź() może już się skończyć tragicznie dla kierowcy i pasażerów auta. Jeżeli uniemożliwimy innym obiektom dostęp do silnika i kierownicy w tym przypadku możemy spać i jeździć spokojnie.

Widać wyraźnie jak bardzo się myliłem w stwierdzenie zawartym w pierwszym akapicie tego paragrafu. Nie mniej jednak co by było gdyby nie było metody jedź(), gdy obiekt miałby służyć tylko przekazaniu pewnej grupy obiektów dalej. Taka sytuacja ma miejsce dość często. Jest to typowy błąd architektoniczny, który trzeba rozwiązywać jak najwcześniej na etapie pisania kodu. Tworzenie obiektów transferowych (do przeniesienia innych obiektów) nie jest najlepszą praktyką.