Jason. Cała informatyka w jednym miejscu!

Powracamy znowu do języka Java. Po raz pierwszy można 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 MOŻE BARDZO POMÓC

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 {
	void takeDamage(int damage);
}

W pierwszej kolejności leci 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 dostępu jest "public", choć edytory kodu lepsze od systemowego Notatnika "stwierdzą", że jest to pisane na wyrost i można go pominąć. Wynika to z samej natury interfejsu. Gdyby można było dać metodę prywatną, to jak byśmy uzyskali do niej dostęp :P? Nielogiczne! A "abstract", bo ma być wymuszenie implementacji na klasie wykorzystującej ten 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 MyClass implements Damageable {
	@Override
	public void takeDamage(int damage) {
		// instrukcje
	}
}

PRZYKŁADY 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 przesłaniacie 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. Natomiast 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 jednym z postulatów paradygmatu obiektowego, "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 damage);
	void die();
}

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

KLASA "ENTITY"
public abstract class Entity implements Damageable {
	private int health = 100;
	private boolean destroyed;

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

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

			die();
		} else {
			System.out.println("Zdrowie: " + health);
		}
	}
}
KLASA "ITEM"
public class Item extends Entity {
	@Override
	public void die() {
		System.out.println("Przedmiot nie nadaje się już do niczego...");
	}
}
KLASA "WALL"
public class Wall extends Entity {
	@Override
	public void die() {
		System.out.println("Ściana została zburzona!");
	}
}
KLASA "LAUNCHER"
import java. util.ArrayList;

public class Launcher {
	private Item item = new Item();
	private Wall wall = new Wall();

	public Launcher() {
		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);
			}
		}
	}
}

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 przykładu. 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łoby "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. Świetnie :D!

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 :D!

PODOBNE ARTYKUŁY