Najpierw trzeba się zastanowić czym jest interfejs i wtedy odpowiedź nasuwa się sama. Otóż interfejs służy do deklarowania metod, które muszą być zdefiniowane w obiekcie implementującym ten interferjs. Wiadomo też, że jeśli klasa implementuje interfejs, to możemy tej klasy użyć w metodzie, która jako argument pobiera typ będący tym interfejsem. Tutaj interfejs występuje w roli typu.
O co chodzi: mamy Klasy "KlasaA", "KlasaB" i interfejs "InterfejsA. "KlasaA" implementuje "InterfejsA" :
Kopiuj
public class KlasaA implements InterfejsA{
public KlasaA() {}
public void zrobCos(){
System.out.println(2);
}
}
public class KlasaB {
public KlasaB(){}
public void uzyj(InterfejsA inter){
inter.zrobCos();
}
}
public interface InterfejsA {
public void zrobCos();
}
Teraz mając gdzieś kod kliencki możemy napisać:
Kopiuj
public static void main(String [] args) {
KlasaA a = new KlasaA();
KlasaB b = new KlasaB();
b.uzyj(a);
}
Do czego to się przydaje w praktyce, i kiedy tego używać:
Przede wszystkim, ważne jest to - że faktycznie zamiast interfejsów można używać klas - można używać klas anonimowych, różne są sposoby którymi można zastąpić interfejsy, ale po co? W powyższym przykładzie aby użyć wyłącznie klas musiałbyś stworzyć trzecią klasę, powiedzmy "KlasaC", a w metodzie "uzyj" klasy "KlasaB" musiałbyś użyć jako argumentu - typu "KlasaC" - to działałoby równie dobrze.
I tutaj pojawia się jeszcze jedna sprawa, są sytuacje w których kod metody "zrobCos" jest zawsze inny. W tym przypadku nie ma sensu pisać klasy. Znacznie lepiej i wygodniej jest użyć interfejsu i implementować go w każdej klasie, której będziemy chcieli użyć w innym miejscu.
Właśnie ActionListener jest idealnym przykładem właściwego wykorzystania interfejsów. W metodzie, którą deklaruje ActionListener - Ty jako programista wymieniasz to, co ma się stać w momencie wykonania kliknięcia czy innej akcji. Możesz tego interfejsu użyć dla wielu swoich klas, które potem możesz przesłać do Componentu Swinga przez addActionListener() (czy jakoś tak, nie pamiętam dokładnie Swinga :).
Najważniejszą korzyścią jest to, że nie musisz tworzyć nowej klasy, która implementowałaby zachowanie po kliknięciu, lecz implementujesz to zachowanie w istniejącej klasie - metodą, której wymaga interfejs. Zasadniczo ta metoda to fragment kodu, który chcesz, żeby się wykonał po kliknięciu. Ułatwia to kilka spraw, bo:
- Używając powyższego przykładu - KlasaC nie mając dostępu do danych KlasyB nie ma możliwości interakcji z nią.
- Aby umożliwić klasieC dostęp do KlasyB, musiałbyś w niej zawrzeć referencję do niej i wywoływać wszelkie jej metody... a to jest problem, bo też KlasaC nie ma dostępu do prywatnych i zabezbieczonych metod i pól KlasyB.
Bardzo ładnie można to wytłumaczyć poprzez analogię do funkcji anonimowych w programowaniu funkcjonalnym np: JavaScript.
W JS można przesłać funkcję anonimową (lub referencję do funkcji) do innej funkcji. Poniższy kod:
Kopiuj
function doFoo(funcBar) {
funcBar();
}
doFoo(
function() { alert("2")}
)
doFoo(function() { alert("5")}
... wyświetli "2" i potem "5".
Podobny efekt można użyskać w Javie, z użyciem interfejsów. W moim przykładzie, przesyłając obiekt KlasyA do obiektu KlasyC - w konsoli zostanie wypisane "2".
Jest jedna bardzo ważna zasada: dwie klasy implementujące ten sam interfejs nie mogą tego robić tak samo. Chodzi o to, że metody zrobCos obu klas muszą być różne bo wtedy można wydzielić ten kod do klasy nadrzędnej. Wyjątkiem mogą być jakieś banalne implementacje.
Zasadniczo interfejs DEFINIUJE ktoś kto tworzy bibliotekę - który będzie używany w kodzie klienckim (korzystającym z tej biblioteki).
Jeśli piszesz aplikację pod konkretne biblioteki to raczej mało prawdopodobne, że będziesz tworzył interfejsy, będziesz je raczej IMPLEMENTOWAŁ. Wiem to z własnego doświadczenia: tworząc silnik 3d miałem właśnie do czynienia z nasłuchiwaniem zdarzeń i wyglądało to bardzo podobnie do tego jak stworzony jest Swing. Przed tym też nie rozumiałem zastosowania interfejsów.