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.