Jak programować obiektowo? cz. 3 – czym jest metoda?

Ostatnio pisałem o elementach struktury obiektów, o tym co je określa i tworzy. Jednak, tak naprawdę z obiektami jest jak w życiu – to nasze czyny nas określają, definiują kim jesteśmy. I to właśnie te zachowania, nasze reakcje są tym, co interesuje naszych znajomych. Czy Sebastian byłby tą samą irytującą osobą, gdyby nazywał się inaczej Zakładając, że w naszych imionach nie tkwi żadna magiczna moc, to zapewne tak.

Nie chcę jednak umniejszać znaczenia wartości atrybutów obiektów, ponieważ często zachowanie jest przez nie uwarunkowane np. jeżeli wiek jest moim atrybutem to jasno widać, że jego wartość ma spory wpływ na wykonywane przeze mnie czynności, na moje reakcje, itp. Dobra, wystarczy już tego odniesienia do rzeczywistości. Rzućmy teraz okiem na metody od strony czysto programistycznej.

Czym jest metoda?

Jak już pisałem wyżej, metody określają to, co „mogą i potrafią” obiekty konkretnej klasy, w jakie interakcje oraz w jaki sposób mogą wchodzić.

Mimo tego, że serię rozpocząłem od artykułu opisującego atrybuty klas, to przy projektach waszych aplikacji radzę zaczynać od metod, od tego co klasy (a właściwie konkretne ich instancje) powinny robić. Wynikową tego i tak będą między innymi atrybuty. Jednak zaczynając od tej strony unikamy nadmiarowości, bo mamy pewność, że trafią do niej (klasy) jedynie te atrybuty, które rzeczywiście są do czegoś nam potrzebne, a nie dodane na zapas, „bo może się przydadzą”. Mam na myśli również klasy domenowe. Wiem, że wielu programistów zaczynając tworzenie nowej aplikacji uważa, że doskonale zdaje sobie sprawę z tego, co siedzi w tych obiektach, bo przecież „są reprezentacją rzeczywistych bytów”, które już owe wartości posiadają.

Oczywiście jest to prawda, niemniej jednak nie każdy z tych atrybutów musi okazać się przydatnym z punktu widzenia implementowanej funkcjonalności. A zawsze łatwiej dodać atrybut gdy okaże się on potrzebny niż usuwać już istniejący. Dlaczego? Jeżeli programista trafia na kawałek kodu, to zakłada, że został on napisany w jakimś celu. A nie zawsze jest możliwość zapytania autora „po co to?”. A niekiedy nawet mając taką możliwość możemy nie uzyskać odpowiedzi.

Czym jest deklaracja/sygnatura metody?

Deklaracje występują jedynie w interfejsach oraz klasach abstrakcyjnych i pełnią tam rolę gwaranta, że metoda o danym API zostanie dostarczona przez klasy, które daną strukturę rozszerzą/zaimplementują. Jeżeli do deklaracji dodamy ciało metody to wtedy już mówimy o sygnaturze metody. W skład deklaracji/sygnatury wchodzą nazwa metody wraz z listą parametrów, które metoda będzie przyjmować oraz typem zwracanym.

Tutaj warto nadmienić, że o ile zmiana nazwy, ilości bądź typu parametrów w deklaracji oznacza stworzenie nowej, to nie jesteśmy w stanie stworzyć dwóch deklaracji różniących się jedynie typem zwracanym. Powód takiego zabiegu jest całkiem prosty – nigdy nie bylibyśmy w stanie stwierdzić, którą metodę w danym momencie wywołujemy, a co za tym idzie – nie bylibyśmy w stanie stwierdzić co dostaniemy w odpowiedzi. Dlatego też przyjmuje się, że metodę jednoznacznie określa jej nazwa oraz parametry.

Ciało i definicja

Dodając do deklaracji ciało (blok z instrukcjami) tworzymy metodę, jej definicję. Dopiero w tym momencie możemy mówić o metodzie. Warto zapamiętać, że wiele definicji może posiadać taką samą deklarację np. wiele klas może implementować ten sam interfejs z licznymi deklaracjami.

Te wszystkie metody, które mamy

Pomimo tego, że z punktu widzenia określenia tego czym metoda jest, powyższe powinno wystarczyć, to omawiając taki temat nie można nie wspomnieć o rodzajach metod. Pod kątem budowy nie różnią się od siebie, jednak każdy rodzaj stosuje się w konkretnych sytuacjach.

  • Metody definiujące funkcjonalność – to jest to co nas najbardziej interesuje. Te metody określają zachowanie obiektów, pozwalają na zapewnienie określonych relacji. Tak naprawdę, to gdyby ktoś się uparł, to są jedyne metody, które są nam potrzebne do uszczęśliwienia naszych klientów poprzez dostarczenie działającej aplikacji. To są również jedyne metody niezbędne do tego. Reszta owszem, przydaje się (z biegiem czasu, gdy życie projektu się wydłuża, coraz wyraźniej będziecie to zauważać), ale nie są jednak niezbędne.
  • 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.
  • Metody specjalne – są to metody, które są wykonywane w określonych przypadkach, bądź cyklach życia obiektu. Zazwyczaj ich wywołanie nie musi się odbywać jawnie, następuje w określonych sytuacjach. Są to m. in. konstruktor, destruktor, clone(), toString().
  • Metody abstrakcyjne – tak naprawdę to nie są metody, ponieważ są to jedynie ich deklaracje. Jak już pisałem wyżej, takie konstrukcje wykorzystujemy w interfejsach oraz klasach abstrakcyjnych w celu zapewnienia istnienia konkretnej metody w klasach potomnych.
  • Metody statyczne – są to metody, których wywołanie nie wymaga utworzenia obiektów danej klasy, są one powiązane z nią, a nie z jej konkretną instancją.

Metody z punktów trzeciego i czwartego na razie zostawimy, wrócimy do nich jeszcze w późniejszych częściach serii.

I to by było na tyle

Dzisiaj bez kodu, daję wam trochę czasu na przetrawienie tego wszystkiego i poznęcanie się nade mną w komentarzach. Za to obiecuję, że następnym razem będzie tylko kod i postaramy się popatrzyć na przedstawione dzisiaj informacje z tego przyjemniejszego punktu.

Dodatkowo chciałem powiedzieć, że niejedna rzecz, o której dzisiaj pisałem, zostanie dokładniej omówiona w przyszłości, więc jeżeli coś nie jest do końca jasne, to spokojnie, przed nami jeszcze trochę artykułów.

Oczywiście, możecie też po prostu napisać komentarz.

Pozostałe artykuły z cyklu

Jestem fanatykiem obiektowego programowania i nieustannie pogłębiam swoją wiedzę we wszelkich tematach z nim związanych. Wszystko czego się dowiem konfrontuję z rzeczywistością, ponieważ teoria, która nie ma odzwierciedlenia w praktyce, traci swój sens tam, gdzie zaczyna się praca programisty :)

  • satyr

    „Wraz z typem zwracanym”
    To na pewno tutorial o php?

    • Guest

      W teorii informatyki, w szczególności

    • Łukasz Haze

      W teorii informatyki, w szczególności w typowanym rachunku lambda, typ zwracany *należy* do sygnatury funkcji. To, że część języków go pomija, to insza inszość. Robią to dla uproszczenia mechanizmu nadpisywania metod. Jako kontrprzykład, w Haskellu możesz nadpisać metodę dla konkretnego typu wyniku.

      PS. Sorki za zbędny post.

    • Sebastian Malaca

      Przede wszystkim o OOP 🙂 Poza tym jestem zdania, że język nie powienien zwalniać z wiedzy. Tym bardziej, że im większe będziesz miał doświadczenie, tym istotniejsza stanie się dla Ciebie (oraz potencjalnych pracodawców/osób rekrutujących) wiedza ponadjęzykowa, która może być stosowana niezależnie od wykorzystywanego narzędzia.

      • satyr

        Ale nie takie było moje pytanie 😉

        Proponuję Ci się jednak skupić na jednym języku bo zaraz zamieszasz i siebie i tych, którzy chcieliby się czegoś nauczyć. Różnice w obiektowości między Javą, PHP, Pythonem i np C++ są bardzo znaczące. W pythone nie ma rozdziału na private/public więc jak chcesz opisywać wszystkie aspekty OOP to zaraz będziesz musiał opisywać wszystkie wyjątki itd. Idąc dalej skoro piszesz ogólnie o OOP to np to:

        >> Deklaracje występują jedynie w interfejsach oraz klasach abstrakcyjnych
        Jest też nieprawdą bo deklaracje w c++ występują w plikach nagłówkowych.

        Stanowczo proponuję skupić się na jednym języku, który znasz najlepiej, jeśli chcesz żeby ktokolwiek coś wyniósł z kursu.

        • Sebastian Malaca

          To jasne, że każdy język różni się czymś od pozostałych. Gdyby tak nie było, to nie byłoby ich aż tylu 🙂
          Niemniej jednak duża część jest wspólna i zastosowana w większości języków. Choć w Pythonie nie określasz widoczności, wielokrotne dziedziczenie mamy np. w C++, typy generyczne np. w Javie, a taki JavaScript to już zupełnie inna historia :), itd. itp.

          Wracając jednak do pytania, to będzie o PHPie i choć nie określasz w nim typu zwracanego, to sam zawsze staram się na to uczulać, ponieważ różnorodność w zwracanych typach sprawia, że w różnych miejscach w kodzie musisz to jakoś obsługiwać i zazwyczaj (zawsze?) lepiej rozbić taką konstrukcję na kilka różnych metod.

          • satyr

            W pythonie też masz dziedziczenie wielokrotne.

            >> zazwyczaj (zawsze?) lepiej rozbić taką konstrukcję na kilka różnych metod.

            Mógłbyś rozwinąć?

          • Sebastian Malaca

            Jeżeli metoda może zwracać np. array() gdy wiele elementów lub obiekt gdy masz jeden element to taka implementacja zmusza Cię do tworzenia poniższego kodu gdziekolwiek Jej nie wywołasz:
            $result = someMethod($params …);
            if (is_array($result)) // do something
            else // do something else

            I raczej nie jest to zbyt rozsądne.

            Czy nie lepiej w takim wypadku byłoby stworzyć dwie metody?
            A co gdy ilość typów zwracanych jest jeszcze większa?
            Ja zdaję sobie sprawę, że „typ zwracany” mixed jest dostępny w PHP, ale nie powinno się go używać „dla wygody” tylko będąc w pełni świadomym wszystkich konsekwencji.

          • satyr

            Rozumiem o co Ci chodzi. Zgadzam się w pełni, albo zwracasz kolekcję (np. coś co implementuje ArrayIterator), albo pojedynczy obiekt/wartość. Jeśli chodzi o mixed to domyślam się, że chodzi o oznaczenie phpdoca, bo równie dobrze możesz jawnie wpisać JakisObiekt|array|null i też będziesz miał mieszane typy. Co do mieszanych typów to znajduje on zastosowanie np w przypadku, jeśli metoda zwraca obiekt lub nulla. To jest sensowny przypadek zwracania mieszanych typów. Również przypadek gdzie masz mixed type i jest on całkowicie poprawny jest taki, gdy metoda zwraca zupełnie różne obiekty, ale wszystkie z tych obiektów implementują jakiś interfejs.

          • Sebastian Malaca

            Dokładnie chodziło o „ten” mixed 🙂
            Co do pary Obiekt lub null to zgadzam się, że to ma sens (z resztą w wielu językach typowanych również dopuszczają coś takiego). Czasami warto rozważyć Null Object bądź pewnego rodzaju Proxy zamiast null’a, ale to już zależy od konkretnego przypadku.
            Niemniej jednak nie jestem do końca pewny czy tutaj mówimy o różnych typach. Jakby na to nie patrzeć null jest domyślną wartością atrybutów niezależnie od ich typów (i mowa tutaj również o językach typowanych).

            Co do zwracania instancji różnych klas implementujących jeden interfejs to tutaj raczej nie może być mowy o różnych typach zwracanych, ponieważ jest on jeden – implementowany interfejs.

  • satyr

    >>tworzenie zestawu setów i getów dla każdego atrybutu
    A w jaki sposób chcesz pobierać wartości atrybutów private/protected? W ostatnim tutorialu sugerowałeś aby używać pól private, a teraz piszesz że nie ma sensu zawsze tworzyć getterów i setterów. Mi przychodzi na razie do głowy tylko jeden przypadek gdzie faktycznie nie tworzyłbym getterów i setterów, mianowicie dependency injection i wstrzykiwanie serwisów w konstruktorze. Logiczne jest wtedy że używasz takiego serwisu tylko w obrębie klasy i nie ma sensu do tego pisać getterów i setterów bo dependency nie powinny być dostępne z zewnątrz.

    • Sebastian Malaca

      Zwróć jednak uwagę, że nie licząc obiektów, które są przeznaczone do przesyłania danych, wyciąganie i ustawianie parametrów nie jest czymś co jest istotą obiektów. Nas interesuje ich działanie, zachowanie i jego skutki (które oczywiście jest zależne od wartości ich atrybutów).
      Tak jak pisałem, jeżeli jest logiczne uzasadnienie tego, aby dany obiekt posiadał takie metody, to nie powinniśmy uciekać od akcesorów tylko dlatego, że gdzieś ktoś napisał, że są złem. Jeżeli są potrzebne to są potrzebne.
      Tylko, że zazwyczaj nie ma takiej potrzeby.

      Zwróć ponadto uwagę, że jeżeli każdy atrybut jest prywatny, ale mamy do nich sety i gety co się staje z enkapsulacją? Równie dobrze każdy z tych atrybutów mógłby być publiczny. Efekt taki sam, ale mamy mniej kodu.

      • satyr

        >>wyciąganie i ustawianie parametrów nie jest czymś co jest istotą obiektów

        Nie zgadzam się. W każdej aplikacji masz szereg klas-modeli których istotą jest właśnie zarządzanie swoimi parametrami. Zwykle nawet o tym nie wiem, bo dzieje się to pośrednio jeśli używasz np komponentu do obsługi formularzy, ORMa itd.

        >>Równie dobrze każdy z tych atrybutów mógłby być publiczny. Efekt taki sam, ale mamy mniej kodu.

        Nie zgadzam się. Settery i gettery mają tę zaletę że wprost określają czy dane pole istnieje i czy jest dostępne do modyfikacji. W php dodatkowo przy setterach możesz wymusić typ obiektu (lub tablicę) w setterze co dodatkowo zwiększa bezpieczeństwo. Gdybyś operował bezpośrednio na publicznych atrybutach pozbywasz się tej możliwości i oworzysz szereg innych patologii, które powodują błędy.

        • Sebastian Malaca

          >> Nie zgadzam się. W każdej aplikacji masz szereg
          >> klas-modeli których istotą jest właśnie zarządzanie
          >> swoimi parametrami. Zwykle nawet o tym nie wiem,
          >> bo dzieje się to pośrednio jeśli używasz np komponentu
          >> do obsługi formularzy, ORMa itd.

          Nie wiem o co chodzi z obiektami do obsługi formularzy. Chętnie rzucę okiem na przykład.
          Co do ORMów to zwróć uwagę, że w najnowszych wersjach odchodzą od konieczności stosowania akcesorów, a powód jest właśnie taki, że te metody były wykorzystywane jedynie w mechanizmie zamiany obiektów na wiersze w tabeli i z powrotem. To nie są metody, które powstają, bo są niezbędne aplikacji czy logice, którą implementujesz. Powstawały dlatego, że ORMy ich wymagały.
          Jeżeli więc patrzymy na OOP/ OOA/D od strony tworzenia aplikacji i budowania klas/obiektów składających się na jej dziedzinę to zauważysz, że settery i gettery pojawiają się niezwykle rzadko w tym momencie. Są dokładane (i to nie zawsze) dopiero na etapie, gdy decydujemy się gdzie będziemy zapisywali nasze encje.

        • Sebastian Malaca

          >> Nie zgadzam się. Settery i gettery mają tę zaletę
          >> że wprost określają czy dane pole istnieje i czy jest
          >> dostępne do modyfikacji.
          Tylko czy często jest tak, że ich potrzebujesz? Prawda jest taka, że w aplikacjach interesuje Cię przede wszystkim zachowanie obiektów, a nie możliwość modyfikacji parametrów.
          Zazwyczaj jest tak, że są jakieś niezbędne przy inicjalizacji (kontruktor) i często nie są one modyfikowane (brak geterów), zmiana pojedynczych parametrów to również coś rzadkiego, bo zazwyczaj „robisz to” hurtem, a więc wystarczy Ci spokojnie jedna metoda (zamiast x setów jedna metoda update() czy coś takiego, brak gettera).
          Oczywiście niekiedy nie unikniemy konieczności wyciągnięcia jakichś informacji z obiektu, ale do tego nie potrzebujemy n getów, wystarczy jedna metoda getDTO(), która zwraca prosty obiekt zawierający nic tylko wartości atrybutów klasy (ten sam obiekt może być również przekazywany do metody update())

          >> W php dodatkowo przy setterach możesz wymusić typ
          >> obiektu (lub tablicę) w setterze co dodatkowo zwiększa
          >> bezpieczeństwo. Gdybyś operował bezpośrednio na
          >> publicznych atrybutach pozbywasz się tej możliwości i
          >> oworzysz szereg innych patologii, które powodują błędy.

          Muszę przyznać, że to akurat jest naprawdę dobry argument. Niemniej jednak podane przeze mnie wyżej rozwiązanie również pozwala na sprawdzanie i zachowanie typów.

          • satyr

            Mogę zapytać z jakiego frameworka do php korzystasz? Jeśli używasz kilku podaj ten najczęściej wykorzystywany lub ten który jest Tobie najbliższy.

          • Sebastian Malaca

            Zend, trochę Symfony, dawniej Kohana, a z ORMów Doctrine.

Send this to a friend

webmastah.weekly
Cotygodniowa porcja linków ze świata WEBDEV BEZ spamu, TYLKO samo mięcho!
Zobacz poprzednie wydania. Dołącz do 2 tysięcy webdeveloperów!
HTML5, CSS3, JS (React, Angular, Ember, Vue), PHP, SQL