Zapraszam po kilku dniach na następny artykuł na temat Javy. Pamiętacie jak pisałem o polimorfizmie, jednym z postulatów dotyczących programowania obiektowego? Tam prezentowałem taki przykład, który dotyczył jednej "rodziny" klas i tym samym, dziedziczenie stanowiło część wspólną. A co gdy chcemy wyjść poza jedyny "sznurek" powiązań i odwołać się do wszystkich za pomocą interfejsu? Polimorfizm raz kolejny staje się głównym powodem i zarazem największą zaletą jak może nam to pomóc w pisaniu logicznie powiązanego kodu. Zapraszam serdecznie do lektury!

POLIMORFIZM POPRZEZ INTERFEJS

Zgodnie z moimi poprzednimi tłumaczeniami, "wielopostaciowość" oznacza możliwość zapisu czy zaprogramowania na kilka różnych sposobów. Czasem definiowane jest to również jako "przybieranie różnych form". W każdym razie, chodzi o ujednolicenie kilku różnych źródeł do jednej "wtyczki". Dziedziczenie również sprzyja polimorfizmowi. Przecież możemy utworzyć zmienną referencyjną zarówno tak:

Wolf wolf = new Wolf();

jak i tak:

Animal wolf = new Wolf();

Pamiętajcie że nawet, jeśli "Animal" jest klasą abstrakcyjną, to nic nie stoi na przeszkodzie, aby przypisać do takiej klasy odwołanie istniejącego obiektu, bądź tworzonego klasy potomnej. Jedynie tworzenie instancji jest zabronione!

W tym artykule będziemy rozpatrywać polimorfizm od zupełnie innej strony. Rozpatrzymy przypadek gdy chcemy wydobyć część wspólną z klas niemających ze sobą nic wspólnego. Jak to można zrobić poza dziedziczeniem? Zostaje jeszcze drugi wariant: implementacja interfejsu! Przypominam, że interfejs jest zestawem abstrakcyjnych metod i każda klasa wykorzystująca dany interfejs MUSI przesłonić wszystkie bez wyjątku metody wchodzące w skład tego interfejsu.

Nie gniewajcie się, ucieknę się znowu do podstawienia do gry. Mamy gracza i beczkę. Gracz jest człowiekiem, beczka elementem interaktywnym. Nie wiążą ich żadne relacje od strony dziedziczenia. Chociaż obaj mogą odnosić obrażenia. To będzie część wspólna! Reakcja na sytuację odnoszenia obrażeń. Skutki będą inne, ale szczegóły zostaną zawarte w metodach, które przesłonimy.

KOD ŹRÓDŁOWY Z INTERFEJSEM

OK, problem przedstawiony, teraz gotowy kod źródłowy do złożenia i uruchomienia. Prześledźcie wszystko powoli:

  • KLASA "Main"
public class Main
{
	public static void main(String[] args)
	{
		new Launcher();
	}
}
  • INTERFEJS "Damageable"
public interface Damageable
{
	void takeDamage(int n);
	void die();
}

Zwracam baczniejszą uwagę, tu dałem interfejs, a nie klasę, zatem polimorfizm będzie zapewniany przez interfejs! 

  • KLASA "Player"
public class Player implements Damageable
{
	private int health = 100;
	private boolean died = false;

	@Override
	public void takeDamage(int n)
	{
		health -= n;

		if(health <= 0 && !died)
		{
			health = 0;
			died = true;

			die();
		}
	}

	@Override
	public void die()
	{
		System.out.println("Gracz zmarł...");
	}
}
  • KLASA "Barrel"
public class Barrel implements Damageable
{
	private int endurance;
	private boolean exploded;

	public Barrel()
	{
		endurance = 100;
	}

	@Override
	public void takeDamage(int n)
	{
		endurance -= damage;

		System.out.println("Wytrzymałość beczki: " + endurance);

		if(endurance <= 0 && !exploded)
		{
			endurance = 0;
			exploded = true;

			die();
		}
	}

	@Override
	public void die()
	{
		System.out.println("Bum!");
	}
}
  • KLASA "Launcher"
import java. util.ArrayList;

public class Launcher
{
	private Player player;
	private Barrel barrel;

	public Launcher()
	{
		createInstances();

		ArrayList<Damageable> damageableObjects = new ArrayList<>();

		damageableObjects.add(player);
		damageableObjects.add(barrel);

		for (int t = 1; t <= 5; ++t)
		{
			for (Damageable d : damageableObjects)
			{
				d.takeDamage(50);
			}
		}
	}

	private void createInstances()
	{
		player = new Player();
		barrel = new Barrel();
	}
}

Polimorfizm w tym przykładzie prezentuję się inaczej niż poprzednio. Można by się uprzeć, że jakieś powiązanie między dwoma obiektami jest. Na przykład, oba obiekty byłyby modelami trójwymiarowymi. Ale to szczegóły przesłaniające istotę tego artykułu. Tworzymy sobie prosty interfejs posiadający dwie metody: "takeDamage" oraz "die". Odpuszczę sobie tłumaczenie obiektów i przejdę do rzeczy.

W klasie "Launcher", naszej klasie uruchomieniowej, dorzucamy sobie instancje naszych niepowiązanych ze sobą obiektów i dodajemy je sobie do listy. Tu jest ciekawie! Nie dorzucamy klasy jako części wspólnej (bo w tym przypadku jedyną częścią wspólną byłby "Object"), tylko interfejs! To interfejs będzie scalał nam obiekty różnych typów. Rozumiecie już czemu polimorfizm określa się "przybieraniem wielu postaci"?

Następnie poprzez pętlę "for", programujemy odwoływanie się do obiektów listy za pomocą drugiej pętli rozszerzonej i w jej środku wywołujemy metodę, która nie pojawia się ani w jednej, ani w drugiej klasie. Za to pojawia się ona w interfejsie! Interfejsie, który pełni rolę "podtrzymywacza" więzi. I teraz metoda "takeDamage" mająca tę samą postać i w jednej, i w drugiej klasie wykona wszystkie instrukcje uszczegółowione w naszych klasach. Czyli gracz odbierze to inaczej, a beczka inaczej, ale program dostanie się tam, gdzie trzeba.

Polimorfizm poprzez interfejs

Interfejs również może stanowić część wspólną dla obiektów podpiętych do listy czy tablicy za pomocą przesłanianych metod abstrakcyjnych.


Na tym wywód uważam za skończony. To już był troszkę trudniejszy do przyswojenia przejaw tego, jak działa polimorfizm, aczkolwiek zrozumienie tego pozwoli Wam pisać o wiele lepszy i bardziej zrozumiały kod! Chociaż wcale nie jest konieczne opanowanie tego przez początkujących. Ja bym to ocenił na poziomie średniozaawansowanym, jeśli chodzi o stopień trudności.

PODOBNE ARTYKUŁY