Rozszerzanie interfejsów
W trzecim tygodniu kursu zapoznałeś się z interfejsami – wtedy za bardzo się nimi nie zajmowaliśmy. Czas, aby wziąć się za zagadnienie związane z rozszerzanie interfejsów – tak samo jak klasy mogą być rozszerzane.
Na początku uczulam, aby przykład, który będzie pokazywany w tej lekcji wziąć z przymrużeniem oka – nie jestem znawcą zwierząt, a stwierdziłem, że dzięki takiemu przykładowi łatwiej zrozumiesz działanie interfejsów.
Powrót do przeszłości…
Może pamiętasz z trzeciego tygodnia czym jest interfejs…
Jeśli nie to pokrótce Ci przypomnę, choć mam nadzieję, że jest to zbędne.
Interfejs utożsamialiśmy z osobą, która mówi drugiej osobie (klasie) co ma robić – nie obchodzi jak to zrobi, dla niej ma to po prostu być zrobione.
W takim razie w interfejsie były deklarowane metode, jednak bez implementancji – w końcu, każda osoba (klasa), może to wykonać w inny sposób. Liczy się tak naprawdę tylko efekt.
Skoro już jesteśmy po przypomnieniu sobie czym jest interfejs to możemy wziąć się za jego rozszerzanie. 😉
Rozszerzanie interfejsów
Tak jak klasa może być rozszerzona przez inną klasę, tak samo interfejs może być rozszerzony przez interfejs.
Jest tylko mała różnica – klasa może być rozszerzona tylko przez jedną klasę, za to interfejs może być rozszerzony przez wiele interfejsów.
W innych językach obiektowych spotkacie się raczej z inną zasadą – szczególnie, że w innych raczej nie ma czegoś takiego jak interface, to w Javie nacisk został postawiony na interfejsy, a nie dziedziczenie kilku klas. 😉
Po co nam to?
Czasami możemy wyróżnić właśnie kilka interfejsów, które są zależne od siebie – posiadają te same metody. Dlatego zamiast powielać je kilkukrotnie w różnych interfejsach to warto umieścić je w jednym i rozszerzać ten interfejs w innych.
Co może być takim przykłądem?
Weźmy sobie królestwo roślin lub zwierząt – wybierzmy to drugie.
Możemy stworzyć interfejs dla każdej grupy zwierząt i w każdej powielić podstawowe czynności np. poruszanie się, jedzenie, wydalanie itd.
Ale można wspólne czynności zamknąć w jeden interfejs i później go rozszerzać.
Mamy wtedy jeden interfejs – Animal, w którym są wszystkie czynności, które wykonuje każde zwierzę.
Następnie możemy stworzyć interfejs do każdej grupy zwierząt czyli: ssaki, gady, płazy itd., który będzie rozszerzał Animal.
A jeśli chcemy jeszcze wyróżnić konkretne zwierzęta np. pies, to możemy stworzyć nowy interfejs, który rozszerzy interfejs ssaków.
Ma to sens? Mam nadzieję, że tak.
W programowaniu jest jeden ważny cel – pisać jak najmniej kodu, po co w takim razie powielać wspólne metody? Musimy go wtedy ugenerycznić – czyli zapewnić możłiwość kilkukrotnego używania tego samego kodu w rożnych sytuacjach. 😉
Do sedna!
No to skoro mamy już zobrazowane rozszerzanie interfejsów to czas na stworzenie jakiegoś prostego przykładu.
Stworzymy kilka interfejsów:
- Animal – reprezentujący podstawowe czynności wszystkich zwierząt
- Mammal – reprezentujący podstawowe czynności wszystkich ssaków
- Reptiles – reprezentujący podstawowe czynności wszystkich gadów
- Turtle – reprezentujący podstawowe czynności żółwia
- Dog – reprezentujący podstawowe czynności psa
Na koniec zaimplementujemy dwa ostatnie interfejsy – czyli stworzymy psa i żółwia.
Wszystkie interfejsy będą naprawdę proste, byle zobrazować Ci zastosowanie w kodzie w sposób jak najbardziej prosty.
Animal
Interfejs będzie posiadał metody odpowiedzialne za jedzenie i poruszanie się. Animal jest naszym najbardziej generycznym – czyli podstawowym – interfejs dlatego nie rozszerzanie żadnego z nich.
package pl.maniaq.api; public interface Animal { void move(); void eat(); }
Mammal
Interfejs będzie posiadał metodę odpowiedzialną za karmienie potomstwa mlekiem. Ze względu, że ssaki są zwierzętami to rozszerzają interfejs Animal.
package pl.maniaq.api; public interface Mammal extends Animal { void feedChildWithMilk(); }
Reptiles
Interfejs będzie posiadał metodę odpowiedzialną za składanie jaj. Tak samo jak wcześniej, ze względu, że gady są zwierzętami to rozszerzają interfejs Animal.
package pl.maniaq.api; public interface Reptiles extends Animal { void layEggs(); }
Dog
Interfejs będzie posiadał metodę odpowiedzialną za szczekanie. Skoro pies jest ssakiem to warto rozszerzyć już istniejący interfejs Mammal i nie martwić się już o czynności charakterystyczne dla ssaków. 😉
package pl.maniaq.api; public interface Dog extends Mammal { void bark(); }
Turtle
Interfejs będzie posiadał metodę odpowiedzialną za chowanie się w muszli. 😉 Ze względu, że zółw jest gadem to rozszerza interfejs Reptiles.
package pl.maniaq.api; public interface Turtle extends Reptiles { void hideInShell(); }
Implementacji nadszedł czas!
Skoro mamy już aż 5 interfejsów to czas je zaimplementować – oczywiście można zaimplementować je wszystkie, jednak naszym celem jest zaimplementowanie tych ostatnich czyli Dog oraz Turtle.
DogImpl
Tworzymy klasę DogImpl i implementujemy interfejs Dog:
public class DogImpl implements Dog { public DogImpl() { } }
Następnie jestesmy zmuszeni do implementacji wszystkich metod z… – a no właśnie, ze wszystkich interfejsów, a nie tylko z interfejsu Dog!
Zaimplementujmy je wszystkie – można to zrobić przy użyciu alt + enter mając kursor na linii od deklaracji klasy czyli tej:
public class DogImpl implements Dog {
I wybieramy opcję implement methods.
Następnie wszystkie metody uzupełniamy zwykłymi println, aby wyśwetlić cokolwiek na ekran konsoli
Cała klasa wygląda tak:
package pl.maniaq.impl; import pl.maniaq.api.Dog; public class DogImpl implements Dog { public DogImpl() { } @Override public void bark() { System.out.println("Pies szczeka"); } @Override public void feedChildWithMilk() { System.out.println("Pies karmi potomstwo mlekiem."); } @Override public void move() { System.out.println("Pies się porusza"); } @Override public void eat() { System.out.println("Pies je"); } }
TurtleImpl
Analogicznie zrobimy z klasą TurtleImpl – zaimplementujemy interfejs Turtle.
public class TurtleImpl implements Turtle { public TurtleImpl() { } }
Oraz wszystkie metody z interfejsu:
package pl.maniaq.impl; import pl.maniaq.api.Turtle; public class TurtleImpl implements Turtle { public TurtleImpl() { } @Override public void hideInShell() { System.out.println("Żółw schował się w muszli"); } @Override public void layEggs() { System.out.println("Zółw składa jaja"); } @Override public void move() { System.out.println("Żółw się porusza"); } @Override public void eat() { System.out.println("Zółw je"); } }
Ponownie mamy metody ze wszystkich interfejsów, dzięki czemu nie musimy się martwić, że jakąś ważną funkcję życia osobnika pominiemy. 😉
Wypróbuj to sam!
Następnie można wypróbować stworzyć obiekty naszych klas i wywołać metody w mainie.
package pl.maniaq; import pl.maniaq.api.*; import pl.maniaq.impl.DogImpl; import pl.maniaq.impl.TurtleImpl; public class Main { public static void main(String[] args) { // żółw jest zwierzęciem, więc typ może być Animal Animal turtle = new TurtleImpl(); // zółw jest gadem, więc typ może być Reptiles Reptiles secondTurtle = new TurtleImpl(); Turtle thirthTurtle = new TurtleImpl(); // pies jest ssakiem, więc typ może być Mammal Mammal dog = new DogImpl(); // pies jest zwierzęciem, więc typ może być Animal Animal secondDog = new DogImpl(); Dog thirthDog = new DogImpl(); turtle.eat(); secondTurtle.layEggs(); thirthTurtle.hideInShell(); thirthDog.bark(); dog.feedChildWithMilk(); secondDog.move(); } }
Jak widzisz nasze klasy posiadają wszystkie metody zdefiniowane w odpowiednich interfejsach.
Po uruchomieniu otrzymujemy:
Zółw je Zółw składa jaja Żółw schował się w muszli Pies szczeka Pies karmi potomstwo mlekiem. Pies się porusza
Podsumowanie
Choć sam temat wydaję się prosty, ilość linii kodu mała, a interfejsy wydają się niepotrzebne to moim zdaniem samo zagadnienie jest bardzo ważne. Szczególnie w sytuacjach, gdy aplikacje są bardzo rozbudowane to właśnie wtedy interfejsy pomagają upilnować porządek w kodzie.
Jeśli mielibyśmy sytuację, że trzy klasy posiadają te same metody, tylko inne implementacje – to czy interfejsy się przydają? Oczywiście, że tak – wtedy w jednym pliku mamy wszystkie metody jakie implementują klasy.
Dodatkowo dzięki IDE – np. IntelliJ łatwo możemy podejrzeć w projekcie, jakie klasy implementują dany interfejs. Zaś sam interfejs, bez potrzeby oglądania kodu mówi nam co potrafi dana klasa. Nie musimy się przewijać przez duże ilości linii kodu w klasie – wystarczy nam jeden mały interfejs. 😉
Kod z lekcji możesz znaleźć tutaj.
Aby utrwalić swoją wiedzę z interfejsów i ich rozszerzanie zachęcam Cię do rozwiązania poniższego zadania.
- Stwórz interfejs Pojazd, który będzie posiadał takie metody jak: skręć w lewo, skręć w prawo.
- Następnie utwórz interfejs PojazdLądowy z metodami: jedź oraz hamuj.
- Kolejno stwórz interfejs PojazdWodny z metodami: płyń.
- Następnie stwórz interfejs Samochód z metodami: zaciągnij hamulec ręczny, abs.
- Następnie stwórz interfejs Rower z metodami: zmień przerzutki.
- Utwórz interfejs Statek z metodami: rzuć kotwicę.
- Na sam koniec zaimplementuj interfejsy Samochód, Rower i Statek.
Twoim obowiązkiem jest również odpowiednie rozszerzanie interfejsów. Pamiętaj również o stosowaniu angielskich nazw!
Rozwiązanie zadania możesz znaleźc w tym repozytorium.