Obiekty mają metody. Tak, w tym stwierdzeniu nie należy doszukiwać głębokiego sensu – jest ono po prostu prawdziwe :) Gdy mówimy o metodach obiektów czy też klas, zwykle mamy jednak na myśli tylko jeden ich rodzaj: metody instancyjne. W wielu językach programowania nie jest to aczkolwiek ich jedyny rodzaj – z takim przypadkiem mamy do czynienia chociażby w Pythonie.
Jak zawsze metody instancyjne są domyślnym typem, który tutaj jest dodatkowo zaznaczony obecnością specjalnego parametru – self
– występującego zawsze jako pierwszy argument. To odpowiednik this
z języków C++, C# czy Java i reprezentuje instancję obiektu, na rzecz której wywoływana jest metoda:
Fakt, że musi być on jawnie przekazany, wynika z zasad tworzenia zmiennych w Pythonie. Nie muszą być one jawnie deklarowane. Dlatego też odwołanie do pola obiektu jest zawsze kwalifikowane, gdyż przypisanie do _value
zamiast self._value
stworzyłoby po prostu zmienną lokalną.
Istnieją jednak takie metody, które nie operują na konkretnej instancji klasy. Typowo nazywa się je statycznymi. W Pythonie nie posiadają one parametru self
, lecz są opatrzone dekoratorem @staticmethod
:
Statyczną metodę można wywołać zarówno przy pomocy nazwy klasy (Counter.format_string()
), jak i jej obiektu (Counter().format_string()
), ale w obu przypadkach rezultat będzie ten sam. Technicznie jest to bowiem zwyczajna funkcja umieszczona po prostu w zasięgu klasy zamiast zasięgu globalnego.
Mamy wreszcie trzeci typ, mieszczący się w pewnym sensie pomiędzy dwoma opisanymi powyżej. Nie wydaje mi się jednak, żeby występował on w żadnym innym, popularnym języku. Chodzi o metody klasowe (class methods). Nazywają się tak, bo są wywoływane na rzecz całej klasy (a nie jakiejś jej instancji) i przyjmują ową klasę jako swój pierwszy parametr. (Argument ten jest często nazywany cls
, ale jest to o wiele słabsza konwencja niż ta dotycząca self
).
W celu odróżnienia od innych rodzajów, metody klasowe oznaczone są dekoratorem @classmethod
:
Podobnie jak metody statyczne, można je wywoływać na dwa sposoby – przy pomocy klasy lub obiektu – ale w obu przypadkach do cls
trafi wyłącznie klasa. Tutaj akurat będzie to Counter
, lecz w ogólności może to być także klasa pochodna:
Powyższy kod – będący przykładem dodatkowego sposobu inicjalizacji obiektu – to dość typowy przypadek użycia metod klasowych. Korzystanie z nich wymaga aczkolwiek nieco wprawy w operowaniu pojęciami instancji klasy i samej klasy oraz ich poprawnego rozróżniania.
W gruncie rzeczy nie jest to jednak nic trudnego.
Trzeci typ funkcji wydaje mi się przerostem formy nad treścią, różni się to czymś od zwykłej metody statycznej z jawnym podaniem nazwy klasy? Lub inaczej, daje to jeszcze jakieś inne ciekawsze opcje?
Metody klasowe występują w prawie każdym języku dynamicznym.
Na pewno mają je języki Scala i Ruby.
@SebaS86: Praktyczny przykład będzie tu dobry, aczkolwiek będzie wymagał trochę dodatkowego info.
Oto w Google App Engine mamy rodzaj obiektowej bazy danych, do której pythonowy interfejs wygląda mniej więcej tak:
Zamiast SQL mamy więc wywołania metod klasowych (Person.all
), które w środku przekładają się na tworzenie obiektu klasy db.Query
. Jeśli teraz chcemy zrobić trochę sprytniejszą wersję funkcji all
, która implementuje typowy wzorzec “flagi dla obiektu usuniętego” dla naszych encji, to możemy to zrobić tak:
W ten sposób wszystkie modele dziedziczące z BaseModel
będą automatycznie traktować swoje obiekty z flagą active == False
jako logicznie usunięte. W przypadku metody statycznej nie byłoby to możliwe bez ponownego implementowania metody all
w każdej klasie pochodnej.
(Disclaimer: kod pisałem w zasadzie z pamięci, więc nie ręczę za poprawność)
Czyli w skrócie taki polimorfizm na poziomie klasy i statyczne funkcje wirtualne?
Obj-C umożliwa zrobienie czegoś takiego równie łatwo, nie ma oczywiście funkcji klasowej jako takiej, można uzyskać jednak ten sam efekt korzystając ze zwykłych metod statycznych, podstawowych reguł przesłaniania, wykorzystując dodatkowo metody class, self i super.
W sumie chyba kiedyś brakowało mi tego w C++ nie pamiętam jednak do czego… niestety tam utrudnieniem jest dość słabo rozwinięte RTTI.