czwartek, 10 listopada 2011

Jak programować obiektowo cz. 3 - metody klas I

po długiej przerwie

Trochę to potrwało zanim znalazłem chwilę czasu, żeby coś napisać, ale mam nadzieję, że warto było czekać:) Postaram się, żeby kolejne wpisy z tej serii pojawiały się częściej, a że w najbliższym czasie nie planuję już żadnych długoterminowych prac, myślę, że jest to założenie, które uda mi się zrealizować.
Dobra, bez dłuższego przynudzania, zaczynam. Dzisiaj kilka słów o metodach, czyli:

czym jest metoda?

Metody klasy definiują operacje, które można wykonać na jej obiektach.
Możemy wyróżnić kilka głównych rodzajów metod:
  • metody abstrakcyjne
  • gettery i settery
  • metody przeciążone
  • metody przeciążające operatory
  • metody statyczne
  • metody definiujące funkcjonalność
  • metody specjalne
Poniżej krótsze bądź dłuższe omówienie poszczególnych rodzai.

metody abstrakcyjne

Są to deklaracje metod (bez implementacji ich ciała), które występują w klasach abstrakcyjnych oraz interfejsach. Muszą one zostać zdefiniowane w klasie (nie abstrakcyjnej), która implementuje dany interfejs bądź rozszerza klasę abstrakcyjną.
Dzięki nim jesteśmy pewni, że wszystkie klasy potomne (nie abstrakcyjne) posiadają pożądaną funkcjonalność.

gettery i settery

Są to metody pozwalająca na pobieranie/ustawianie wartości atrybutu obiektu.
Od niepamiętnych czasów toczą się zażarte dyskusje na temat tego, czy powinny być stosowane, czy nie i trwają one nadal. Nie chcę rozpoczynać tutaj kolejnej polemiki, jednak ze swojej strony uważam, że z pewnością nadużywanie ich (np. tworzenie zestawu setów i getów dla każdego atrybutu) nie jest zbyt dobrym rozwiązaniem z punktu widzenia projektu. Dodatkowo można zastanawiać się, co w takich przypadkach z enkapsulacją, która jest jednym z paradygmatów programowania obiektowego.
Nie uważam jednak, że powinno się zaprzestać ich używania w ogóle, jednak należy to czynić rozsądnie i przede wszystkim świadomie. Jeżeli jakaś metoda jest niezbędna i zdefiniowanie jej jest poprawne z punktu widzenia projektu, to nie należy na siłę implementować innych rozwiązań tylko dlatego, że 'gettery i settery są złe', bo nie są. Złe jest ich błędne używanie.
Bardzo ciekawy artykuł na ten temat można znaleźć tutaj.

metody przeciążone

Są to metody posiadające taką samą nazwę tylko różny zbiór lub/i typy parametrów.
W PHP niestety nie jest możliwe przeciążanie metod, więc w przypadku, gdy potrzebujemy takiej funkcjonalności, jesteśmy zmuszeni zaimplementować ją na własną rękę.
W skrócie sprowadza się do używania w metodzie instrukcji warunkowych, które po określeniu typu danego parametru wykonują pożądane instrukcje.

metody przeciążające operatory

Są to metody, które pozwalają zdefiniować w jaki sposób obiekty mają się zachowywać w przypadku wykonywania na nich określonych operacji np. dodawania.
Coś takiego również nie jest możliwe w PHP, a żeby zaimplementować taką funkcjonalność, trzeba napisać metody, które muszą być wywoływane jawnie.

metody statyczne

Są to metody, których wywołanie nie wymaga utworzenia obiektów danej klasy. Mogą one dostarczać pewną funkcjonalność oraz korzystać ze statycznych atrybutów klasy.
O tym, czy warto ich używać napiszę w jednym z przyszłych wpisów.

metody definiujące funkcjonalność

Są to wszystkie metody, dzięki którym możemy wykonywać akcje na bądź przy użyciu danego obiektu. To właśnie one pozwalają nam definiować zachowanie obiektu oraz funkcjonalność, którą dostarcza.

metody specjalne

Są to metody, które są wywoływane w określonych sytuacjach, czyli np. przy wywołaniu określonych funkcji języka.
Obsługa trzech z nich jest zaimplementowana chyba w większości języków zorientowanych obiektowo:
  • konstruktor - jest to metoda, która zostaje wywołana w chwili tworzenia obiektu. Dzięki niej możemy określić, czy dany obiekt wymaga jakichś parametrów przy tworzeniu czy nie.
    Osobiście uważam, że za każdym razem, przy tworzeniu nowej klasy warto się zastanowić, które jej atrybuty są niezbędne do jego poprawnego działa bądź zachowania logicznej struktury obiektów. Ich wartości (atrybutów) powinny być inicjowane właśnie tutaj, a nie za pomocą innych metod, już po utworzeniu obiektu. Jeżeli coś ma logiczny sens tylko z określonymi parametrami, to nie powinna istnieć możliwość utworzenia takiego obiektu bez nich.
  • destruktor - jest to metoda, która jest wywoływana w chwili niszczenia obiektu.
  • clone - jest to metoda wywoływana w obiekcie sklonowanym zaraz po wykonaniu operacji klonowania.
Pozostałe metody specjalne, to:
A na koniec metody, których używania osobiście odradzam, ponieważ wprowadzają one zachowania magiczne do kodu i często powodują niepotrzebny bałagan. Uważam, że dobrze zaprojektowane klasy posiadają wszystkie niezbędne metody (stąd brak potrzeby stosowania __invoke(), __call() i __callStatic()), a dostęp do atrybutów obiektów powinien być możliwy jedynie poprzez jawne metody, a nie takie, które wprowadzają pseudo-enkapsulację (__get(), __set(), __isset(), __unset(), __set_state()).
Oto one:
  • __call() i __callStatic() - wywoływane zawsze wtedy, gdy na obiekcie próbujemy wywołać nieistniejące metody, odpowiednie niestatyczne i statyczne.
  • __get() i __set() - wywoływane zawsze, gdy staramy się odwołać (przypisać wartość) do atrybutu tak, jak byłby publiczny, czyli: $obj->attr;.
  • __isset() i __unset() - wywoływane zawsze, gdy wykonujemy na atrybucie obiektu metode isset() bądź unset().
  • __set_state() - wywoływana wtedy, gdy wywołujemy na obiekcie metodę var_export()
  • __invoke() - wywoływana wtedy, gdy staramy używamy obiektu jak funkcji.

czy to koniec?

Wpis trochę się rozrósł, więc podzieliłem go na trzy części. W następnej kolejna dawka informacji teoretycznych, a w trzecim demonstracja, jak tego wszystkiego ugryźć w zetknięciu z rzeczywistością:)
I jeżeli nie wydarzą się żadne nieprzewidziane wydarzenia, to ukażą się w odstępach tygodniowych.