– Cześć, Amigo! Dzisiaj opowiem Ci, do czego są nam właściwie potrzebne interfejsy. Domyślam się, że często słyszysz, że pewna klasa, obiekt czy jednostka obsługuje dany interfejs. Ale co to właściwie znaczy: obsługiwać interfejs?

Zadaniem interfejsów jest obsługa konkretnego zachowania - 1

Ogólnie rzecz biorąc, interfejs jest mechanizmem, który umożliwia danej rzeczy wchodzenie w interakcje z czymś innym. Na przykład, pilot do telewizora jest interfejsem zdalnego sterowania. Jeśli pies rozumie i wykonuje komendy, mówimy, że obsługuje on interfejs głosowy (sterowania głosem). Podsumowując, można powiedzieć, że interfejs stanowi pewien standaryzowany sposób na przeprowadzenie interakcji pomiędzy dwoma bytami, pod warunkiem, że istnieje pomiędzy nimi swoista umowa definiująca szczegóły tych interakcji. Kiedy ktoś mówi do psa „siad”, to wydaje mu komendę, która jest częścią psiego «interfejsu sterowania głosem». Jeśli pies tę komendę wykonuje, to mówimy, że obsługuje on dany interfejs.

Dokładnie tak samo jest w programowaniu. Metody są działaniami przeprowadzanymi na obiekcie i jego danych. Jeśli klasa implementuje dane metody, to znaczy, że «obsługuje wykonanie» konkretnych komend. A co zyskujemy dzięki łączeniu metod w interfejs?

1) Każdy interfejs, tak jak klasa, ma swoją unikalną nazwę. Dzięki temu każda strona może być w 100% pewna, że strona przeciwna wspiera dokładnie ten sam interfejs, który obie one znają, a nie, na przykład, interfejs, który jest zaledwie podobny.

2) Każdy interfejs narzuca klasie, która będzie go obsługiwać, pewne określone restrykcje. To klasa (czy też jej programista) decyduje, co będzie robić, kiedy wywołane zostaną metody dziedziczone z interfejsu, ale wynik powinien mieścić się w ramach rozsądnych oczekiwań. Jeśli wydamy psu komendę „siad”, a ten tarza się po ziemi przez 5 minut i dopiero potem siada, to obsługuje on ten interfejs. Ale jeśli zamiast tego ciągnie Cię za nogawkę, to musimy przyznać, że jednak tego interfejsu nie obsługuje. Wykonanie komendy różniło się od oczekiwanego rezultatu.

Załóżmy, że wraz z przyjaciółmi tworzysz grę komputerową. Przypisałeś/aś do programu zachowanie pewnej postaci. Okazuje się, że jeden z Twoich znajomych napisał już wcześniej kod, który wyświetla na ekranie wszystkich bohaterów gry. Drugi kolega, odpowiedzialny za zapisywanie gry na dysku, napisał kod zachowujący w pliku stan wszystkich obiektów. Obaj napisali wiele linijek kodu oraz stworzyli interfejs, który wchodzi w interakcje z kodem. Na przykład, może to wyglądać tak:

Kod Java Opis
interface Saveable
{
 void saveToMap(Map<String, Object> map);
 void loadFromMap(Map<String, Object> map);
}
— Interfejs służący do przechowywania/ładowania obiektu z mapy.
interface Drawable
{
 void draw(Screen screen);
}
— Interfejs służący do rysowania obiektu wewnątrz przekazanego obiektu Screen.
class PacMan implements Saveable, Drawable
{
…
}
— Twoja klasa, która obsługuje dwa interfejsy.

Innymi słowy, Twoja klasa, aby obsłużyć dany interfejs (grupę interfejsów), musi:

1) Dziedziczyć te interfejsy

2) Implementować metody w nich zadeklarowane

3) Metody muszą robić to, do czego zostały przeznaczone.

Dopiero wtedy reszta kodu, który nic nie wie o Twojej klasie i jej obiektach, może współpracować z Twoją klasą.

– Dlaczego kod miałby nic nie wiedzieć o mojej klasie?

– Wyobraź sobie, że bierzesz kod napisany rok temu przez inną osobę. Albo że Twoi znajomi kupili/licencjonowali silnik czyjejś gry komputerowej. Posiadasz już działający kod dla tej gry. Tysiące obiektów, które wchodzą ze sobą w interakcje. Będą one mogły prawidłowo wchodzić w interakcje z Twoimi obiektami, jeśli zrealizujesz te interakcje poprzez interfejsy, które Twoje klasy poprawnie zaimplementują.

– Ale odlot! Nie wiedziałam, że coś takiego jest w ogóle możliwe.

– Wszystkie duże projekty działają w ten sposób. Ludzie już dawno temu zrezygnowali z pisania wszystkiego od początku.

Nie chcą za każdym razem na nowo odkrywać alfabetu czy praw matematyki. Zamiast tego wolą zapoznać się ze wszystkim, co zostało wynalezione już wcześniej.