Enkapsulacja - Programowanie jest łatwe

Gdy przychodzi potrzeba opakowania warstwy danych w inną warstwę danych, to mamy do czynienia z enkapsulacją, o której słów kilka w poniższym artykule.

Enkapsulację można zwizualizować jako wkładanie mniejszych pudełek do jednego, większego

Użyłem czasownika pakować, ponieważ "wizualnie" chyba najbardziej oddaje sens enkapsulacji. Najprostszym przykładem niech będzie ciąg znaków. String mieści się w tablicy, której ostatni element zakończony jest wartością 0. Aby sprawdzić jaką ma długość, należy ustalić na jakiej pozycji w tablicy znajduje się wartość zerowa. Można tego dokonać np. pętlą, która iteruje tak długo, dopóki nie napotka na koniec tablicy, a wartość iteratora jest wielkością stringu. Jeżeli zajdzie potrzeba, aby wyeliminować wielokrotne wykonywanie tej pętli sprawdzającej długość ciągu znaków, można przechować tę wartość w zmiennej i "dołączyć" ją do konstrukcji, która obok samego ciągu znaków, będzie udostępniać także jego długość. Jeżeli obie te informacje zawarte są w jednej strukturze, zachodzi enkapsulacja.

Najprostsza topografia enkapsulacji wyglądałaby następująco: Enkapsulacja logiczna. W momencie, gdy potrzebujemy ułożyć wiele danych w jedną strukturę, aby łatwiej tworzyć algorytmy, korzystające z tych danych. Poniżej klasyczny przykład z prostokątem. Prostokąt posiada długość i szerokość, a także przekątną (oraz wiele innych właściwości, jednak zatrzymajmy się na tych trzech). Można stworzyć prostokąt w następujący sposób:

#include <math.h> // dla funkcji pow() czyli potęga oraz sqrt() czyli pierwiastek kwadratowy
int prostokat_dlugosc = 10;
int prostokat_szerokosc = 5;
int prostokat_przekatna = sqrt(pow(protokat_dlugosc) + pow(prostokat_szerokosc));

Wszystko pięknie, jednak kiedy zajdzie potrzeba przekazania tych wartości do funkcji, będziemy musieli przekazywać osobno 3 parametry. Dodatkowo problem zajdzie, gdy będziemy chcieli stworzyć zmienne dla kolejnych prostokątów. Między innymi do radzenia sobie z takimi problemami powstały struktury (klasy, obiekty), które pozwalają zamykać zmienne w jednym logicznym pojemniku. Poniżej przykład dla języka C, gdzie najpierw trzeba zdefiniować typ takiej struktury:

typedef struct {
  int dlugosc;
  int szerokosc;
  int przekatna;
} prostokat_t; // tworzę typ prostokat_t, który jest strukturą zawierającą trzy zmienne typu int

Prostakat_t moj_prostakat;
moj_prostakat.dlugosc = 10;
moj_prostakat.szerogosc = 5;
moj_prostakat.przekatna = sqrt(pow(moj_prostakat.dlugosc) + pow(moj_prostakat.szerokosc));

Powyżej zamknąłem trzy zmienne do jednej struktury. Teraz mogę przekazywać jedynie tę strukturę jako argument do funkcji. Jest to wygodniejsze, kod jest bardziej przejrzysty, a co najważniejsze, można lepiej separować (w sensie logicznym) fragmenty algorytmów.

  1. Enkapsulacja ze względu na oszczędność pamięci. Ponieważ najmniejszą porcją danych, którą przetwarza procesor jest bajt, w niektórych przypadkach może okazać się to ogromnym marnotrawstwem. Wyobraźmy sobie, że posiadamy kokpit, w którym jest 5 przycisków. Każdy przycisk może być włączony albo wyłączony. Informacje o tych przyciskach chcemy przekazywać raz na sekundę do innego komputera. Jeżeli utworzymy zmienną dla każdego z tych przycisków i użyjemy najmniejszego rozmiaru zmiennej char, to co sekundę będziemy transmitować 5 bajtów danych. Ponieważ interesuje nas informacja 0 albo 1, która zmieści się w jednym bicie zmiennej, na każdej "marnujemy" 7 bitów (char zajmuje 1 bajt = 8 bitów). Informacje o wszystkich przyciskach (5 bitów) zmieszczą się w jednej zmiennej typu char. Należy "upchać" te informacje w jednej zmiennej, gdy to zrobimy, zamiast wysyłania 5 bajtów co sekundę wystarczy 1 bajt czyli ogromna oszczędność. W tym celu stosuje się operatory maski binarne. To może być temat na osobny artykuł, jednak aby nie być gołosłownym, zaprezentuję tutaj, jak mogłoby to wyglądać:
char przyciski = 0; // wszystkie wyłączone

przyciski = przyciski | (1 << 3); // włączam przycisk 4
przyciski = przyciski & ~(1 << 3); // wylączam przycisk 4

if (przyciski & 1 (<< 3) ) {
  // przycisk włączony
}
else {
 /// przycisk wyłączony
}

W powyższym przykładzie tworzę zmienną char przyciski. W kolejnej linii nakładam maskę o wartości 8 (binarnie 1000) i wykonuję binarną alternatywę, która ustawi wartość 4 bitu na 1, nie zmieniając pozostałych wartości. Pisemnie wyglądałoby to tak:


  00000000
| 00000100
  ----------
  00001000

Trzecia linijka tworzy maskę odwrotną do poprzedniej (binarnie 11110111) i wykonuje binarną koniunkcję, która "gasi" bit na pozycji 4 ustawiając go na zero. Następnie pokazuję jak można sprawdzić, czy konkretny bit (w tym przypadku 4) jest ustawiony na 1. Co do zapisu 1 << 3. Jest to przesunięcie bitowe wartości 1 o 3 bity w prawo, czyli na czwartą pozycję.

Taka forma enkapsulacji wykorzystywana jest przy transmisji danych, np. w protokole TCP/IP, gdzie każdy bit ma znaczenie i każdy bajt wykorzystany jest do absolutnego maximum.

Procesem odwrotnym jest dekapsulacja, czyli rozpakowywanie danych z powrotem do ich pierwotnej formy. Inne określenia tego procesu to hermetyzacja i kapsułkowanie.

Jak Ty korzystasz z enkapsulacji?

Dodano: 2018-04-18 06:44 przez Piotr Poźniak

Słowniczek , enkapsulacja , hermetyzacja , kapsułkowanie , struktury , maski binarne ,
Piotr Poźniak
O autorze:

Programuję od ponad 15 lat. Prowadzę software house. Angażuję i zachęcam wszystkich do programowania w ramach inicjatywy Programowanie jest łatwe.