Jason. Cała informatyka w jednym miejscu!

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. Opiszę 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" metod (będąc zgodnym z terminologią języka C). Bardziej po ludzku, zawiera jedynie nagłówki metod, aby można było bez przeszkód "podstawiać" dowolne instrukcje do nich 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 metod (to jest bardzo ważne!). Wymuszanym modyfikatorem jest "public abstract", choć jest szansa że niektóre edytory kodu lepsze od systemowego Notatnika "stwierdzą", że jest to pisane na wyrost i będzie można to pominąć. Wynika to z samej natury interfejsu. Gdyby można było dać metodę prywatną, to jak byśmy uzyskali do niej dostęp? Nielogiczne! A "abstract" tak samo. Wymuszenie implementacji na klasie wykorzystującej tenże interfejs.

Po wspomnianym modyfikatorze piszemy 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ć fraza "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 przedmiotu. Obu można "uszkodzić" na swój własny sposób. Ścianę można uszkodzić doprowadzając ją do ruiny. Przedmiot też można "uszkodzić" w pewnym znaczeniu tego słowa, na przykład łamiąc go na części. Zachowanie jest wspólne, ale skutki różne. U ściany to będą pęknięcia, przedmiot może stać się bezużyteczny. 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ę. Załóżmy, że chcemy zaprogramować uszkodzenie i ściany, i przedmiotu przy pomocy jednego ciągu instrukcji, dajmy na to przez pętlę rozszerzoną. Mamy dwie różne klasy, które nie łączy żadna relacja więc wspólna klasa raczej odpada. Ale dysponując faktem, że mają ten sam interfejs, mamy prawo wykorzystać to jako ich część wspólną i podstawić jako typ tablicy lub kolekcji! To jest manewr jak najbardziej dozwolony.

Przenosząc teorię na kod, załóżmy że podstawiamy sobie kolekcję "ArrayList":

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

która będzie typu naszego interfejsu "Damageable". Teraz możemy dodać obiekt ściany i przedmiotu (zakładając, że zostały uprzednio utworzone):

damageables.add(wall);
damageables.add(item);

i zapewniam, że nie będzie żadnych błędów składniowych. Na mocy "protokołu" wyznaczonego przez interfejs, oba obiekty pochodzą od klas, które muszą posiadać określone definicje metod implementowanych. Dlatego też kompilator nie będzie widział żadnych przeciwwskazań w związku z powyższymi instrukcjami. Zgodnie z moimi poprzednimi tłumaczeniami, "wielopostaciowość" oznacza możliwość zapisu czy zaprogramowania czegoś 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". Tutaj mamy taką sytuację. W tym przypadku naszą "wtyczką" jest protokół wyznaczony przez interfejs w ramach części wspólnej wszystkich instancji.

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 x);
	void die();
}

Zwracam baczniejszą uwagę, tu dałem interfejs, a nie klasę, zatem polimorfizm będzie zapewniany przez "interface" w języku Java!

KLASA "ITEM"
public class Item implements Damageable {
	private int endurance = 100;
	private boolean destroyed;

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

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

			die();
		}

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

	@Override
	public void die() {
		System.out.println("Przedmiot nie nadaje się już do niczego...");
	}
}
KLASA "BARREL"
public class Wall implements Damageable {
	private int endurance = 100;
	private boolean destroyed;

	@Override
	public void takeDamage(int x) {
		endurance -= damage;
		
		if(endurance <= 0 && !destroyed) {
			endurance = 0;
			destroyed = true;

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

	@Override
	public void die() {
		System.out.println("Ściana została zburzona!");
	}
}
KLASA "LAUNCHER"
import java. util.ArrayList;

public class Launcher {
	private Item item;
	private Wall wall;

	public Launcher() {
		createInstances();

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

		damageables.add(item);
		damageables.add(wall);

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

	private void createInstances() {
		item = new Item();
		wall = new Wall();
	}
}

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 przedmiot odbierze to inaczej, a ściana 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. Fragment dotyczący polimorficznego użycia oceniłbym na poziomie średniozaawansowanym, jeśli chodzi o stopień trudności.

PODOBNE ARTYKUŁY