05. Hermetyzacja danych

05. Hermetyzacja danych

 Paweł Kruczkowski
Paweł Kruczkowski
00:00
24.10.2006
98060 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