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.

  1. Stwórz interfejs Pojazd, który będzie posiadał takie metody jak: skręć w lewo, skręć w prawo.
  2. Następnie utwórz interfejs PojazdLądowy z metodami: jedź oraz hamuj. 
  3. Kolejno stwórz interfejs PojazdWodny z metodami: płyń.
  4. Następnie stwórz interfejs Samochód z metodami: zaciągnij hamulec ręcznyabs.
  5. Następnie stwórz interfejs Rower z metodami: zmień przerzutki.
  6. Utwórz interfejs Statek z metodami: rzuć kotwicę.
  7. Na sam koniec zaimplementuj interfejsy Samochód, Rower 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.

Kamil Klimek

Od 2016 jestem programistą Java. Przez pierwsze 4 lata pracowałem jako Full Stack Java Developer. Później postanowiłem postawić nacisk na Javę, żeby jeszcze lepiej ją poznać.

Subscribe
Powiadom o
guest
0 komentarzy
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x