05. Hermetyzacja danych

05. Hermetyzacja danych

 Paweł Kruczkowski
Paweł Kruczkowski
00:00
24.10.2006
9 komentarzy
97357 wyświetleń

Jedną z najważniejszych cech dobrego programowania obiektowego jest dostarczenie użytkownikowi takiego programu, który będzie nadawał się do dalszej rozbudowy bez poprawy tego, co już zostało napisane. Jak taki cel osiągnąć? Po prostu poprzez zaprojektowanie klasy w taki sposób, aby zabezpieczyć ją przed ingerencją z zewnątrz. Innymi słowy, należy ukryć wewnętrzną strukturę definiowanej przez nas klasy. Taki mechanizm nazywamy hermetyzacją. Często pojęcie to określane jest enkapsulacją, bądź po prostu ukrywaniem danych.

Ukrywanie wewnętrznej struktury obiektu jest bardzo ważne z kilku powodów. Po pierwsze, obiekt taki jest odizolowany, a więc nie jest narażony na celowe, bądź niezamierzone działanie ze strony użytkownika. Po drugie, obiekt ten na pewno jest chroniony od niepożądanych referencji ze strony innych obiektów. Po trzecie, obiekt taki – jeśli jest to tylko możliwe, nie wpływa na zmiany, czy jakieś małe korekty wprowadzone w implementacji. Po prostu obie strony nie kolidują wówczas ze sobą. I po czwarte, dzięki ukryciu wewnętrznej struktury obiektu, można uzyskać jego przenośność. Innymi słowy, zastosować definiującą go klasę w innym fragmencie kodu, czy też programie.

A więc jak widzimy, poznane przez nas pojęcie hermetyzacji odgrywa w programowaniu obiektowych ogromne znaczenie. Przejdźmy więc od razu do przykładu, który będzie dotyczył enkapsulacji.

public class Punkty
{
    private int a;
    private int b;
 
    //definicja metod, ktore beda pobierac wartosci a i b
    public int GetA()
    {
        return a;
    }
 
    public int GetB()
    {
        return b;
    }
    //definicja metod, ktore beda zmieniac wartosci a i b
    public void SetA(int nowyA)
    {
        this.a = nowyA;
    }
    public void SetB(int nowyB)
    {
        this.b = nowyB;
    }
}
 
public class Glowna
{
    static void Main()
    {
        //deklarujemy obiekt p klasy Punkty
        Punkty p = new Punkty();
        //przypisanie odpowiednich wartosci zmiennym w obiekcie p
        p.SetA(10);
        p.SetB(20);
 
        //pobranie odpowiednich wartosci ze zmiennych obiektu p
        int wart1 = p.GetA();
        int wart2 = p.GetB();
        System.Console.WriteLine("Współrzędne punktu wynoszą: {0}.{1}", wart1, wart2);
     }
}

W klasie Punkty zadeklarowaliśmy sobie dwie zmienne składowe (a i b), które są liczbami całkowitymi i będą stanowić współrzędne danego punktu. Zadeklarowaliśmy sobie również 2 metody (GetA() oraz GetB()), które będą pobierać wartości, jakie są przechowywane w tych zmiennych. Klasa Punkty dostarcza nam również dwie metody (SetA() oraz SetB()), dzięki którym będziemy przypisywać zmiennym składowym nowe wartości.

Po skompilowaniu i uruchomieniu powyższego przykładu otrzymamy nastepujące wyniki:

Powyższy program jest napisany w sposób dobrze nam już znany. Pewnie wielu z nas zastanawia się, gdzie tutaj ta nasza enkapsulacja, gdzie ukrywanie danych etc. Już spieszę z odpowiedzią. Powyższy przykład jest jedynie dobrym wstępem do ukazania hermetyzacji danych, gdyż w języku C# nie musimy tworzyć opisanych wyżej metod, które zapewniają nam dostęp do prywatnych zmiennych. Możemy bowiem zdefiniować specjalne metody, które będą nam służyć do odczytu i zmian wartości tych zmiennych. Te specjalne metody będziemy nazywać właściwościami.

Poprawmy więc powyższy przykład, poprzez użycie w nim właściwości. W ten sposób nasz program będzie miał właściwy styl programowania obiektowego. A więc do dzieła:

public class Punkty
{
    //prywatne zmienne a i b
    private int a;
    private int b;
 
    //wlasciwosci A i B
    public int A
    {
        get
        {
            return a;
        }
        set
        {
            a = value;
        }
    }
 
    public int B
    {
        get
        {
            return b;
        }
        set
        {
            b = value;
        }
    }
}
 
public class Glowna
{
    static void Main()
    {
        //deklarujemy obiekt p klasy Punkty
        Punkty p = new Punkty();
        //przypisanie odpowiednich wartosci zmiennym w obiekcie
        p.A = 10;
        p.B = 20;
 
        //pobranie odpowiednich wartosci ze zmiennych obiektu p
        int wart1 = p.A;
        int wart2 = p.B;
        System.Console.WriteLine("Współrzędne punktu wynoszą: {0}.{1}", wart1, wart2);
    }
}

Powyższy program zwraca identyczne wyniki jak poprzedni przykład, gdyż robi to samo, ale…został napisany inaczej. Jego styl jest zgodny z dobrym programowaniem obiektowym. Zastosowaliśmy bowiem w nim właściwości.

W klasie Punkty zadeklarowaliśmy dwie zmienne prywatne (a i b), do których dostęp będziemy mieli nie przez zwykłe metody, a poprzez specjalne. Właściwości, bo o nich jest tutaj mowa pełnią w naszym programie 2 istotne funkcje. Po pierwsze, udostępniają użytkownikom prosty interfejs, który wbrew pozorom wygląda jak zwykła składowa (A i B w naszym przykładzie). Po drugie, właściwości umożliwiają ukrycie danych, co jest – jak już wieloktotnie zostało tutaj podkreślone – jedą z cech dobrego programowania obiektowego.

Jak już zauważyliście, właściwość (zarówno A jak i B) posiada w swojej deklaracji dwa nowe pojęcia: get i set. Od tej pory będziemy je nazywać akcesorami. Akcesor get jest podobny do metody, która zwraca obiekt tego samego typu co właściwość (w naszym przykładzie, akcesor ten odpowiada za odczytanie wartości jakie aktualnie przechowują zmienne prywatne a i b). Akcesor set pozwala ustawić wartość właściwości i przypomina na pewno metodę, która zwraca typ void. W akcesorze set używamy słowa: value, które reprezentuje argument, którego to wartość przyjmuje i przechowuje właściwość (w naszym przykładzie akcesor ten odpowiada za przypisanie odpowiednich wartości – 10 oraz 20 – prywatnym zmiennym składowym: a i b).

Po skompilowaniu i uruchomieniu powyższego programu otrzymamy następujące wyniki:

W artykule o przekazywaniu parametrów napisaliśmy między innymi następujący program:

public class Czas
{
     private int _rok;
     private int _miesiac;
     private int _dzien;
 
     //konstruktor klasy Czas
     public Czas(System.DateTime dt)
     {
         _rok = dt.Year;
         _miesiac = dt.Month;
         _dzien = dt.Day;
     }
       
     public void PobierzCzas(int r, int m, int d)
     {
         r = _rok;
         m = _miesiac;
         d = _dzien;
     }
 }
 
 public class Glowna
 {
     static void Main()
     {
     int rok = 2004;
     int miesiac = 11;
     int dzien = 10;
     //obiekt klasy DateTime
     System.DateTime dt = new DateTime(1982, 8, 23);
     //obiekt klasy Czas 
     Czas czas = new Czas(dt);
 
     czas.PobierzCzas(rok, miesiac, dzien);
 
     System.Console.WriteLine("Mamy następującą datę: {0}-{1}-{2}", rok, miesiac, dzien);
 
 
}
}

Po zapoznaniu się z właściwościami, na pewno każdy z nas pokusiłby się o użycie w nim właśnie tego mechanizmu, który niewątpliwie stanowi prawidłowy styl programowania obiektowego. Użyjmy więc akcesorów get i set, aby powyższy przykład zawierał w sobie hermetyzację danych. A więc:

public class Czas
{
     //deklaracji zmiennych prywatnych
     private int _rok;
     private int _miesiac;
     private int _dzien;
 
     //deklaracja wlasciwosci
     public int Rok
     {
         get
         {
             return _rok;
         }
         set
         {
             _rok = value;
         }
     }
 
    public int Miesiac
    {
         get
         {
             return _miesiac;
         }
         set
         {
             _miesiac = value;
         }
    }
 
    public int Dzien
    {
         get
         {
             return _dzien;
         }
         set
         {
             _dzien = value;
         }
    }
 
    //konstruktor klasy Czas
    public Czas(System.DateTime dt)
    {
        _rok = dt.Year;
        _miesiac = dt.Month;
        _dzien = dt.Day;
    }
}
 
public class Glowna
{
     static void Main()
     {
         //obiekt klasy DateTime
         System.DateTime dt = new DateTime(1982, 8, 23);
         //obiekt klasy Czas 
         Czas czas = new Czas(dt);
 
         int dzien = czas.Dzien;
         int miesiac = czas.Miesiac;
         int rok = czas.Rok;
 
         System.Console.WriteLine("Mamy następującą datę: {0}-{1}-{2}", rok, miesiac, dzien);
 
         dzien = dzien + 6;
         miesiac = miesiac + 2;
         rok = rok + 24;
        
         czas.Dzien = dzien;
         czas.Miesiac = miesiac;
         czas.Rok = rok;  
     
         System.Console.WriteLine("Po aktualizacji mamy: {0}-{1}-{2}", rok, miesiac, dzien);
     }
}

Jak już wyżej zostało napisane, akcesor get podobny jest do metody, która zwraca obiekt tego samego typu co dana właściwość. W poniższym fragmencie kodu:

        get

        {
            return _rok;
        }

zwracana jest lokalna zmienna składowa, która jest typu intiger (typu całkowitego). Może to być również wartość pobierana na przykład z bazy danych, bądź w inny sposób obliczana. Niemniej jednak, zawsze gdy w kodzie odczytywana jest wartość właściwości, to wywoływany jest akcesor get.

W powyższym przykładzie, akcesor ten wywoływany jest w następujących liniach kodu:

  Czas czas = new Czas(dt);
 
        int dzien = czas.Dzien;
        int miesiac = czas.Miesiac;
        int rok = czas.Rok;
Dlaczego? Ponieważ pobieramy wartości właściwości: Dzien, Miesiac oraz Rok obiektu czas, a następnie przypisujemy je zmiennym (odpowiednio: dzien, miesiac oraz rok).

Akcesor set pozwala natomiast ustawić wartość właściwości i przypomina nam metodę, która zwraca void. Poniżej:

        

   set
         {
             _rok = value;
         }

wartość właściwości przechowywana jest w prywatnej zmiennej _rok, a nasz akcesor mógłby również przypisywać wartość w bazie danych bądź innych zmiennych.

W powyższym przykładzie, akcesor ten wywoływany jest w następującyhc liniach:

         dzien = dzien + 6;
         miesiac = miesiac + 2;
         rok = rok + 24;
        
         czas.Dzien = dzien;
         czas.Miesiac = miesiac;
         czas.Rok = rok;  

W tym miejscu bowiem przypisujemy odpowiednie wartości właściwościom.

Po skomplilowaniu powyższego przykładu otrzymamy następujące wyniki:

Z powyższych programów możemy wywnioskować następujące cechy właściwości:

  • Definiuje się je podobnie jak metody, aczkolwiek nie posiadają argumentów ani nawiasów
  • Zawierają dwie sekcje: get i set (nazywamy je akcesorami). Oczywiście może zaistnieć sytuacja, że będziemy tylko odczytywać wartości ze zmiennych (będzie jedynie akcesor get w naszym kodzie), bądź przypisywać tylko wartości zmiennym prywatnym (będzie jedynie akcesor set).
  • Jeżeli w naszym kodzie istnieje tylko akcesor get, to zmienna, do której dana właściwość zapewnia dostęp jest zmienną tylko do odczytu.
  • Używamy je podobnie jak zwykłe zmienne, stosując znak podstawiania wartości (np. p.A = 10)./li>

W niniejszym artykule powiedzieliśmy sobie o jednym z najważniejszych mechanizmów, który powinniśmy stosować w naszym kodzie, aby był on przejrzysty, logiczny, po prostu dobry.

Za tydzień kolejna ważna rzecz, a mianowicie wprowadzimy sobie pojęcia dziedziczenia oraz polimorfizmu w języku obiektowym C# 2.0.


Spodobał Ci się ten artykuł? Podziel się z innymi!

Źródło:

Polecamy również w kategorii Kurs C#, cz. II

Komentarze

  • kulluk 15:22 26.07.2007

    Bardzo dobry artykuł, bardzo dobre przykłady. Pozdr dla autora.

  • Pitrol 19:51 22.04.2008

    Kurs jest bardzo ambitnie zrobiony ,tak więc duże podziękowania dla autora:)

  • Ka 19:42 09.11.2008

    <p>Obawiam sie że na stronie 6 wkradła się nieścisłość. Akcesor set jest wywoływany jedynie w przytoczonych ostatnich trzech liniach kodu, a nie we wszystkich 6. </p>

  • hmm 20:02 08.03.2009

    <p>enkapsulacja to co innego niż hermetyzacja... nie nalezy sie sugerowac wikipedia rowniez:D</p>

  • ehe 18:12 17.06.2009

    <p>może mi ktoś powiedzieć, po co na stronie 4 są te linie:</p> <p><span><code>[size= 10pt; font-family: "Courier New"]<span>      [/size]<span>   </span>czas.Dzien = dzien;</span><br /> [size= 10pt; font-family: "Courier New"]<span>         [/size]czas.Miesiac = miesiac;</span><br /> [size= 10pt; font-family: "Courier New"]<span>         [/size]czas.Rok = rok;<span> </span></span></code></span></p> <p>bo ja nie rozumie. po co obrabiać zmienne, którym już nadalismy nową wartość, i po obróbce w sumie nic się nie zmienia? wykomentowałem te linie w kodzie przez // i program się tak samo skompilował i dał ten sam wynik co z tymi liniami przetwarzającymi dane.</p> <p>więc... po co one są?</p> <p>po to żeby wykorzystać funkcję SET i przenieść ich wartość na właściwości w klasie Czas? to wszystko fajnie i w ogóle, ale w WriteLine przecież piszemy wartości zmiennych lokalnych. Przydało by się, aby autor pokazał jak wydobywać wartości bezpośrednio ze zmiennych ukrytych w klasie.</p> <p>aby wykorzystać tę operację do czegokolwiek, ostatni wiersz kodu powinien wyglądać tak:</p> <p> System.Console.WriteLine("\nPo aktualizacji mamy datę: {0}-{1}-{2}", czas.Rok, czas.Miesiac, czas.Dzien);</p> <p>bo wtedy tu pobieramy bezpośrednio wartości z klasy Czas.</p>

  • crv 13:35 22.07.2010

    <span style="font-family: "Courier New"; color: #1e1e1e; font-size: small;">[size= 13px; line-height: 18px]Te 4 linijki są po to by uaktualnić dane w obiekcie czas. Oczywiście można było wyciąć poprzednie 4 linijki i zmiany dokonać teraz tutaj a przy wypisywaniu posłużyć się kodem jak post wyżej jednak w tutaj nie chodzi o pisanie świetnych, zoptymalizowanych programów tylko o pokazanie pewnych mechanizmów. Trzeba przyjąć iż kurs czytają również początkujący w tej dziedzinie więc wg. mnie nie powinno im się mieszać w głowach pokazując po kolei poszczególne składniki języka w jak najprostszy sposób.[/size]</span> <span style="font-family: "Courier New"; color: #1e1e1e; font-size: small;">[size= 13px; line-height: 18px]pozdro [/size]</span>

  • Andrew viewx7 17:51 24.10.2010

    Świetny artykuł, bardzo zrozumiale napisany.

  • Mati 13:20 20.01.2014

    "enkapsulacja to co innego niż hermetyzacja... nie nalezy sie sugerowac wikipedia rowniez:D" Enkapsulacja i hermetyzacja to akurat to samo - w tym przypadku ciocia Wikia ma rację :) Dodatkowym synonimem jest hermetyczność, zatem pojęcia: hermetyzacja, enkapsulacja i hermetyczność można używać zamiennie (no i oczywiście dochodzi ukrywanie danych). Pzdr

  • Aleksander 10:21 16.02.2016

    Od siebie dodałbym tylko, że w tak prostym przypadku użycia akcesorów można je zastąpić skrótem { get; set; } pozbywając się przy okazji pól prywatnych - rezultat będzie ten sam

Skomentuj

Autor