Powracamy znowu do języka Java. Po raz pierwszy pragnę się wypowiedzieć na temat interfejsów i przykładów ich zastosowania. Aby nikt nie poczuł się zagubiony, artykuł ten będzie podzielony na dwie części: pierwsza będzie służyła wstępnemu tłumaczeniu czym jest interfejs i co ma z tym wspólnego słowo kluczowe "interface" w języku Java, a druga będzie obrazować przykład ich sensownego wykorzystywania. Opowiem Wam także jak można wykorzystać polimorfizm w stosunku do interfejsu i co nam to daje w efekcie końcowym.

"INTERFACE" W JĘZYKU JAVA. TŁUMACZENIE NA SZYBKO

Interfejs to zestaw metod abstrakcyjnych posiadających jedynie "prototypy" funkcji (będąc zgodnym z terminologią języka C). Bardziej po ludzku, zawiera jedynie nagłówki funkcji, aby można było bez przeszkód "podstawiać" dowolne instrukcje do tych metod w każdym innym obiekcie implementującym ten sam interfejs. Są bardzo przydatne w sytuacjach gdy dwie różne klasy są sobie obce, nie mają ze sobą powiązań ani części wspólnych, ale współdzielą te same zachowania lub zdarzenia.

To jest taka szybka definicja zachowująca najważniejsze elementy. Oto podstawowy kod źródłowy przedstawiający jak się tworzy interfejsy za pomocą "interface" w języku Java:

interface Damageable
{
	public abstract void damage(int x);
}

W pierwszej kolejności idzie słowo kluczowe "interface", następnie dowolna nazwa i para klamerek. W środku bloku deklarujemy same nagłówki funkcji (to jest bardzo ważne!). Dodajemy modyfikator "public abstract" oraz typ zwracanej wartości. W naszym przykładzie zostaniemy przy "void". Potem dowolna nazwa metody oraz ewentualne parametry formalne. Gdy chcemy mieć więcej metod w interfejsie, po średniku piszemy taki sam wzór, tylko zmieniamy nazwę i dorzucamy parametry (o ile jakiekolwiek mają być). Interfejsy wymagają zdefiniowania WSZYSTKICH metod wchodzących w skład zaimplementowanego interfejsu przez dowolną klasę.

Korzystając z kolejnego słowa kluczowego "implements" jesteśmy w stanie zaimplementować nasz interfejs do dowolnej klasy. Jeżeli klasa ta dziedziczy od innej klasy wyższego rzędu, najpierw ma być klauzula "extends", a potem "implements". Poniższy kod pokazuje jak działają interfejsy implementowane przez klasę.

class Wall implements Damageable
{
	private int endurance = 50;
	
	public void damage(int x)
	{
		endurance -= x;
		
		if(endurance <= 0)
		{
			System.out.println("Ściana została zniszczona...");
		}
		else
		{
			System.out.println("Ściana otrzymała " + x + " obrażeń.");
		}
	}
}

PRZYKŁADY SENSOWNYCH ZASTOSOWAŃ

Tutaj sobie darujemy kody źródłowe, podam jedynie "tekstowe" przykłady sensownych zastosowań interfejsów. Pierwszy z nich jest nader oczywisty, to jest "podpinanie" zdarzeń do przycisków, pól tekstowych, klawiatury i tym podobnych rzeczy. Na przykład dla wykrywania wciśniętych klawiszy dodajecie "addKeyListener" i podajecie wszystkie metody z notacją "@Override". Tak, to też jest interfejs.

Drugi przykład to wyszukiwanie wspólnych zachowań dla obiektów, które nie łączy żadna relacja ani podobieństwo, tak jak napisałem poprzednio. Przykład, który zawsze podaję to jest klasa ściany i człowieka. Obu można "uszkodzić" na swój własny sposób. Ścianę można uszkodzić doprowadzając ją do ruiny. Człowieka też można "uszkodzić" w pewnym znaczeniu tego słowa, na przykład rozbijając mu nos. Zachowanie jest wspólne, ale skutki różne. U ściany to będą zgliszcza, u człowieka poniesienie śmierci.

Może mało elegancki przykład jednak potrafi dobrze zobrazować jak bardzo użyteczne są interfejsy definiowane za pomocą "interface" w języku Java. Trzeci przykład to zapisywanie obiektów, czyli serializacja. W chwili zapisu, nie ma znaczenia czy chcemy przechować login, obrazek czy współrzędne modelu trójwymiarowego jako obiekt w grze. Jest wspólny czynnik? Jest!

POLIMORFIZM POPRZEZ "INTERFACE" W JĘZYKU JAVA

Trzeba opisać jeszcze jedną kwestię. 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. I tak się składa, że interfejs RÓWNIEŻ się zalicza do tematów spokrewnionych.

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.

PRZYKŁAD KODU ŹRÓDŁOWEGO

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 "interface" w języku Java! 

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 z "interface" w języku Java 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 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.

Słowo kluczowe "interface" w języku Java

Interfejs to zestaw metod abstrakcyjnych. Na mocy postulatu polimorfizmu, również może stanowić część wspólną dla obiektów podpiętych do listy czy tablicy za pomocą przesłanianych metod abstrakcyjnych. Tworzony jest przy pomocy słowa kluczowego "interface" w języku Java.


To tyle na dzisiaj. Interfejsy to bardzo ważna rzecz i każdy programista Java musi rozumieć sens ich istnienia. 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