As you probably know very well, in Python you can add properties to your classes. They behave like instance fields syntactically, but under the hood they call accessor functions whenever you want to get or set the property value:
Often – like in the example above – properties are read-only, providing only the getter method. It’s very easy to define them, too: just stick a @property
decorator above method definition and you’re good to go.
Occasionally though, you will want to define a read-write property. (Or read-delete, but those are very rare). One function won’t cut it, since you need a setter in addition to getter. The canonical way Python docs recommend in such a case (at least since 2.6) is to use the @property.setter
decorator:
Besides that I find it ugly to split a single property between two methods, this approach will annoy many static code analyzers (including PEP8 checker) due to redefinition of x
. Warnings like that are very useful in general, so we certainly don’t want to turn them off completely just to define a property or two.
So if our analyzer doesn’t support line-based warning suppression (like, again, pep8), we may want to look for a different solution.
Zgodnie 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.
Niezastą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:
[delphi]type TFoo = class
private
FNumber : Integer;
procedure SetNumber(const ANumber : Integer);
public
// właściwość
property Number : Integer read FNumber write SetNumber;
end;[/delphi]
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 ;-)
W 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:
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 :)