Pola i akcesory wewnątrz klasyZgodnie z zasadami programowania obiektowego pola klas nie powinny być bezpośrednio dostępne na zewnątrz. Należy jest zawsze opakowywać w akcesory: właściwości lub krótkie metody typu get i set. Z nich właśnie korzysta potem kod zewnętrzny, dzięki czemu nie może on (w dobrze napisanej klasie) niczego zepsuć poprzez - chociażby - ustawienie jakiegoś pola na nieprzewidzianą wartość.
Taka praktyka jest powszechnie przyjęta i raczej nie budzi wątpliwości. Inaczej jest z używaniem tychże pól lub akcesorów wewnątrz klasy, a więc w jej własnych metodach. Tutaj często mamy wybór: czy odwołać się do "gołego" pola, czy też poprzez odpowiednią właściwość/metodę.
Które podejście jest właściwsze? C#/.NET od wersji 3.0 zdaje się to rozstrzygać, umożliwiając półautomatyczne tworzenie właściwości:
Nie ma tutaj nie tylko bloków get i set, ale i ukrytą pod tą właściwością pola. Przy korzystaniu z tego feature'a żadnego dylematu więc nie ma.
Wydaje mi się jednak, że wybór nie jest taki oczywisty i że niekoniecznie należy używać akcesorów wewnątrz klasy. Argumentem przeciw, który od razu przychodzi do głowy, jest troska o wydajność - zwykle jednak przesadzona, bo proste gettery i settery są bez problemu rozwijane w miejscu użycia. Drugim 'ale' jest wygląd kodu w językach bez właściwości; zwłaszcza dotyczy to Javy, w której odpowiednik C#-owego:
roiłby się od getów. W końcu można by się jeszcze pokusić o uzasadnienie na wpół merytoryczne: skoro bądź co bądź prywatne pole jest składnikiem klasy do jej wyłącznej dyspozycji, to dlaczego metody miałyby obchodzić je dokoła zamiast odwoływać się doń bezpośrednio? A może jednak lepiej jest skorzystać z tej dodatkowej warstwy pośredniczącej (mogącej np. wykrywać jakieś błędy)?...
Na razie - mimo całkiem przyzwoitego doświadczenia w programowaniu w językach wszelakich - trudno jest mi na te pytania odpowiedzieć. Ostatnio aczkolwiek skłaniam się ku bezpośredniemu dostępowi do pól w metodach klas. Chętnie poznałbym jednak opinie innych koderów na ten temat.
Własności właściwościNiezastąpionym rodzajem składników klas są pola i metody, ale większość języków umożliwia też dodawanie również innych elementów. Ich głównym przeznaczeniem jest ułatwianie życia programiście i czynienie kodu bardziej czytelnym - pod takim rzecz jasna warunkiem, że są one odpowiednio użyte.
Do tej szuflady wpadają na przykład właściwości. Są to takie składniki klas, które wyglądają jak zmienne, lecz w rzeczywistości za pobieraniem i ustawianiem ich wartości może kryć się bardziej skomplikowany kod niż tylko proste odwołanie do zmiennej.
Spośród mainstreamowych języków prawdopodobnie najwygodniejszy mechanizm właściwości występuje w C#:
Ważną cechą właściwości jest też to, aby możliwe było tworzenie takowych w wersji tylko-do-odczytu. Tutaj wystarczy pominąć frazę set.
Dla kontrastu w Javie właściwości nie ma... w ogóle :) Mimo to narzędzia do wizualnego tworzenia aplikacji radzą sobie całkiem nieźle w odczytywaniu funkcjonalności komponentów, posługując się tzw. JavaBeans. W skrócie jest to pewien mechanizm oparty na refleksjach (odczytywaniu informacji o klasie w kodzie programu) i specyficznej konwencji nazywania metod dostępowych. Oczywiście nawet najlepsze nazwy nie zrekompensują dziwnej składni takich "właściwości", ale sam pomysł trzeba uznać za dosyć udany.
W Delphi w zasadzie też trzeba tworzyć metody dostępowe, lecz można je podpiąć pod faktyczne właściwości:
Możliwe jest też, jak widać, bezpośrednie przełożenie właściwości na odpowiednią zmienną, która przechowuje jej wartość. Całkiem niezłe, chociaż wymaga to sporo pisania - co aczkolwiek jest charakterystyczną cechą Delphi :)
A cóż z naszym ulubionym językiem, czyli C++? Właściwości w nim oczywiście nie ma, chociaż w Visual C++ na przykład istnieje deklaracja __declspec(property), mająca podobne możliwości do słówka property z Delphi. Jeśli zaś chodzi o przenośne rozwiązanie, to można sobie wyobrazić obiekt "opakowujący" wskaźnik na pole lub metodę, który działałby jako symulacja właściwości - na przykład:
Przy użyciu kilku sztuczek z szablonami, wspomnianymi wskaźnikami i być może jakimś rozwiązaniem dla delegatów, taka implementacja jest zapewne możliwa. Istnieje aczkolwiek dość poważna niedogodność: taki obiekt opakowujący należałoby zainicjować w konstruktorze:
zatem "deklaracja" naszej "właściwości" rozbita by została na dwa miejsca. A właściwie - na co najmniej dwa miejsca, bo w przypadku większej ilości konstruktorów rzecz wygląda nawet gorzej.
Technika ta będzie przydatna wtedy, gdy pola będziemy mogli inicjalizować w momencie ich deklaracji, co jest proponowane w C++0x. Wygląda więc na to, że znów dochodzimy do konkluzji, że język C++ pozostaje daleko w tyle w stosunku do swych młodszych braci. Ale czegóż można oczekiwać po staruszku, który w tym roku będzie obchodził swoje ćwierćwiecze? :) Zanim więc otrzyma on jakże konieczny lifting, musimy żyć z niezbyt wygodnymi, ale koniecznymi, metodami dostępowymi.
Hmm... A przecież chciałem dzisiaj ponarzekać raczej na Javę... No cóż, nie wyszło ;-)
Bolączki C++ #4 – WłaściwościW teorii OOPu klasa może składać się wielu różnych rodzajów elementów. Mamy więc pola, metody, właściwości, operatory, typy, zdarzenia czy nawet sygnały (cokolwiek to oznacza). Z drugiej reprezentacja obiektu w pamięci operacyjnej działającego programu to niemal wyłącznie wartości jego pól (z drobną poprawką na ewentualną tablicę metod wirtualnych).
To są dwie skrajności, a między nimi mam wszystkie obiektowe języki programowania. Jedne oferują w tym zakresie więcej, inne mniej. Weźmy na przykład C++.
Oprócz niezbędnych pól i metod pozwala on definiować przeciążone operatory i typy wewnętrzne. Nie posiada za to niezwykle przyjemnego "cukierka składniowego", czyli właściwości. Z punktu widzenia programisty właściwości to takie elementy interfejsu klasy, który wyglądają jak pola. Różnica polega na tym, że dostęp do właściwości nie musi oznaczać bezpośredniego odwołania do pamięci, lecz może mu towarzyszyć dodatkowy kod - na przykład sprawdzający poprawność ustawianej wartości.
Prawdopodobnie najbardziej elastyczny mechanizm właściwości wśród popularnych języków programowania ma C#. Tam kod wykonywany przy pobieraniu i ustawianiu właściwości pisze się bezpośrednio w jej deklaracji:
// właściwość - mianownik ułamka
public int Denominator
{
get { return denom; }
set
{
if (value == 0) throw new DivideByZeroException();
denom = Math.Abs(value);
num *= Math.Sign(value);
}
}
}
Nieco gorzej jest w Delphi czy Visual C++, gdzie istnieje deklaracja __declspec(property). Tam trzeba napisać odpowiednie metody służące pobieraniu/ustawianiu danej wartości (akcesory) i wskazać je w deklaracji właściwości.
Natomiast w czystym C++ rzeczone akcesory - metody Get/Set - stosowane bezpośrednio są jedynym wyjściem. Niezbyt ładnym rzecz jasna.
Bez właściwości można się obyć, a ich wprowadzenie do języka pewnie nie byłoby takie proste. Pomyślmy na przykład, jak miałyby się one do wskaźników i referencji: o ile pobranie adresu właściwości nie ma sensu, o tyle przekazywanie jej przez referencję byłoby z pewnością przydatne.
Dlatego chociaż akcesory wyglądają brzydko, pewnie jest przez długi czas będą jedyną opcją. Na pocieszenie dodam, że programiści Javy są pod tym względem w identycznej sytuacji :)