P-Programowanie

Metody wirtualne, abstrakcyjne i polimorfizm

23 lutego 2015, kategoria: C#

Metody wirtualne oraz metody abstrakcyjne są ściśle związane z mechanizmem polimorfizmu. Polimorfizm jest jednym z filarów paradygmatu programowania obiektowego. Jak wiadomo język C# jest w całości językiem obiektowym, dlatego tak ważne jest aby zapoznać się z jego podstawowymi konstrukcjami. Dowiesz się także, kiedy lepiej wybrać metodę wirtualną a kiedy abstrakcyjną.

Czym jest polimorfizm?

Na początku trochę suchej teorii. Polimorfizm jest jednym z czterech podstawowych założeń programowania obiektowego. Opisując najprościej, polimorfizm polega na zdolności obiektu do różnych zachowań zależnie od bieżącego wykonania programu.

Polimorfizm to filar programowania obiektowego. Polega na różnym zachowaniu tych samych metod polimorficznych (o tych samych deklaracjach) w klasach będących w relacji dziedziczenia.

Metodami polimorficznymi (czyli obsługującymi mechanizm polimorfizmu) są w C# metody wirtualne oraz metody abstrakcyjne.

Do powyższej definicji polimorfizmu można się przyczepić, stricte definiuje ona polimorfizm dynamiczny. Oprócz tego w C# występuje także polimorfizm statyczny. Jednak to właśnie wiązania dynamiczne są sednem polimorfizmu, dlatego zdecydowałem się uogólnić definicję właśnie dla tego typu wielopostaciowości. Więcej szczegółów znajdziesz w kolejnych akapitach.

polimorfizm c#

Komu i po co to potrzebne? Jednym przeznaczeniem polimorfizmu jest znaczne skrócenie i uproszczenie kodu programu. Więcej na ten temat w kolejnych akapitach.

Polimorfizm statyczny w C#

Polimorfizmem statycznym w C# jest przeciążanie funkcji i operatorów. W tej samej klasie może istnieć wiele metod o tej samej nazwie, różniących się tylko parametrami. Nie jest to zaskakujące, jednak warto wiedzieć, że przeciążanie także nazywane jest polimorfizmem. W tym artykule nie będę tego opisywał, jest to dobry temat na osobny wpis.

Polimorfizm statyczny zachodzi podczas kompilacji programu. Nie mamy wpływu na zachowanie metod podczas działania aplikacji. To, która metoda zostanie wybrana jest postanowione już na etapie komplikacji i zależy od ilości przekazanych do metody argumentów.

Polimorfizm dynamiczny w C#

Polimorfizm dynamiczny w C# to przesłanianie funkcji. Jest to aspekt, który najbardziej nas interesuje. Rzadko używane jest pojęcie ‚polimorfizmu dynamicznego’, przeważnie pisze się tylko ‚polimorfizm’ i takiej terminologii będę się dalej trzymał.

Polimorfizm został opisany w akapicie wyższej. Co należy zapamiętać, aby włączyć mechanizm polimorfizmu, musimy w danej klasie utworzyć dowolną metodę polimorficzną. Na metody polimorficzne składają się metody wirtualne oraz metody abstrakcyjne (czysto wirtualne).

Metody abstrakcyjne są przesłaniane, dzięki temu osiąga się wielopostaciowość interfejsu polimorficznego w obrębie klas znajdujących się w relacji dziedziczenia. Metody polimorficzne przesłania się słowem kluczowym override.

Używanie terminu „przysłanianie” zamiast „przesłanianie” w terminologii paradygmatu programowania obiektowego jest błędne (a niestety często spotykane).

Interfejs polimorficzny

Ściśle z polimorfizmem związane jest pojęcie interfejsu polimorficznego. Dla danego związku klas będących w relacji dziedziczenia, tworzymy jeden interfejs polimorficzny i osiągamy różne zachowanie tych samych metod, narzuconych przez interfejs.

Nie jest to artykuł o interfejsach występujących w C#, jednak czym one w ogóle są? Interfejsy to „umowa”, mówiąca jakie metody musi zaimplementować klasa rozszerzająca dany interfejs.

Interfejs polimorficzny to „umowa”, mówiąca jakie metody polimorficzne muszą implementować klasy pochodne. Dzięki temu grupa klas posiada jeden interfejs polimorficzny ale wiele tych samych metod o różnych zachowaniach (wielopostaciowość metod polimorficznych).

Przykładowo, możemy utworzyć interfejs polimorficzny dla klasy Pracownik. Jedną z metod klasy będzie metoda Pracuj(). Z klasy pracownik będzie dziedziczyła klasa Szef i Sekretarka. Metoda Pracuj() będzie się zachowywać inaczej dla każdej klasy pochodnej, a to jak będzie się zachowywać będzie zależało od typu statycznego obiektu, na rzecz którego jest wywoływana.

Uwaga! Nie można mylić interfejsu polimorficznego ze zwykłym interfejsem w języku C#. Interfejs to konstrukcja programistyczna deklarowana słowem interface, a interfejs polimorficzny to zbiór metod polimorficznych (wirtualnych i abstrakcyjnych) występujących w klasie bazowej, których implementacją obarczamy klasy pochodne.

Przesłanianie zwykłych metod

Cała idea wielopostaciowości kręci się w okół dziedziczenia klas oraz przesłaniania metod. Przesłanianie metod wywodzi się z polimorfizmu dynamicznego.

W przypadku zwykłego przesłonięcia metody, kompilator ostrzega nas i pyta, czy jest to aby na pewno efekt przez nas pożądany:

Kod się kompiluje jednak występuje ostrzeżenie: Warning ‚Sekretarka.Pracuj()’ hides inherited member ‚Pracownik.Pracuj()’. Use the new keyword if hiding was intended.

Aby pozbyć się ostrzeżenia, wystarczy w linijce nr. 9 dodać słowo kluczowe new. Jedynym zadaniem new jest powiadomienie kompilatora, że przesłaniamy funkcję niepolimorficzną świadomie.

Takie przesłanianie niepolimorficznych funkcji nie jest poprawne, przeważnie oznacza błędnie zaprojektowany program. Jakie są skutki takiego przesłonięcia funkcji? W przypadku rzutowania klasy pochodnej na typ klasy bazowej, zostanie wywołana metoda typu statycznego:

Od teraz, trzymaj się takich konstrukcji programistycznych z daleka. Na początku zapewne nie dostrzegasz wad tego rozwiązania, jednak zrozumiesz je w dalszych akapitach. Podstawową z takich wad jest znaczące powielanie kodu.

W tym momencie wkracza polimorfizm. Aby korzystać z jego zalet, będziemy przesłaniać metody polimorficzne, co jest opisane w następnym akapicie.

Metody polimorficzne i przesłanianie metod

W C# istnieją dwa typy metod polimorficznych, które uruchamiają mechanizm polimorfizmu. Są to metody wirtualne oraz metody abstrakcyjne. Ich przedrostki dopisujemy do dowolnej metody danej klasy. Niżej są one omówione bardziej szczegółowo:

Metody wirtualne

Metoda wirtualna w C# jest metodą polimorficzną. Jest to funkcja składowa dowolnej klasy oznaczona słowem kluczowym virtual.

Metoda wirtualna (funkcja wirtualna) jest metodą składową klasy, której wywołanie zależy od typu dynamicznego obiektu. Jej użycie włącza mechanizm polimorfizmu dynamicznego.

Metodę wirtualną tworzy się w klasie bazowej. We wszystkich klasach pochodnych można polimorficznie przesłonić jej nazwę i zapewnić jej inną implementację.

Jaka jest różnica między metodą wirtualną a zwykłą? Różnica polega na zachowaniu wywoływania przesłoniętych metod podczas dziedziczenia klas.
Rozważmy przykład znany z akapitu o przesłanianiu metod, wzbogacony o funkcję wirtualną:

W przypadku przesłonięcia metody niewirtualnej, zostanie wywołana metoda typu statycznego wskazywanego przez referencję, czyli metoda z klasy Pracownik. Ponieważ użyliśmy metody wirtualnej (polimorficznej), mimo rzutowania instancji klasy Sekretarka na typ bazowy Pracownik, wywołana zostaje metoda wirtualna klasy Sekretarka. Występuje tutaj zależność od typu dynamicznego, a nie statycznego jak w przypadku zwykłego przesłaniania.

Zauważ! Metoda klasy bazowej jest wirtualna. Nie musimy jej przesłaniać, ale jeżeli chcemy to musimy to zrobić za pomocą słowa kluczowego override.

Kilka wniosków na temat metod wirtualnych
  • metody, która nie jest wirtualna, nie można przesłonić poprzez użycie override
  • metody statyczne ani prywatne nie mogą być wirtualne
  • metoda wirtualna może być przesłonięta w klasie pochodnej – ale nie musi. W przypadku braku przesłonięcia zostanie wywołana metoda klasy bazowej

Najważniejszą cechą metod wirtualnych, jest ta opisana wyższej. Nie jesteśmy zmuszeni do przesłonięcia metody wirtualnej w klasie bazowej. Jest to główna różnica w stosunku do metod abstrakcyjnych.

Każda klasa w C# dziedziczy niejawnie z klasy Object, która z kolei udostępnia kilka metod wirtualnych.

Dzięki temu, możesz zawsze je nadpisać za pomocą override, nawet jeżeli jawnie nie rozszerzasz Object. Ponieważ to metody wirtualne, nie musisz ich przesłonić. W przypadku braku przesłonięcia zostanie wywołana implementacja domyślna z kasy bazowej Object.

Metody abstrakcyjne

Metoda abstrakcyjna w C# jest metodą polimorficzną. Musi być zadeklarowana jako funkcja składowa abstrakcyjnej klasy, poprzedzona przedrostkiem abstract.

Metoda abstrakcyjna zachowuje się dokładnie tak samo jak metoda wirtualna, jedyna różnica leży w braku definicji ciała funkcji. Tworząc metodę abstrakcyjną deklarujesz funkcję (deklaracja to typ zwracany, nazwa i argumenty) ale nie definiujesz ciała funkcji.

Metodę abstrakcyjną można zadeklarować tylko w klasie abstrakcyjnej (także poprzedzonej słowem abstract). Dlaczego tak jest? Klasa abstrakcyjna jest klasą, której instancji nie da się stworzyć. Można po niej tylko dziedziczyć, rozszerzając ją o inne klasy. Klasy dziedziczące muszą implementować metody abstrakcyjne klasy abstrakcyjnej. Gdyby dało się utworzyć instancję klasy abstrakcyjnej, doszło by do sytuacji, że istnieje w niej abstrakcyjna funkcja bez definicji ciała.

Za zdefiniowanie ciała funkcji abstrakcyjnej odpowiedzialne są klasy pochodne. Odbywa się to poprzez przesłanianie słowem override tak samo jak w funkcjach wirtualnych. Krótki przykład:

Warto dodać, że klasa abstrakcyjna może posiadać zwykłe metody, posiadające ciało funkcji mimo posiadania metod abstrakcyjnych. Cała reszta działa tak samo jak w funkcjach wirtualnych. Wywoływana jest przesłonięta metoda, biorąc pod uwagę typ dynamiczny.

Podsumowanie metod abstrakcyjnych
  • działają tak samo jak funkcje wirtualne, oprócz tego że nie podaje się definicji ciała funkcji w klasie bazowej
  • muszą być zadeklarowane w klasie abstrakcyjnej
  • nie mogą być prywatne ani statyczne
  • klasa pochodna musi przesłonić wszystkie metody abstrakcyjne

Kiedy używać metod wirtualnych a kiedy abstrakcyjnych?

Zarówno metody abstrakcyjne jak i metody wirtualne są polimorficzne i prawie niczym się nie różnią. Nasuwa się więc pytanie: kiedy używać jednych a kiedy drugich? Odpowiedź na to pytanie jest prosta.

Metod wirtualnych używamy wszędzie tam, gdzie może wystąpić powielanie kodu w przesłanianych funkcjach. Dlaczego? Używając polimorfizmu i metod wirtualnych, możemy wywołać po typie dynamicznym metodę wirtualną klasy pochodnej (domyślnie), a jeśli zechcemy także metodę klasy pochodnej i bazowej. Odbywa się to dzięki słowu kluczowemu base odnoszącemu się do klasy bazowej. Prosty przykład:

W przykładzie wyżej, każdy pracownik przed rozpoczęciem pracy musi przejść proces autoryzacji a następnie sprawca zlecenia na dziś. Dopiero po tym etapie zaczyna właściwe dla swojego stanowiska obowiązki.

Jeżeli stworzylibyśmy reprezentację klas dla dużej ilości pracowników, błędem byłoby użycie metody abstrakcyjnej. Doskonale za to naddaje się tutaj metoda wirtualna, ponieważ zapobiegamy powielaniu kodu. Obowiązki wspólne dla każdego pracownika są wywoływane z metody wirtualnej klasy bazowej, a specjalistyczne obowiązki poszczególnych stanowisk dopiero później przesłaniając funkcję wirtualną.

Odwrotnie wygląda sprawa z metodami abstrakcyjnymi. Jeżeli masz pewność, że nie zajdzie zjawisko powielania kodu, możesz ich użyć. Przykładowy program dotyczący figur geometrycznych:

W tym przykładzie użycie metody wirtualnej nie miałoby sensu. Pole powierzchni dla każdej figury liczy się zupełnie inaczej, więc wygodnie nam było użyć metody abstrakcyjnej. Dzięki temu mamy pewność, że klasa pochodna musi dostarczyć własną definicję ciała metody Pole().

W przypadku metod wirtualnych klasa pochodna nie musi przesłonić metody klasy bazowej.

Polimorfizm w C++, C# i Javie

Polimorfizm występuje we wszystkich obiektowych językach programowania. Generalnie zawsze polega on na tym samym – wielopostaciowości przesłanianych metod wirtualnych. Małe różnice występują natomiast w specyficznych elementach danego języka.

C++ vs C#

W języku C++ uruchamiamy polimorfizm dodając do klasy dowolną funkcję wirtualną. W klasach pochodnych przesłaniamy funkcję, jednak nie poprzedzamy jej już żadnym słowem kluczowym. W C# konieczne jest dodanie override. W C++ dopuszczalne jest dopisanie virtual jednak nie jest to wymagane, a skoro nie jest to wiadomo – nie dopisujemy.

W języku C++ nie ma interfejsów ani metod abstrakcyjnych, są za to metody czysto wirtualne. Jeżeli klasa posiada jakąkolwiek metodę czysto wirtualną, przyjmujemy, że staje się umownie klasą abstrakcyjną.

W tym przypadku musimy zaimplementować metodę czysto wirtualną. Zwykłej metody wirtualnej oczywiście nie musimy. Zwykła metoda wirtualna w C++ zachowuje się jak metoda wirtualna z C#. Natomiast metoda czysto wirtualna z C++ zachowuje się jak metoda abstrakcyjna z C#.

Warto zapamiętać: w C++ nie występują klasy wirtualne, ponieważ nie występuje konstrukcja pozwalająca poprzeć słowa class słowem abstract. Nie występują także interfejsy.

Programiści przyjęli, że definiując w C++ klasę z funkcjami czysto wirtualnymi oraz funkcjami normalnymi osiągamy twór na wzór klasy abstrakcyjnej. Tworząc natomiast klasę zawierającą tylko funkcje wirtualne otrzymujemy twór na wzór interfejsu.

Java vs C#

Java niczym szczególnym nie różni się od C#. W Javie wszystkie funkcje domyślnie są wirtualne. Są dostępne interfejsy i metody abstrakcyjne, które także są domyślnie wirtualne.

Chociaż wirtualna maszyna Javy i odśmiecacz pamięci działają bardzo dobrze, wciskanie polimorfizmu wszędzie tam gdzie nie jest potrzebny, nawet do najprostszych programów często odbija się na wydajności.

Mimo tego programiści C# nie odczują dużej różnicy przesiadając się na Javę, przynajmniej w przypadku tego aspektu.

Chcąc wyłączyć polimorfizm – przesłonić funkcję tak jak w C# za pomocą operatora new, jesteśmy zmuszeni w klasie bazowej zadeklarować funkcję jako prywatna.

Przykłady użycia polimorfizmu

Istnieją setki przykładów na użycie polimorfizmu, można wymyślać je bez końca. Pokażę tutaj dwie duże zalety i ich reprezentację w kodzie. Obydwie skupiają się przede wszystkim na znacznym skróceniu kodu programu.

Sztandarowy przykład to wywoływanie metod klas zebranych w kolekcji lub tablicy. Dzięki zastosowaniu polimorfizmu wywołujemy metodę polimorficzną związaną z typem statycznym. Dzięki dziedziczeniu, które jest drugim filarem programowania obiektowego, możemy klasy pochodne rzutować na referencję klasy bazowej. Dzięki temu powstają potężne konstrukcje jak np. ta:

W konstrukcji foreach możemy jedną linijką kodu wywołać metody dla wielu różnych klas, wywodzących się z jednej klasy bazowej.

Kolejnym przykładem jest przekazywanie klasy pochodnej do różnych specjalistycznych metod. Dzięki użyciu polimorfizmu możemy dopuścić się następującej konstrukcji:

Bez użycia funkcji wirtualnych metoda musiałaby wyglądać następująco:

Prawda, że kod jest o wiele dłuższy?

Zaawansowane dziedziczenie i polimorfizm

Zadania dotyczące polimorfizmu i nadpisywania funkcji zawsze pojawiają się na rozmowach kwalifikacyjnych. Dlatego warto dobrze przyswoić sobie ten temat.

Jeżeli doczytałeś artykuł do tego momentu, polimorfizm może wydawać Ci się prosty. Nic bardziej mylnego! W tym akapicie udowodnię Ci, że istnieje wiele konstrukcji programistycznych w których bardzo łatwo się pogubić!

Język C# nie pozwala na dziedziczenie z wielu klas bazowych – i bardzo dobrze, eliminuje to wiele dodatkowych problemów (ang. the diamond inheritance problem). Mimo tego działanie mechanizmu polimorfizmu jest oczywiste tylko w przypadku powiązania relacją dziedziczenia dwóch klas.

W przypadku dziedziczenia z wielu klas bazowych, zawierających metody wirtualne i abstrakcyjne sytuacja zaczyna się komplikować (ang. inheritance chain problem). Gdy dodatkowo przepleciemy je zwykłymi metodami, oraz metody wirtualne zaczną wywoływać inne metody wirtualne – łatwo się zgubić. Rozważmy przykład:

Powyższy przykład ukazuje sedno problemu. Każdy znajdzie w nim coś dla siebie. Jest to połączenie metod wirtualnych, metod abstrakcyjnych i zwykłych.

Przeanalizujmy krok po kroku kilka wywołań, aby nie powielać kodu przyjmijmy, że znajdują się one w linijce 43. Spróbuj przewidzieć, co zostanie wypisane na ekran po wykonaniu tego kodu:

Przykład:

Analiza:
  • wczesne wiązanie: klasa A (od niej zaczniemy), późne wiązanie: klasa B (na niej skończymy)
  • (w klasie A) typowana do wykonania jest funkcja A::VirtualFun. Jest wirtualna więc sprawdzamy czy nie jest wyżej przesłonięta?
  • (w klasie B) typowana do wykonania jest funkcja przesłonięta B::VirtualFun. Jest wirtualna więc przerywamy łańcuch dziedziczenia (nie nadpisuje).
  • wywołujemy A::VirtualFun
  • (w klasie A) funkcja A::VirtualFun wywołuje funkcje VirtualFun2
  • (w klasie A) typowana do wykonania jest funkcja A::VirtualFun2. Jest wirtualna więc sprawdzamy czy nie jest wyżej przesłonięta?
  • (w klasie B) typowana do wykonania jest funkcja przesłonięta B::VirtualFun2. To ostatnia klasa więc nie sprawdzamy wyżej.
  • wywołujemy B::VirtualFun2

Wynik działania programu:

Przykład:

Analiza:
  • wczesne wiązanie: klasa A (od niej zaczniemy), późne wiązanie: klasa D (na niej skończymy)
  • (w klasie A) typowana do wykonania jest funkcja wirtualna A::VirtualFun. Jest wirtualna więc sprawdzamy czy nie jest wyżej przesłonięta?
  • (w klasie B) typowana do wykonania jest funkcja przesłonięta B::VirtualFun. Jest wirtualna więc przerywamy łańcuch dziedziczenia (nie nadpisuje).
  • wywołujemy A::VirtualFun
  • (w klasie A) funkcja A::VirtualFun wywołuje funkcje VirtualFun2
  • (w klasie A) typowana do wykonania jest funkcja A::VirtualFun2. Jest wirtualna więc sprawdzamy czy nie jest wyżej przesłonięta?
  • (w klasie B) typowana do wykonania jest funkcja przesłonięta B::VirtualFun2. Przesłania poprzednią funkcję, sprawdzamy czy nie jest wyżej przesłonięta?
  • (w klasie C) brak funkcji VirtualFun2 więc dziedziczymy ją z klasy bazowej B::VirtualFun2.
  • (w klasie C) typowana do wykonania jest funkcja przesłonięta B::VirtualFun2. Przesłania poprzednią funkcję, sprawdzamy czy nie jest wyżej przesłonięta?
  • (w klasie D) typowana do wykonania jest funkcja przesłonięta D::VirtualFun2. To ostatnia klasa więc nie sprawdzamy wyżej.
  • wywołujemy D::VirtualFun2

Wynik działania programu:

Przykład:

Analiza:
  • wczesne wiązanie: klasa C (od niej zaczniemy), późne wiązanie: klasa F (na niej skończymy)
  • (w klasie C) typowana do wykonania jest funkcja C::VirtualFun. Jest przesłonięta więc sprawdzamy czy nie jest wyżej przesłonięta?
  • (w klasie D) typowana do wykonania jest funkcja przesłonięta D::VirtualFun. Jest przesłonięta więc sprawdzamy czy nie jest wyżej przesłonięta?
  • (w klasie E) typowana do wykonania jest funkcja przesłonięta E::VirtualFun. Jest wirtualna więc przerywamy łańcuch dziedziczenia (nie nadpisuje).
  • wywołujmy D::VirtualFun
  • (w klasie C) typowana do wykonania jest funkcja NormalFun. Bbrak funkcji NormalFun więc dziedziczymy ją z klasy bazowej B::NormalFun.
  • wywołujemy B::NormalFun.

Wynik działania:

Przykład:

Analiza:
  • wczesne wiązanie: klasa D (od niej zaczniemy), późne wiązanie: klasa F (na niej skończymy)
  • (w klasie D) typowana do wykonania jest funkcja D::VirtualFun. Jest przesłonięta więc sprawdzamy czy nie jest wyżej przesłonięta?
  • (w klasie E) typowana do wykonania jest funkcja E::VirtualFun. Jest wirtualna więc przerywamy łańcuch dziedziczenia (nie nadpisuje).
  • wywołujmy D::VirtualFun
  • (w klasie D) typowana do wykonania jest funkcja D::NormalFun. Jest odnaleziona więc wywołujemy:
  • wywołujmy D::NormalFun

Wynik działania:

 Podsumowanie i kilka ważnych zasad

Jak widzisz, dziedziczenie wielu klas z metodami wirtualnymi może doprowadzić do lekkich problemów. Zalecam Ci poćwiczyć takie przykłady, ponieważ zawsze pojawiają się na testach kwalifikacyjnych. Prawdopodobnie nie aż w takim natężeniu, ale zawsze są zadania gdzie dziedziczenie odbywa się dla ilości klas n>2.

Oto podstawowe zasady na jakie należy zwracać uwagę w tak rozbudowanych przykładach:

  • Analizujemy łańcuch klas zaczynając od typu statycznego kończąc na typie dynamicznym.
  • Wywołanie metod normalnych (niepolimorficznych) zależy od typu statycznego, jeżeli takiej metody nie ma, są dziedziczone z klas bazowych (wędrujemy w dół po hierarchii dziedziczenia).
  • Wywołanie metod polimorficznych (abstract i virtual) zależy od typu dynamicznego
  • Jeżeli zaczynamy analizę od metody abstrakcyjnej lub wirtualnej, wędrujemy w górę po metodach przesłoniętych aż do typu statycznego obiektu (ostatnia klasa) lub aż do napotkania kolejnej metody wirtualnej lub abstrakcyjnej.
  • Jeżeli zaczniemy analizę od metody przesłaniającej i napotkamy metodę abstrakcyjną lub wirtualną, kończymy analizę bez dochodzenia do typu statycznego. Zostaje przerwany łańcuch dziedziczenia z powodu wygenerowania nowej tabeli vtable.

Niestety, te przykłady da się jeszcze bardziej utrudnić, np. wprowadzając metody wirtualne i abstrakcyjne do dwóch dziedziczących po sobie klas abstrakcyjnych. Polecam Ci zainteresować się takim przypadkiem. Nie umieszczam go w tym artykule, ponieważ analiza byłaby zbyt długa i nieczytelna.

Podsumowanie

Warto przyswoić sobie działanie polimorfizmu, napisać kilka prostych programów korzystających z tego mechanizmu. Obiektowość bez używania polimorfizmu nie istnieje, a przynajmniej nie wykorzystuje swoich wszystkich atutów.

Polimorfizm oprócz tego, że niezbędny w rozbudowanych projektach, często znajduje swoje zastosowanie w implementacji wielu wzorców projektowych.

Komentarze:

Użytkownik Kimurateampl napisał/a:

09 marca 2015


Widać, że masz głowę do programowania :D
PS wysłałem do Ciebie maila z kilkoma pytaniami

Użytkownik Andrzejp napisał/a:

21 marca 2015


Jestem pod ogromnym wrażeniem poziomu tego artykulu. W moich ksiazkach ktore kupilem nie jest to tak dokladnie opisane z tyloma szczegolami, jak Ty potrafiles to opisać. Bardzo dziekuje Ci za ten wpis. Rób dalej to co robisz

Użytkownik L5k napisał/a:

24 marca 2015


Ogólna wartość merytoryczna – OK (zwięźle i na temat).

Jedyne do czego bym się przyczepił to błędne używanie terminu „przesłaniania” (w niektórych przypadkach) co może wprowadzać niepotrzebny zamęt – w języku C# po to występują dwa słowa kluczowe: new/override aby rozróżnić te dwie operacje:
-przesłanianie/ukrywanie (new)
-nadpisywanie (override)

Użytkownik Karol napisał/a:

24 marca 2015


@L5k
Kwestia, którą poruszyłeś jest lekko problematyczna. Występuje tu kolizja terminologii. Logiczne jest takie opisanie operatorów, jakie użyłeś w komentarzu wyżej, a więc ‚new’ -> przesłania, ‚override’ -> nadpisuje. Jednak ‚Override’ z angielskiego tłumaczy się jako przesłaniać. Długo nie wiedziałem jakiej terminologii używać w artykule i dyskutowałem o tym ze znajomym.

Szukałem w tym celu w wielu dostępnych źródłach.W C# 3.5 Troelsena w Polskim tłumaczeniu używana jest także terminologia ‚override’->przesłanianie. Dlatego postanowiłem trzymać się tego schematu, mimo, że na początku pisałem artykuł według Twoich wytycznych.

Użytkownik L5k napisał/a:

24 marca 2015


@Karol
Waśnie z powodu tej kolizji terminologii o tym wspomniałem. Chodzi mi o rozróżnienie tych dwóch pojęć bo może to być po prostu mylące. Obojętnie czy tak jak wcześniej napisałem, czy inaczej.

Jeżeli chcesz już trzymać się terminologii to najlepiej zajrzeć na MSDN. Książka, tak jak wspomniałeś została przetłumaczona z języka angielskiego.

MSDN rzecze:
-„The new modifier creates a new member with the same name and causes the original member to become hidden.”
-„The method that is overridden by an override declaration is known as the overridden base method.”

Tak więc wg MSDN ‚new’ ukrywa, a ‚override’ nadpisuje lub też jak wolisz: przesłania.

Użytkownik Tomek napisał/a:

11 lipca 2015


Szacunek za wykonaną pracę.
Znalazłem jednak pewną nieścisłość. Piszesz:(sprzeczne fragmenty otoczyłem gwiazdkami)

„Zauważ, ponieważ metoda klasy bazowej jest wirtualna, * ***musimy*** przesłonić metodę w klasie pochodnej* za pomocą słowa kluczowego override.
Kilka wniosków na temat metod wirtualnych

metody, która nie jest wirtualna, nie można przesłonić poprzez użycie override
metody statyczne ani prywatne nie mogą być wirtualne
metoda wirtualna * **może** być przesłonięta w klasie pochodnej – **ale nie musi** *. W przypadku braku przesłonięcia zostanie wywołana metoda klasy bazowej.

Najważniejszą cechą metod wirtualnych, jest ta opisana wyższej. **Nie jesteśmy zmuszeni do przesłonięcia metody wirtualnej w klasie bazowej**. Jest to główna różnica w stosunku do metod abstrakcyjnych.”

Poznaję dopiero temat „wirtualności” i ten fragment w pierszym czytaniu (gry analizuje się każde słowo) wprowadził trochę zamętu.
Pozdrawiam

Użytkownik CTS napisał/a:

21 lipca 2015


Bardzo dobry wpis. Daje sporo do myślenia.

Użytkownik Karol napisał/a:

02 sierpnia 2015


@Tomek
Dzięki za zwrócenie uwagi. Nieścisłość wynika z mojego stylu wypowiedzi. Metody wirtualnej nie trzeba przesłaniać, ale jeżeli chcemy ją przesłonić to musimy to zrobić słowem kluczowym override. Poprawiłem ten akapit, aby był bardziej po polsku.

Użytkownik Janek napisał/a:

10 listopada 2015


Coś niesamowitego…. świetny artykuł. Czyli jednak można to normalnie i dostepnie przekazać. Gratuluję !

Użytkownik Patryk napisał/a:

29 marca 2016


Świetny artykuł. Wszystko jest przejrzyście i jasno wytłumaczone, dziękuję! :)

Użytkownik łukasz napisał/a:

19 kwietnia 2016


Niesamowite ! Nigdy nie widziałem tak przejrzystego artykułu o polimorfizmie ! Ostatni rozbudowany przykład – po prostu bajka.

Użytkownik Tomek napisał/a:

24 kwietnia 2016


Bardzo dobry artykuł! W książce, z której się aktualnie uczę (Rusz Głową C#), jest opisany polimorfizm, ale nie w aż tak dokładnym stopniu. Przykłady bardzo dobrze obrazują istotę problemu, analizy rozwiewają wątpliwości. Naprawdę bardzo dobry artykuł :)

Użytkownik Andrzej napisał/a:

22 maja 2016


Świetna robota panie Karolu :)

Użytkownik Puppey napisał/a:

02 czerwca 2016


Naprawdę dobry opis, właśnie uczę się programowania zupełnie od podstaw przy pomocy Pluralsighta. Ilekroć mój angielski zawodzi posiłkuję się Twoimi artykułami na temat C#.
Dziękuję.

Użytkownik Andrzej napisał/a:

02 stycznia 2017


1) „W C++ dopuszczalne jest dopisanie *virtual* jednak nie jest to wymagane… ” <– czy tutaj nie powinno być *override* ?
2) Często używasz terminu "funkcja" zamiast "metoda" – jest to w ogóle poprawne?
Bardzo dobry artykuł – wydaje się że wszystko jest zebrane do kupy i temat jest solidnie omówiony (zamiast "po łebkach" jak w niektórych innych).

Użytkownik Karol napisał/a:

02 stycznia 2017


@Andrzej
1) Słowo kluczowe „virtual” uruchamia mechanizm polimorfizmu. W licznych publikacjach pojawia się ono jednocześnie w metodach w klasie bazowej jak i w przesłanianych metodach klasy pochodnej. Jednak w klasie pochodnej nie jest wymagane, nie trzeba go tam dopisywać. Nastomiast „override” w ogóle nie jest słowem kluczowym C++, nie wpływa na mechanizm polimorfizmu. Jest to dodatkowy modyfikator, którym można udekorować metodę. Dzięki niemu podobno zwiększa się czytelność kodu i podobno kompilator potrafi wyłapać niektóre błędy programisty już w fazie kompilacji. Nigdy nie używałem „override” w C++ jednak nie jestem też programistą tego języka.

2) Metoda jest to funkcja zawarta w obrębie danej klasy. Funkcje w klasie to metody, zmienne w klasie to pola. Metody i pola klasy tworzą atrybuty klasy. Teoretycznie każda metoda jest funkcją, aczkolwiek funkcja nie zawsze jest metodą. W C++ używa się pojęcia funkcji, no ale są też metody w klasach. W C# i Javie teoretycznie wcale nie ma funkcji, ponieważ wszystko jest obiektem i częścią jakiejś klasy.

Ja używam pojęcia funkcji i metody zamiennie ze względu na lepsze pozycjonowanie i rozmieszczenie słów kluczowych w artykule. Połowa ludzi wpisuje w google metoda wirtualna, połowa funkcja wirtualna, w zależności czy bliżej im do C++ czy do Javy.

Użytkownik Gucio napisał/a:

24 stycznia 2017


Dzięki! :)

Użytkownik Łukasz napisał/a:

06 marca 2017


Bardzo dobry artykuł. Bardzo jasno i przejrzyście wytłumaczone.

Użytkownik Budowanie na piasku – Dev on board napisał/a:

13 marca 2017


[…] będących w relacji dziedziczenia.” Tutaj zagadnienie wg. mnie jest idealnie opracowane w tym miejscu dlatego odsyłam Cię do tego artykułu, ponieważ jesteś w stanie z niego wynieść wiedzę […]

Użytkownik Fundamenty programistyczne – Dev on board napisał/a:

13 marca 2017


[…] będących w relacji dziedziczenia.” Tutaj zagadnienie wg. mnie jest idealnie opracowane w tym miejscu dlatego odsyłam Cię do tego artykułu, ponieważ jesteś w stanie z niego wynieść wiedzę […]

Użytkownik Damian napisał/a:

14 maja 2017


Genialny artykuł, szczególnie podobają mi się łańcuchy dziedziczenia, dzięki nim wiele się nauczyłem (chociaż przyznam że chętnie bym sprawdził trudniejsze przykłady)

Zachęcam Cię do zostawienia komentarza!

Ilość znaków: 0

Zachęcam Cię do polubienia bloga na facebooku! Dając lajka wspierasz moją pracę - wszystkie artykuły na blogu są za darmo!