Jednym z pierwszych elementów programowania obiektowego, poznawanym w ramach kursów programowania, jest hermetyzacja. Prawidłowo stosowana hermetyzacja ma znikomy wpływ na wydajność, a znacząco zwiększa przejrzystość tekstu źródłowego i niezawodność programu oraz ułatwia późniejszą refaktoryzację kodu.
Hermetyzację realizuje się typowo za pośrednictwem tak zwanych getterów oraz setterów. Na przykład, klasa reprezentująca osobę i przechowująca jej imię oraz nazwisko może zostać zahermetyzowana następująco:
class Osoba { std::string imie; std::string nazwisko; public: Osoba(const std::string &Imie, const std::string &Nazwisko); void UstawImie(const std::string &NoweImie); void UstawNazwisko(const std::string &NoweNazwisko); inline const std::string & Imie() const; inline const std::string & Nazwisko() const; };
Metody hermetyzujące UstawImie
oraz UstawNazwisko
mogą zostać dodatkowo zabezpieczone przed podaniem niepoprawnych danych. Podanie argumentu nie spełniającego pewnego zbioru reguł (dotyczących długości, wielkości znaków lub stosowanych symboli) może powodować zgłoszenie sytuacji wyjątkowej. Z jednej strony zabezpiecza to program przed podaniem niewłaściwych danych (co długofalowo przekłada się na jego bezpieczeństwo, likwidując na przykład jedno z potencjalnych źródeł ataków typu SQL injection), z drugiej — umożliwia przechwycenie sytuacji wyjątkowych w warstwie interfejsu użytkownika i zmuszenie operatora do poprawienia błędnych danych.
Hermetyzacja może też ewoluować w zależności od potrzeb. Na przykład, programista może stwierdzić, że imię i nazwisko osoby są zawsze ustalane razem, zatem dwie odrębne metody hermetyzujące
void UstawImie(const std::string &NoweImie); oid UstawNazwisko(const std::string &NoweNazwisko);
można zastąpić jedną wspólną:
void UstawImieNazwisko(const std::string &NoweImie, const std::string &NoweNazwisko);
Hermetyzacja umożliwiająca odczytywanie i modyfikowanie cech obiektów sprawia jednak problemy w oprogramowaniu o bardziej skomplikowanej architekturze. Przede wszystkim, w zasadzie uniemożliwia oszczędzanie pamięci przez zastąpienie kopiowania obiektu współdzieleniem referencji do obiektu. Jeżeli w jednym miejscu obiekt zostanie zmodyfikowany za pomocą metod hermetyzujących, zmiana przeniesie się do wszystkich pozostałych obiektów. Często dokładnie o to chodzi, ale jeśli zmiana miała być tylko lokalna, oszczędność skutkuje wprowadzeniem błędu do programu.
Jeszcze większy problem pojawia się w przypadku architektur wykorzystujących przetwarzanie współbieżne. Modyfikacja obiektu używanego przez wiele wątków wymaga stosowania mechanizmów synchronizacji; w przeciwnym przypadku program będzie korzystał z częściowo zmienionych danych lub ulegnie całkowitej awarii. Synchronizacja znacząco utrudnia programowanie i wprowadza narzut wydajnościowy.
Jedną z technik umożliwiających poradzenie sobie z tymi problemami jest stosowanie klas niemutowalnych (ang. immutable classes). Niemutowalność polega na braku możliwości wprowadzania zmian do istniejących obiektów. Najprostszym środkiem ku temu jest usunięcie metod hermetyzujących typu setter. W takim przypadku jedynym sposobem, by zmodyfikować obiekt, staje się stworzenie nowego, gdyż tylko konstruktor parametryczny ma prawo ustalenia zawartości pól.
Pierwszą zaletą niemutowalności jest bezpieczeństwo współdzielenia referencji do obiektu. Ponieważ mamy pewność, że stan obiektu nie zmieni się, możemy przechowywać w pamięci wskaźniki lub referencje do obiektu w różnych miejscach bez obawy, że dane przestaną być aktualne. Jest to szczególnie atrakcyjna perspektywa w językach programowania wyposażonych w mechanizm automatycznego gospodarowania pamięcią operacyjną, gdyż znika w nich pojęcie właściciela obiektu, odpowiedzialnego za zwolnienie pamięci.
Drugą zaletą jest brak konieczności synchronizowania zmian w obiekcie. Synchronizować trzeba co najwyżej zmienne przechowujące wskaźniki lub referencje albo kontenery przechowujące zbiór obiektów. Skraca to czas przebywania programu w sekcjach krytycznych: nawet, jeżeli zmiana stanu obiektu jest kosztowna czasowo, może przebiegać poza sekcją krytyczną. Dopiero podmiana starej referencji na nową wymusza wejście – na kilka cykli zegara mikroprocesora – w sekcję krytyczną.
Niestety, niemutowalność ma swoją ciemną stronę. W przypadku skomplikowanych obiektów, wprowadzanie drobnych zmian jest dużo bardziej kosztowne czasowo, niż w przypadku klas mutowalnych. Każda zmiana wiąże się bowiem z czasochłonnym stworzeniem całkiem nowego obiektu oraz – w chwili, gdy jest to możliwe – zniszczeniem dotychczasowego. Przy obydwu tych operacjach konieczne jest alokowanie i zwalnianie bloków pamięci, co negatywnie wpływa na wydajność oprogramowania pisanego we wszystkich językach programowania, włącznie z tymi, które dysponują mechanizmem automatycznego gospodarowania pamięcią operacyjną.
Dodatkowo, w takich właśnie językach programowania, niemutowalność może prowadzić do szybkiego wzrostu rozmiaru sterty, a więc dodatkowo spowalniać działanie całego systemu operacyjnego oraz pozostałych działających programów.
Niemutowalność jest zatem techniką bardzo przydatną w przypadku, gdy: