Niniejszy artykuł będzie robił za podsumowanie tego "sznurka" poprzednich wpisów na temat samego dziedziczenia w języku Java. Być może przypomni mi się coś jeszcze istotnego a na razie, wypuszczam ostatnią planowaną część na temat dziedziczenia w której polimorfizm wkracza do akcji. Polimorfizm i dziedziczenie to dwa fundamenty programowania obiektowego stojące obok hermetyzacji. Ona sama dotyczy jedynie określenia dostępu do danych, natomiast jeśli zajdzie taka potrzeba, o niej też wspomnę w osobnym artykule. Zakończmy to, co zaczęliśmy już dawno temu!

POLIMORFIZM I DZIEDZICZENIE. ZASTRZYK TEORII

Na początek, wyjaśnienie terminu: polimorfizm zwany też "wielopostaciowością" to postulat programowania obiektowego mówiący o tym, że dane wyrażenie, obiekt lub podprogram może być skonstruowane i wykorzystane na kilka różnych sposobów. Innymi słowy, jedna rzecz może przyjmować wiele wcieleń naraz. Przykładem z życia wziętym może być dorastający chłopiec. Jest czyimś synem czyli ma rodziców, jest uczniem bo chodzi do szkoły, a wsiadając do samochodu jest pasażerem. Jedna osoba przyjmuje wiele ról.

W programowaniu, polimorfizm i dziedziczenie dotyczą pewnych konstrukcji omawianych wcześniej. Być może się nie orientujecie, że część z nich była już prezentowana, a dotyczyła ona metod klas. Co robiliśmy z metodami z klasy "Character"? Przeciążaliśmy je! przeciążanie metod to przejaw stosowania wielopostaciowości. A metoda "toString" przyjmująca na klatę odpowiedzialność formatowania tekstu w chwili podania samej referencji do obiektu? Przesłanialiśmy ją i dostosowywaliśmy tekst do naszych upodobań. Nadpisywanie metod to także zbawienny wpływ polimorfizmu. Metody mogące podlegać przesłonięciu nazywane są często metodami "wirtualnymi", natomiast na tym zakończymy tę teoretyczną pogawędkę.

Być może to dziwne, że powtarzam się znowu na ten sam temat, aczkolwiek chciałem to najpierw "rozbić" na drobniejsze zagadnienia, a dopiero potem "ujawnić prawdę" co to naprawdę jest i jak to opisać własnymi słowami jak przy pokazywaniu palcem. Poznaliśmy słowa "extends", "abstract", "final", "super", a wcześniej jeszcze adnotację "@Override". Gdybym teraz wstawił ten cały zestaw słówek do jednego artykułu, cała seria artykułów na nic by się zdała. Mając nadzieję, że to rozumiecie, przejdźmy do przykładu w postaci kodu źródłowego ukazującego polimorfizm i dziedziczenie już w całej okazałości.

KOD ŹRÓDŁOWY KROPKĄ NAD I

Oprócz znanych już trików wprowadziłem jeszcze jeden zapis przejawiający zdolności polimorfizmu. Polimorfizm to także możliwość przypisania obiektu potomnego do obiektu typu nadrzędnego. W naszym przypadku, obiekt "Warrior" może być bez żadnych problemów przypisany do obiektu typu "Character" (notabene, że to jest klasa abstrakcyjna!). Może to nieść za sobą pewne konsekwencje gdy dochodzą nowe dane składowe do klas potomnych, natomiast przekonajcie się już sami jak to wygląda. Oto połączony polimorfizm i dziedziczenie:

  • KLASA "Main"
public class Main
{
	public static void main(String[] args)
	{
		Warrior w = new Warrior();
		Character c = new Warrior();

		w.setLevel(2);
		c.setLevel(5);

		w.learnAbility("Potężne uderzenie");
		//c.learnAbility("Ogłuszenie tarczą");

		System.out.println(w);
		System.out.println(c);
	}
}
  • KLASA "Character"
public abstract class Character
{
	private int level;

	public Character(int level)
	{
		setLevel(level);
	}

	public void setLevel(int level)
	{
		this.level = level;
	}

	@Override
	public String toString()
	{
		return "Klasa " + getClass().getSimpleName() + ". Mam poziom " + level;
	}
}
  • KLASA "Warrior"
public class Warrior extends Character
{
	private String ability;

	public Warrior()
	{
		super(1);
	}

	@Override
	public void setLevel(int level)
	{
		super.setLevel(level);
		System.out.println("\"Warrior\" teraz ma poziom " + level + ".");
	}

	public void learnAbility(String ability)
	{
		this.ability = ability;

		System.out.println("Nowa zdolność! Jest to " + ability + ".");
	}

	@Override
	public String toString()
	{
		return "Klasa " + getClass().getSimpleName() + ". Zdolność: " + ability;
	}
}

Oto polimorfizm i dziedziczenie w nieco większej okazałości. Z powyższego kodu wynika, że przypisanie obiektu potomnego do typu klasy nadrzędnej nie powoduje wywoływania metod klasy bazowej. To cenna informacja, gdyż widać po tym, że nie dochodzi do utraty referencji i kompilator zachowuje odwołanie do klasy potomnej czyli "wie" jakimi "wersjami" metod ma się posługiwać (spójrzcie wyraźnie na teksty zwracane przez "toString" podczas uruchomienia). Druga rzecz warta uwagi to taka, że takie przypisanie choć nie miesza w wywołaniach metod, to robi się już problem przy próbie wywołania funkcji istniejącej tylko w klasie potomnej. "learnAbility" widoczne tylko w klasie "Warrior" nie istnieje dla typu "Character", zatem zakomentowany wpis spowodowałby błąd kompilacji, ponieważ mimo zachowania odpowiedniego typu referencji, to kompilator musi traktować ten obiekt jako obiekt klasy nadrzędnej, a to znaczy, że musi mimo wszystko sugerować się listą metod zawartych w klasie "Character".


OK, na tym kończymy! Dziękuję wszystkim tym, którzy wytrwali do końca i przeczytali każdą część bez wyjątku.

PODOBNE ARTYKUŁY