Jak wspominałem we wpisie inaugurującym działanie tego bloga, jestem w trakcie wzbogacania swoich umiejętności programistycznych o język C#. W tym celu przeglądam m.in. różne materiały w sieci, czy to artykuły na MSDN, czy to blogi, czy też różne kursy video. Jednym z takich kursów jest opublikowany na portalu virtualstudy.pl kurs języka C#. Zaciekawiony tym kursem (wiele osób zapraszało na niego lub go polecało) postanowiłem się z nim zapoznać. Na początek obejrzałem sesję pierwszą i drugą. Niestety w drugiej, która traktowała o programowaniu obiektowym jako takim, po raz pierwszy natknąłem się na nieścisłość związaną z polimorfizmem. Przejrzałem jeszcze pokrótce sesje trzecią i czwartą, ale ponieważ owa nieścisłość mocno mnie niepokoiła, a w kursie znajdowała się  sesja dedykowana polimorfizmowi (i dziedziczeniu), postanowiłem sprawdzić, czy zdefiniuje ona polimorfizm poprawnie.

Niestety (jak łatwo się domyślić – inaczej ten wpis nie miałby sensu) również tam pojawiła się dokładnie ta sama nieścisłość.

W rzeczonej sesji pojęcie polimorfizmu zaczyna być omawiane około 01:13:30 jej czasu i jest zdefiniowane w ten sposób: „…polimorfizm polega na tym, że możemy zmieniać implementację metod…” czyli (jak podano w podsumowaniu) jest to „…zmiana zachowania metod…”. Otóż nie. Umiejętność zmiany implementacji metod, to część mechanizmu dziedziczenia, które (również nie tak, jak błędnie podano w podsumowaniu tejże sesji) pozwala na współdzielenie funkcjonalności pomiędzy klasami, a także zmianę tychże funkcjonalności.

Jako przykład na poparcie tej właśnie definicji polimorfizmu podawana jest metoda ToString() – jedna z metod klasy Object, bazowej klasy dla wszystkich klas .NET. Metoda ta w swej pierwotnej, bazowej postaci zwraca po prostu kwalifikowaną nazwę obiektu. Dodatkowo jako wirtualna umożliwia zastąpienie swojej implementacji adekwatną do potrzeb klasy dziedziczącej (np. dla typu int zwracany jest łańcuch reprezentujący przechowywaną liczbę). Przykład wygląda następująco (w komentarzach podano zwracaną wartość):

int i = 10;
i.ToString(); // 10
Czlowiek cz = new Czlowiek();
cz.ToString(); // ProsteDziedziczenie.Ludzie.Czlowiek

Należy zauważyć, że ten sam efekt, można uzyskać nie dziedzicząc metody. Można utworzyć dwie niezależne od siebie klasy i zaimplementować w nich metody o tej samej nazwie, które wywołane w ten sam sposób, jak w powyższym przykładzie dadzą analogiczne rezultaty, czyli każda wykona tę metodę na swój sposób. Przykład poniżej:

	class FirstClass
	{
		public string ToText()
		{
			return "Jestem pierwsza klasa";
		}
	}

	class SecondClass
	{
		public string ToText()
		{
			return "Jestem klasa druga";
		}
	}

	static public class EqualMethodNames
	{
		static public void Show()
		{
			FirstClass FC = new FirstClass();
			Console.WriteLine(FC.ToText());
			SecondClass SC = new SecondClass();
			Console.WriteLine(SC.ToText());
		}
	}

Jak widać każda klasa zachowała się inaczej, czyli analogicznie, jak w oryginalnym przykładzie z sesji. Idąc tropem nieprawidłowej definicji, należałoby stwierdzić, że to też jest polimorfizm, a to nieprawda.

Na czym więc polega polimorfizm? Polimorfizm to umiejętność przyjmowania przez obiekt danej klasy (uwaga, obiekt, a nie klasę) postaci, która jest zaimplementowana (podkreślam – zaimplementowana) w klasie potomnej. Wyróżnia się jeszcze inne rodzaje polimorfizmu, ale o tym może innym razem (niecierpliwi mogą sobie zajrzeć na Wikipedię, warto przy okazji zerknąć na „dziedziczenie”, które też zawiera fragment o polimorfizmie).

Przykładem polimorfizmu może być następujący przypadek. Definiujemy bazową klasę Umowa, która będzie posiadała metodę Wynagrodzenie (oczywiście wirtualną, aby można było zastępować jej implementację), w swej pierwotnej postaci zwracającą wartość wynagrodzenia równą zero (lub ustaloną w konstruktorze). Po tej klasie będą dziedziczyć klasy umów dla konkretnej formy zatrudnienia (pełny etat, wykonanie dzieła, praca na akord, etat + premia), które będą posiadały dodatkowe metody, definiujące właściwe tylko dla danego typu umowy właściwości atrybuty. Niemniej, każda z tych klas będzie musiała implementować metodę Wynagrodzenie, aby prawidłowo wyliczyć je dla danego typu zatrudnienia. Poniżej przykład implementacji, z pominięciem dodatkowych, dedykowanych dla danego typu umowy metod, w celu uzyskania jak najlepszej czytelności kodu.

	class Umowa
	{
		private decimal fWynagrodzenie;
		public Umowa(decimal aWynagrodzenie = 0.00M)
		{
			fWynagrodzenie = aWynagrodzenie;
		}
		public virtual decimal Wynagrodzenie()
		{
			return fWynagrodzenie;
		}
	}

	class Akord : Umowa
	{
		private int fWykonano;
		public Akord(decimal aStawka, int aWykonano)
			: base(aStawka)
		{
			fWykonano = aWykonano;
		}
		public override decimal Wynagrodzenie()
		{
			return base.Wynagrodzenie() * fWykonano;
		}
	}

	class Etat : Umowa
	{
		private decimal fWymiarEtatuWProcentach;
		public Etat(decimal aWynagrodzenie, decimal aWymiarEtatuWProcentach)
			: base(aWynagrodzenie)
		{
			fWymiarEtatuWProcentach = aWymiarEtatuWProcentach;
		}
		public override decimal Wynagrodzenie()
		{
			return base.Wynagrodzenie() * fWymiarEtatuWProcentach / 100;
		}
	}

	class EtatPremiowany : Etat
	{
		private int fProcentPremii;
		public EtatPremiowany(decimal aWynagrodzenie, decimal aWymiarEtatuWProcentach, int aProcentPremii)
			: base(aWynagrodzenie, aWymiarEtatuWProcentach)
		{
			fProcentPremii = aProcentPremii;
		}
		public override decimal Wynagrodzenie()
		{
			decimal W = base.Wynagrodzenie();
			return W + (W * fProcentPremii / 100);
		}
	}

	class PoprawnyPolimorfizm
	{
		private List<Umowa> Umowy = new List<Umowa>();
		public PoprawnyPolimorfizm()
		{
			Umowy.Add(new Etat(1000, 100));
			Umowy.Add(new Akord(10, 200));
			Umowy.Add(new EtatPremiowany(800, 100, 30));
			Umowy.Add(new Umowa()); // np. bezpłatny staż
		}
		public decimal IlePotrzebaNaWynagrodzenia()
		{
			decimal suma = 0.00M;
			foreach(Umowa u in Umowy)
			{
				suma += u.Wynagrodzenie();
			}
			return suma;
		}
	}

Z punktu widzenia wyliczenia wynagrodzenia wystarczy po prostu zadeklarować zwykłą listę obiektów klasy Umowa. Następnie jako elementy listy należy dodać konkretne instancje klas potomnych. Metoda wyliczająca wynagrodzenia będzie operować na obiektach typu Umowa, nie mając pojęcia, jakie dokładnie umowy zawarto. Dzięki polimorfizmowi (czyli możliwości użycia dowolnej postaci klasy potomnej) wyliczy ona prawidłowe wynagrodzenia (jak pokazano w metodzie IlePotrzebaNaWynagrodzenia, która oblicza ile potrzeba zasobów finansowych na wynagrodzenia dla zatrudnionych osób), posługując się w rzeczywistości instancjami obiektów stosownych klas.

Powtórzę więc na zakończenie, licząc, że przyczyni się to do zmniejszenia liczby podobnych nieścisłości: polimorfizm to umiejętność przyjmowania przez obiekt danej klasy postaci, która jest zaimplementowana w jej dowolnej klasie potomnej (czyli – jak pokazano w przykładzie – nie tylko bezpośredniej).

I na wszelki wypadek, wiedząc, jak wrażliwi są rodacy, chciałbym zaznaczyć, że niniejszy wpis jest polemiką mającą na celu jedynie zadbanie o poprawność definicji pojęcia polimorfizmu, a nie piętnowanie czyichś błędów (co mam nadzieję udało mi się osiągnąć). Albowiem błądzenie jest rzeczą ludzką, trwanie w błędzie – głupotą.