JavaRush /Java-Blog /Random-DE /Schnittstellen für diejenigen, die „sehr interessiert sin...
Lilly
Level 25
Москва

Schnittstellen für diejenigen, die „sehr interessiert sind, aber nichts verstehen“

Veröffentlicht in der Gruppe Random-DE
Hallo zusammen! Dieser Beitrag wurde eher für diejenigen geschrieben, die bereits mehrere Probleme mit Schnittstellen auf Level 12 (2. Level von Java Core) gesehen und sogar gelöst haben, sich aber immer noch fragen: „Warum werden sie also benötigt, wenn man in ihnen noch Methoden implementieren muss? “ ?“ in der Schnittstelle deklariert??? Das heißt, sie reduzieren nicht die Codemenge!!!“ . Ich habe keinen Zweifel daran, dass dieses Thema in den folgenden Vorlesungen behandelt wird und tiefere Bedeutungen und Möglichkeiten der Verwendung von Schnittstellen aufgezeigt werden, aber wenn Sie sich völlig unwohl fühlen, dann sind Sie herzlich willkommen. Ursprünglich wollte ich einen Kommentar schreiben, aber mir wurde klar, dass es ein sehr langer Kommentar werden würde, also sind wir hier. Dies ist mein erster Artikel, ich freue mich über berechtigte Kritik. Sollte ich irgendwo falsch liegen, könnt ihr mich gerne korrigieren. Wie wir also wissen, können wir beim Erstellen unserer eigenen Klasse nur von einer abstrakten oder nicht abstrakten Klasse erben . Aber gleichzeitig können wir in unserer Klasse eine ziemlich große Anzahl von Schnittstellen implementieren . Beim Erben müssen Sie sich vom gesunden Menschenverstand leiten lassen und dennoch verwandte Entitäten in Ihre Nachkommen eintragen (wir werden nicht eine Katze von einem Bulldozer erben und umgekehrt). Bei Schnittstellen ist die Logik etwas anders: Hier kommt es nicht auf die „Entitäten“ selbst an, sondern auf ihre „Fähigkeiten“ oder was man mit ihnen machen kann . Beispielsweise können sich sowohl eine Katze als auch ein Bulldozer im Weltraum bewegen. Das heißt, sie können grundsätzlich die CanMove- oder Moveable-Schnittstelle implementieren. Oder ein Mann und eine Katze. Beide können trinken, aber sie tun es auf unterschiedliche Weise: Eine Person trinkt Tee oder Kaffee aus einer Tasse und eine Katze schlürft etwas Wasser oder Milch aus einer Schüssel. Aber im Allgemeinen trinken beide, sodass jeder seine eigene Implementierung der CanDrink-Schnittstelle erstellen kann. Welchen Nutzen hat das für uns? Stellen Sie sich vor, Sie machen ein Spiel. Nehmen wir an, Sie haben einen Ort: einen Fluss mit Ufern auf beiden Seiten, und dann einen Wald und Berge. Am Ufer ruhen verschiedene Arten von Lebewesen. Plötzlich naht eine Flut. Jeder, der fliegen kann, fliegt weg. Wer nicht fliegen kann, aber laufen kann, der rennt. Jemand weiß, wie man schwimmt, also kümmert er sich im Prinzip nicht um Ihre Flut (naja, oder er kann an Land schwimmen), obwohl einige von ihnen vielleicht zuerst versuchen zu fliehen (wenn sie es können). Der Rest wird sterben, so traurig es auch sein mag. Versuchen wir, dies umzusetzen. (Haben Sie keine Angst vor der großen Menge an Code auf einmal =) Das meiste davon sind Kommentare.) Welche Klassen könnten wir brauchen? Beginnen wir mit einer abstrakten Klasse verschiedener Charaktere oder Einheiten (leider bin ich nicht so begabt in der Gaming-Terminologie, korrigieren Sie mich, wenn ich falsch liege). Dies sei die Unit- Klasse . Wir gehen davon aus, dass absolut alle Einheiten sich im Raum bewegen und Geräusche machen können. Abstrakte Klasseneinheit :

// Абстрактный класс для всех юнитов
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();
    }
}
Welche Schnittstellen („Skills“) benötigen unsere Einheiten? Sie können laufen ( CanRun ), schwimmen ( CanSwim ) oder fliegen ( CanFly ). Manche Menschen verfügen möglicherweise über mehrere Fähigkeiten gleichzeitig, während einige unglückliche Menschen möglicherweise keine haben.

// Интерфейсы. КАЖДЫЙ КЛАСС, "наследующий" Wieой-то интерфейс,
// ДОЛЖЕН РЕАЛИЗОВАТЬ его у себя.
interface CanRun {
    void run(String action);
}
interface CanSwim {
    void swim();
}
interface CanFly {
    void fly();
}
Als nächstes erstellen wir Klassen, die von der abstrakten Unit-Klasse erben . Der Pfad wird die Klasse Mensch sein :

// Человек НАСЛЕДУЕТ абстрактный класс 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() + " ) " +
                           "с двумя руками и двумя ногами " +
                           "хорошо плаваю");
    }
}
Vogelklasse . _ Ja, ich verstehe, dass es keine einfachen Vögel gibt, aber der Einfachheit halber soll es nur ein Vogel sein und es nicht abstrakt machen:

// Птица НАСЛЕДУЕТ абстрактный класс 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() + " ) улетел отсюда");
    }
}
Jetzt erstellen wir eine weitere abstrakte Klasse Animals , die von Unit geerbt wird :

// Абстрактный класс Животных, НАСЛЕДУЕТ абстрактный класс Unit
public static abstract class Animal extends Unit {
    // тут могут быть Wieие-то данные и/oder методы
}
Und schon sind seine Erben Lamm ( Schaf ):

// Баран НАСЛЕДУЕТ класс 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 +
                           " отсюда на четырёх копытах");
    }
}
und Faultier ( Faultier ):

// Ленивец НАСЛЕДУЕТ класс 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) + " вперед");
    }
}
Die Methode move(int x, int y) der abstrakten Unit-Klasse ist nicht abstract , daher müssen wir sie nicht überschreiben, aber wir haben die Dinge für Sloth etwas verlangsamt. Kommen wir nun zum Handeln.

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) {
			// смотрим на Wieое расстояние разлилась вода
            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() + " ) умер");
}
Die numerischen Literale 12, 50, 400 und 1000 werden spontan übernommen; andere können angegeben werden, aber ich hoffe, dass die Logik klar ist. Wie wir also sehen können, müssen wir bei einer abstrakten übergeordneten Klasse mit allgemeinen Methoden nicht darüber nachdenken, um welche bestimmte Klasse diese oder jene Einheit handelt, sondern rufen einfach diese Methoden auf ( makeSound() und move ( ) ) . Nach dem ersten Durchlauf in der Schleife, wenn die Methode move() für alle Einheiten aufgerufen wird , wird Folgendes auf dem Bildschirm angezeigt: Schnittstellen für diejenigen, die „sehr interessiert sind, aber nichts verstehen“ – 1 Offensichtlich zeigen alle Objekte außer dem Faultier eine Standardmeldung der Methode move() des abstrakten übergeordneten Elements an Die Klasse Unit und das Faultier haben eine Move (-Methode) neu definiert . Eine abstrakte Klasse hilft uns jedoch nicht herauszufinden, was eine bestimmte Einheit „tun kann“. Hier kommen Schnittstellen ins Spiel . Mit der Instanz von finden wir heraus, ob diese Einheit bestimmte Aktionen ausführen kann ( ob sie die von uns benötigte Schnittstelle unterstützt ), und wenn ja, verwenden wir die uns bereits bekannte Typumwandlung, zum Beispiel mit ((CanFly) Einheit). Mit der Funktion „fly()“ wandeln wir unser Objekt vom Typ „ Unit“ in den Schnittstellentyp „CanFly“ um und rufen dessen Methode „fly()“ auf . Dabei spielt es keine Rolle, welche Klasse unser Objekt hat, wichtig ist nur, dass er in seinem „Lebenslauf“ die Fähigkeit zum Fliegen angegeben hat. Wir sagen ihm: „Du weißt wie, also flieg! Es ist uns egal, wie du es machst . “ Das heißt für uns als Entwickler bedeutet dies, dass Klassen, die die CanFly- Schnittstelle implementieren, die Implementierung der fly()- Methode jederzeit und auf jede Art und Weise ändern können . Es können neue Klassen erscheinen, die sie implementieren, oder umgekehrt, Entwickler können dies tun Entfernen Sie einige der alten Klassen. Aber solange diese Methode ihre angegebenen Funktionen ausführt und das Objekt fliegt, ist uns das egal. Unser Code, der mit Objekten funktioniert, die diese Schnittstelle implementieren, bleibt derselbe; wir müssen nichts ändern. Nach dem zweiten Zyklus, wenn alle versuchen zu fliehen, sehen wir je nach Ausmaß der Flut entweder Schnittstellen für diejenigen, die „sehr interessiert sind, aber nichts verstehen“ – 2 oder auf dem Bildschirm Schnittstellen für diejenigen, die „sehr interessiert sind, aber nichts verstehen“ – 3 . Ohne Schnittstelle müssten wir jedes Objekt auf Übereinstimmung mit einer bestimmten Klasse überprüfen (und das hätten wir auch getan). um alles zu überprüfen) und berücksichtigen Sie die Fähigkeiten jeder einzelnen Klasse. Das heißt, vor uns steht jetzt ein Lamm, und es scheint zu wissen, wie man rennt. Was ist, wenn Sie mehrere Dutzend oder Hunderte verschiedener Typen (Klassen) solcher Zeichen haben? Und was wäre, wenn nicht Sie, sondern ein anderer Programmierer sie geschrieben hätten und Sie keine Ahnung hätten, wer was kann? Es wäre viel schwieriger... Und eine kleine Ergänzung nach der Veröffentlichung: Im wirklichen Leben sind Sie nicht der Einzige, der an einem Projekt arbeitet. Nehmen wir an, Sie beschäftigen sich nur mit Logik. Alle Objekte, mit denen Sie interagieren, wurden von anderen Programmierern geschrieben. Möglicherweise kennen Sie nicht einmal alle Klassen, mit denen Ihr Code funktioniert. Alles, was Sie von ihnen erwarten, ist, dass sie das tun, was Sie von ihnen erwarten. Allerdings können sie dies alle auf völlig unterschiedliche Weise tun. Nehmen wir jedoch an, Sie erstellen in Ihrem Code eine Methode, die nur mit Objekten von Klassen funktioniert, die eine bestimmte Schnittstelle unterstützen

void doSomething(CanFly f)
Das heißt, Sie legen die Schnittstelle mithilfe des Methodenparameters fest. Keine konkrete Klasse, sondern eine Schnittstelle. Und alle anderen Programmierer müssen beim Aufrufen Ihrer void doSomething(CanFly)-Methode als Argumente entweder ein explizites Objekt einer Klasse übergeben, die CanFly implementiert, oder ein Objekt einer Klasse, das in sie umgewandelt werden kann:

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
Hier können Schnittstellen nützlich sein. Und das sind nicht alle ihre Fähigkeiten und Anwendungsmethoden. Wir werden wahrscheinlich später im Kurs mehr erfahren =) Vielen Dank, dass Sie bis zum Ende gelesen haben =)
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION