JavaRush /Blog Java /Random-PL /Interfejsy dla tych, którzy są „bardzo zainteresowani, al...
Lilly
Poziom 25
Москва

Interfejsy dla tych, którzy są „bardzo zainteresowani, ale nic nie rozumieją”

Opublikowano w grupie Random-PL
Cześć wszystkim! Ten post został napisany raczej dla tych, którzy już widzieli, a nawet rozwiązali kilka problemów z interfejsami na poziomie 12 (drugi poziom Java Core), ale wciąż zastanawiają się: „Więc po co są one potrzebne, jeśli nadal musisz zaimplementować w nich metody ?” zadeklarowane w interfejsie??? Czyli nie zmniejszają ilości kodu!!!" . Nie mam wątpliwości, że ten temat zostanie poruszony w kolejnych wykładach i ujawnione zostaną głębsze znaczenia i możliwości wykorzystania interfejsów, ale jeśli czujesz się zupełnie nieswojo, to nie ma za co. Początkowo chciałem napisać komentarz, ale zdałem sobie sprawę, że będzie to bardzo długi komentarz, więc oto jestem. To mój pierwszy artykuł, proszę o rozsądną krytykę. Jeśli gdzieś się mylę, proszę mnie poprawić. Jak więc wiemy, tworząc własną klasę, możemy dziedziczyć tylko z jednej klasy abstrakcyjnej lub nieabstrakcyjnej . Ale jednocześnie w naszej klasie możemy zaimplementować dość dużą liczbę interfejsów . Przy dziedziczeniu trzeba kierować się zdrowym rozsądkiem i mimo to wpisywać na potomków podmioty powiązane (kota od buldożera nie odziedziczymy i odwrotnie). W przypadku interfejsów logika jest nieco inna: ważne są tutaj nie same „byty”, ale ich „umiejętności” lub to, co można z nimi zrobić . Na przykład zarówno kot, jak i spychacz mogą poruszać się w przestrzeni. Oznacza to, że w zasadzie mogą implementować interfejs CanMove lub Moveable. Albo człowiek i kot. Oboje potrafią pić, ale robią to na różne sposoby: osoba pije herbatę lub kawę z filiżanki, a kot napija się wody lub mleka z miski. Ale generalnie oboje piją, więc każdy z nich może stworzyć własną implementację interfejsu CanDrink. Jakie ma to dla nas korzyści? Wyobraź sobie, że tworzysz grę. Załóżmy, że masz lokalizację: rzekę z brzegami po obu stronach, a następnie las i góry. Na brzegu odpoczywają różne rodzaje żywych stworzeń. Nagle zbliża się powódź. Każdy, kto potrafi latać, odlatuje. Ci, którzy nie potrafią latać, ale potrafią biegać, uciekajcie. Ktoś umie pływać, więc w zasadzie nie przejmuje się twoją powodzią (no cóż, albo potrafi dopłynąć do brzegu), chociaż niektórzy z nich mogą najpierw spróbować uciec (jeśli wiedzą jak). Reszta, choć smutna, umrze. Spróbujmy to wdrożyć. (Nie bój się dużej ilości kodu na raz =) Większość to komentarze) Jakich klas możemy potrzebować? Zacznijmy od abstrakcyjnej klasy różnych postaci lub jednostek (przepraszam, nie jestem mocny w terminologii związanej z grami, popraw mnie, jeśli się mylę). Niech to będzie klasa Unit . Zakładamy, że absolutnie wszystkie jednostki mogą poruszać się w przestrzeni i wydawać dźwięki. Klasa abstrakcyjna Jednostka :
// Абстрактный класс для всех юнитов
public static abstract class Unit {
    // Базовый метод движения.
    // По умолчанию (если не переопределено) будет у всех наследников.
    public void move (int x, int y) {
        System.out.println("Я ( " + getClassName() + " ) просто брожу по полю на " +
                           x + " метров вправо и " + y + " метров вперед");
    }

    // Абстрактный метод, который ДОЛЖЕН ПЕРЕОПРЕДЕЛИТЬ у себя
    // КАЖДЫЙ КЛАСС, унаследованный от Unit.
    public abstract void makeSound();

    // Вспомогательный метод получения имени класса
    // без всей лишней информации.
    public String getClassName() {
        return this.getClass().getSimpleName();
    }
}
Jakich interfejsów („umiejętności”) potrzebują nasze jednostki? Potrafią biegać ( CanRun ), pływać ( CanSwim ) i latać ( CanFly ). Niektórzy ludzie mogą mieć kilka umiejętności na raz, podczas gdy niektórzy nieszczęśliwi mogą nie mieć żadnej.
// Интерфейсы. КАЖДЫЙ КЛАСС, "наследующий" Jakой-то интерфейс,
// ДОЛЖЕН РЕАЛИЗОВАТЬ его у себя.
interface CanRun {
    void run(String action);
}
interface CanSwim {
    void swim();
}
interface CanFly {
    void fly();
}
Następnie tworzymy klasy, które dziedziczą z abstrakcyjnej klasy Unit. Ścieżką będzie klasa Human :
// Человек НАСЛЕДУЕТ абстрактный класс Unit,
// а также РЕАЛИЗУЕТ интерфейсы CanRun, CanSwim
public static class Human extends Unit implements CanRun, CanSwim {
    // Переопределяем метод public void makeSound()
    // родительского абстрактного класса Unit
    @Override
    public void makeSound() {
        System.out.print("Йу-хуу! ");
    }

    // РЕАЛИЗУЕМ метод public void run(String action) интерфейса CanRun
    @Override
    public void run(String action) {
        System.out.println("Я ( " + this.getClassName() + " ) " + action +
                           " отсюда на двух ногах ");
    }

    // РЕАЛИЗУЕМ метод public void swim() интерфейса CanSwim
    @Override
    public void swim() {
        System.out.println("Я ( " + this.getClassName() + " ) " +
                           "с двумя руками и двумя ногами " +
                           "хорошо плаваю");
    }
}
Klasa ptaka . Tak, rozumiem, że nie ma prostych ptaków, ale dla uproszczenia niech to będzie tylko ptak, nie traktujmy tego abstrakcyjnie:
// Птица НАСЛЕДУЕТ абстрактный класс Unit,
// и РЕАЛИЗУЕТ интерфейс CanFly
public static class Bird extends Unit implements CanFly {
    // Переопределяем абстрактный метод public void makeSound()
    // родительского абстрактного класса Unit
    @Override
    public void makeSound() {
        System.out.print("Курлык! ");
    }

    // РЕАЛИЗУЕМ метод public void fly() интерфейса CanFly
    @Override
    public void fly() {
        System.out.println("Я ( " + this.getClassName() + " ) улетел отсюда");
    }
}
Stwórzmy teraz kolejną klasę abstrakcyjną Animals , która będzie odziedziczona z Unit :
// Абстрактный класс Животных, НАСЛЕДУЕТ абстрактный класс Unit
public static abstract class Animal extends Unit {
    // тут могут быть Jakие-то данные и/Lub методы
}
I już jego spadkobiercy Baranek ( Owca ):
// Баран НАСЛЕДУЕТ класс Animal,
// и РЕАЛИЗУЕТ интерфейс CanRun
public static class Sheep extends Animal implements CanRun {
    // Переопределяем абстрактный метод public void makeSound()
    // родительского абстрактного класса Unit
    @Override
    public void makeSound() {
        System.out.print("Беееее! ");
    }

    // РЕАЛИЗУЕМ метод public void run(String action) интерфейса CanRun
    @Override
    public void run(String action) {
        System.out.println("Я ( "+ this.getClassName() + " ) " + action +
                           " отсюда на четырёх копытах");
    }
}
i Lenistwo ( Lenistwo ):
// Ленивец НАСЛЕДУЕТ класс Animal
// и внутри себя ПЕРЕОПРЕДЕЛЯЕТ метод
// void move(int x, int y) абстрактного класса Unit
public static class Sloth extends Animal {
    // Переопределяем абстрактный метод public void makeSound()
    // родительского абстрактного класса Unit
    @Override
    public void makeSound() {
        System.out.print("Зевает... ");
    }

	// Переопределяем метод public void move(int x, int y)
    // родительского абстрактного класса Unit
    @Override
    public void move(int x, int y) {
        System.out.println("Я ( "+ getClassName() + " ) очень медленный, поэтому " +
                           "переместился на " + (int)(x/12) + " вправо " +
                           "и на " + (int)(y/12) + " вперед");
    }
}
Metoda move(int x, int y) abstrakcyjnej klasy Unit nie jest metodą abstrakcyjną , więc nie musimy jej zastępować, ale w przypadku Sloth nieco spowolniliśmy działanie. Przejdźmy teraz do działania.
public static void main(String[] args) {
    // создаём список юнитов
    // и добавляем туда представителей разных классов
    List<Unit> units = new ArrayList<unit>();
    units.add(new Human());
    units.add(new Bird());
    units.add(new Sheep());
    units.add(new Sloth());

    // проходим в цикле по всем юнитам и вызываем метод move(int x, int y).
    // У всех, кроме ленивца, будет вызван метод базового класса.
    // У Ленивца будет вызван его переопределенный метод
    for (Unit unit : units) {
		// в самом начале ничего не происходит, юниты просто двигаются.
        unit.move((int)(Math.random()*50), (int)(Math.random()*50));
    }

    System.out.println("\n...Наводнение приближается....");
    int distanceOfFlood = (int)(Math.random()*1000); // Число от 0 до 1000
    System.out.println("...Наводнение на " + distanceOfFlood + " от берега...\n");

    // проходим в цикле по всем юнитам
    for (Unit unit : units) {
        unit.makeSound(); // у каждого юнита свой, переопределенный, звук
        // смотрим, кто что "умеет":
		// если юнит умеет летать
        if(unit instanceof CanFly)
            ((CanFly) unit).fly(); // проблем нет, улетает
        // если юнит не умеет летать, но умеет бегать
        else if(unit instanceof CanRun) {
			// смотрим на Jakое расстояние разлилась вода
            if(distanceOfFlood < 400) {
				// если меньше 400 (условно метров)
                ((CanRun) unit).run("убежал"); // юнит убежал
            }
            else {
				// если больше 400, юнит безуспешно пытается убежать
                ((CanRun) unit).run("пытался убежать");
				// если юнит может не только бегать, но и плавать
                if (unit instanceof CanSwim) {
                    ((CanSwim) unit).swim(); // плывёт
                }
				// иначе умирает (он хотя бы пытался)
                else unitIsDead(unit);
            }
        }
		// если юнит не летает, не бегает, но может плавать
        else if (unit instanceof CanSwim)
            ((CanSwim) unit).swim(); // плывёт
        else
			// если юнит не умеет совсем ничего - грустненько :(
            unitIsDead(unit); // умирает

        System.out.println();
    }
}

public static void unitIsDead(Unit unit) {
    System.out.println("Извините, я ( " + unit.getClassName() + " ) умер");
}
Literały numeryczne 12, 50, 400 i 1000 zostały pominięte; inne można określić, ale mam nadzieję, że logika jest jasna. Zatem, jak widzimy, mając abstrakcyjną klasę nadrzędną z metodami ogólnymi, nie musimy zastanawiać się, jaka to konkretna klasa jest ta czy inna jednostka, ale po prostu wywołujemy te metody ( makeSound() i move ( ) ) . Po pierwszym przejściu pętli i wywołaniu metody move() na wszystkich jednostkach na ekranie wyświetli się następujący komunikat: Interfejsy dla tych, którzy są „bardzo zainteresowani, ale nic nie rozumieją” - 1 Oczywiście wszystkie obiekty oprócz leniwca wyświetlają standardowy komunikat metody move() abstrakcyjnego rodzica klasa Unit , a dla leniwca metoda move() została na nowo zdefiniowana . Jednak klasa abstrakcyjna nie pomoże nam dowiedzieć się, co konkretna jednostka „może”. Tutaj w grę wchodzą interfejsy . Korzystając z instancjiof dowiadujemy się, czy ta jednostka może wykonywać określone akcje ( czy obsługuje potrzebny nam interfejs ), a jeśli tak, korzystamy ze znanego nam już rzutowania typów, np. za pomocą jednostki ((CanFly)). fly() rzutujemy nasz obiekt typu Unit na typ interfejsu CanFly i wywołujemy jego metodę fly() . I nie ma znaczenia, jaką klasę ma nasz obiekt, ważne jest tylko to, że w swoim „życiorysie” wskazał umiejętność latania. Mówimy mu: "Wiesz jak, więc lataj! Nie obchodzi nas, jak to robisz . " Oznacza to dla nas, jako programistów, że klasy implementujące interfejs CanFly mogą w dowolnym momencie zmienić implementację metody fly() i w dowolny sposób mogą pojawić się nowe klasy ją implementujące lub odwrotnie, programiści mogą usuń niektóre stare klasy. Ale dopóki ta metoda spełnia swoje określone funkcje, a obiekt leci, nie obchodzi nas to. Nasz kod współpracujący z obiektami implementującymi ten interfejs pozostanie taki sam, nie będziemy musieli niczego zmieniać. Po drugim cyklu, gdy wszyscy będą próbowali uciec, w zależności od skali powodzi na ekranie zobaczymy albo albo. Interfejsy dla tych, którzy są „bardzo zainteresowani, ale nic nie rozumieją” - 2 Bez Interfejsy dla tych, którzy są „bardzo zainteresowani, ale nic nie rozumieją” - 3 interfejsu musielibyśmy każdy obiekt sprawdzać pod kątem zgodności z określoną klasą (a mielibyśmy aby wszystko sprawdzić) i pamiętaj o umiejętnościach każdej konkretnej klasy. Oznacza to, że jest teraz przed nami baranek i wydaje się, że wie, jak biegać. A co jeśli masz kilkadziesiąt lub setki różnych typów (klas) takich postaci? A co jeśli to nie Ty je napisałeś, ale inny programista i nie masz pojęcia kto co potrafi? Byłoby dużo trudniej... I mały dodatek po publikacji: W prawdziwym życiu nie jesteś jedyną osobą pracującą nad projektem. Powiedzmy, że zajmujesz się tylko logiką. Wszystkie obiekty, z którymi wchodzisz w interakcję, są pisane przez innych programistów. Możesz nawet nie znać wszystkich klas, z którymi współpracuje Twój kod. Jedyne, czego od nich oczekujesz, to to, że zrobią to, czego od nich oczekujesz. Jednak wszyscy mogą to zrobić na zupełnie inne sposoby. Załóżmy jednak, że w swoim kodzie tworzysz metodę, która działa tylko z obiektami klas obsługujących określony interfejs
void doSomething(CanFly f)
oznacza to, że interfejs ustawia się za pomocą parametru metody. Nie konkretna klasa, ale interfejs. A wszyscy inni programiści, wywołując tę ​​twoją metodę void doSomething(CanFly), muszą przekazać jako argumenty albo jawny obiekt klasy implementującej CanFly, albo jakiś obiekt jakiejś klasy, który można na niego rzucić:
Object obj = new Bird();
doSomething(obj); // ошибка компиляции Object cannot be converted to CanFly
doSomething((CanFly) obj); // нет ошибки, потому что obj у нас класса Bird и реализует CanFly

Bird b = new Bird();
doSomething(b); // нет ошибки

Human h = new Human() ;
doSomething(h); // ошибка компиляции
doSomething((CanFly) h); // ошибка времени выполнения ClassCastException
W ten sposób interfejsy mogą być przydatne. A to nie wszystkie ich możliwości i sposoby zastosowania. Prawdopodobnie dowiemy się więcej w dalszej części kursu =) Dziękujemy za przeczytanie do końca =)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION