JavaRush /Blog Java /Random-FR /Des interfaces pour ceux qui sont « très intéressés, mais...
Lilly
Niveau 25
Москва

Des interfaces pour ceux qui sont « très intéressés, mais ne comprennent rien »

Publié dans le groupe Random-FR
Salut tout le monde! Cet article a plutôt été écrit pour ceux qui ont déjà vu et même résolu plusieurs problèmes sur les interfaces au niveau 12 (2e niveau de Java Core), mais se demandent toujours : « Alors pourquoi sont-ils nécessaires si vous devez encore y implémenter des méthodes ?" déclaré dans l'interface ??? Autrement dit, ils ne réduisent pas la quantité de code !!!" . Je n'ai aucun doute que ce sujet sera abordé dans les conférences ultérieures et que des significations et des options plus profondes pour l'utilisation des interfaces seront révélées, mais si vous êtes complètement inquiet, alors vous êtes les bienvenus. Au départ, je voulais écrire un commentaire, mais j'ai réalisé que ce serait un commentaire très long, alors nous y sommes. Ceci est mon premier article, j’accepte les critiques raisonnables. Si je me trompe quelque part, n'hésitez pas à me corriger. Ainsi, comme nous le savons, lors de la création de notre propre classe, nous ne pouvons hériter que d'une seule classe abstraite ou non abstraite . Mais en même temps, dans notre classe, nous pouvons implémenter un assez grand nombre d'interfaces . Lors de l'héritage, vous devez être guidé par le bon sens tout en notant les entités liées dans vos descendants (nous n'hériterons pas d'un chat d'un bulldozer et vice versa). Avec les interfaces , la logique est un peu différente : ce qui est important ici , ce ne sont pas les « entités » elles-mêmes, mais leurs « compétences » ou ce qu'on peut en faire . Par exemple, un chat et un bulldozer peuvent se déplacer dans l’espace. Autrement dit, en principe, ils peuvent implémenter l'interface CanMove ou Moveable. Ou un homme et un chat. Ils peuvent tous les deux boire, mais ils le font de différentes manières : une personne boit du thé ou du café dans une tasse, et un chat boit de l'eau ou du lait dans un bol. Mais en général, ils boivent tous les deux, chacun d’eux peut donc réaliser sa propre implémentation de l’interface CanDrink. Quel avantage cela nous apporte-t-il ? Imaginez que vous créez un jeu. Disons que vous avez un emplacement : une rivière, avec des berges des deux côtés, puis une forêt et des montagnes. Divers types de créatures vivantes se reposent sur le rivage. Soudain, une inondation approche. Tous ceux qui peuvent voler s'envolent. Ceux qui ne peuvent pas voler mais peuvent courir, courent. Quelqu'un sait nager, donc en principe, il ne se soucie pas de votre inondation (enfin, ou il peut nager jusqu'au rivage), même si certains d'entre eux peuvent d'abord essayer de s'échapper (s'ils savent comment). Les autres, aussi triste que cela puisse être, mourront. Essayons de mettre en œuvre cela. (Ne soyez pas effrayé par la grande quantité de code à la fois =) La plupart sont des commentaires) De quelles classes pourrions-nous avoir besoin ? Commençons par une classe abstraite de différents personnages ou unités (désolé, je ne suis pas fort en terminologie de jeu, corrigez-moi si je me trompe). Que ce soit la classe Unit . Nous supposerons qu'absolument toutes les unités peuvent se déplacer dans l'espace et émettre des sons. Unité de classe abstraite :
// Абстрактный класс для всех юнитов
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();
    }
}
De quelles interfaces (« compétences ») nos unités ont-elles besoin ? Ils peuvent courir ( CanRun ), nager ( CanSwim ) ou voler ( CanFly ). Certaines personnes peuvent avoir plusieurs compétences à la fois, tandis que certains malheureux n’en ont aucune.
// Интерфейсы. КАЖДЫЙ КЛАСС, "наследующий" Howой-то интерфейс,
// ДОЛЖЕН РЕАЛИЗОВАТЬ его у себя.
interface CanRun {
    void run(String action);
}
interface CanSwim {
    void swim();
}
interface CanFly {
    void fly();
}
Ensuite, nous créons des classes qui héritent de la classe abstraite Unit. Le chemin sera la classe 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() + " ) " +
                           "с двумя руками и двумя ногами " +
                           "хорошо плаваю");
    }
}
Classe d'oiseaux . Oui, je comprends qu'il n'y a pas d'oiseaux simples, mais par souci de simplicité, que ce soit juste un oiseau, ne le rendons pas abstrait :
// Птица НАСЛЕДУЕТ абстрактный класс 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() + " ) улетел отсюда");
    }
}
Créons maintenant une autre classe abstraite Animals , qui sera héritée de Unit :
// Абстрактный класс Животных, НАСЛЕДУЕТ абстрактный класс Unit
public static abstract class Animal extends Unit {
    // тут могут быть Howие-то данные и/or методы
}
Et déjà ses héritiers Agneau ( Mouton ) :
// Баран НАСЛЕДУЕТ класс 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 +
                           " отсюда на четырёх копытах");
    }
}
et Paresseux ( Paresseux ):
// Ленивец НАСЛЕДУЕТ класс 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) + " вперед");
    }
}
La méthode move(int x, int y) de la classe abstract Unit n'est pas abstract , nous n'avons donc pas besoin de la remplacer, mais nous avons un peu ralenti les choses pour Sloth. Passons maintenant à l'action.
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) {
			// смотрим на Howое расстояние разлилась вода
            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() + " ) умер");
}
Les littéraux numériques 12, 50, 400 et 1000 sont pris à la légère ; d'autres peuvent être spécifiés, mais j'espère que la logique est claire. Ainsi, comme nous pouvons le voir, ayant une classe parent abstraite avec des méthodes générales, nous n'avons pas besoin de penser à quelle classe spécifique est telle ou telle unité, mais appelons simplement ces méthodes ( makeSound() et move ( ) ) . Après le premier passage dans la boucle, lorsque la méthode move() est appelée sur toutes les unités , ce qui suit sera affiché à l'écran : Des interfaces pour ceux qui sont « très intéressés, mais ne comprennent rien » - 1 Evidemment, tous les objets sauf le paresseux affichent un message standard de la méthode move() du parent abstrait classe Unit , et le paresseux a une méthode move() qui a été redéfinie . Cependant, une classe abstraite ne nous aidera pas à découvrir ce qu’une unité particulière « peut faire ». C'est là que les interfaces entrent en jeu . En utilisant instanceof , nous découvrons si cette unité peut effectuer certaines actions ( si elle prend en charge l'interface dont nous avons besoin ), et si tel est le cas, nous utilisons le transtypage qui nous est déjà familier, par exemple en utilisant ((CanFly) unit). fly() nous convertissons notre objet de type Unit en type d'interface CanFly et appelons sa méthode fly() . Et peu importe la classe de notre objet, la seule chose importante est qu'il ait indiqué la capacité de voler dans son « CV ». On lui dit : "Tu sais comment, alors vole ! On s'en fout de la façon dont tu fais . " Autrement dit, pour nous, en tant que développeurs, cela signifie que les classes qui implémentent l' interface CanFly peuvent modifier l'implémentation de la méthode fly() à tout moment et de n'importe quelle manière , de nouvelles classes qui l'implémentent peuvent apparaître, ou, à l'inverse, les développeurs peuvent supprimez certaines des anciennes classes. Mais tant que cette méthode remplit ses fonctions déclarées et que l'objet vole, cela ne nous importe pas. Notre code qui fonctionne avec les objets qui implémentent cette interface restera le même ; nous n’aurons rien à changer. Après le deuxième cycle, lorsque tout le monde tentera de s'échapper, selon l'ampleur de l'inondation, nous verrons sur l'écran soit Des interfaces pour ceux qui sont « très intéressés, mais ne comprennent rien » - 2 ou Des interfaces pour ceux qui sont « très intéressés, mais ne comprennent rien » - 3 Sans interface, il faudrait vérifier la conformité de chaque objet à une certaine classe (et il nous faudrait pour tout vérifier) ​​et garder à l'esprit les compétences de chaque classe spécifique. Autrement dit, il y a maintenant un agneau devant nous et il semble savoir courir. Que se passe-t-il si vous disposez de plusieurs dizaines ou centaines de types (classes) différents de ces personnages ? Et si ce n’était pas vous qui les aviez écrits, mais un autre programmeur, et que vous n’avez donc aucune idée de qui peut faire quoi ? Ce serait bien plus difficile... Et un petit ajout après publication : Dans la vraie vie, vous n’êtes pas le seul à travailler sur un projet. Disons que vous ne faites que de la logique. Tous les objets avec lesquels vous interagissez sont écrits par d'autres programmeurs. Vous ne connaissez peut-être même pas toutes les classes avec lesquelles votre code fonctionne. Tout ce dont vous avez besoin, c’est qu’ils fassent ce que vous attendez d’eux. Cependant, ils peuvent tous le faire de manières complètement différentes. Mais disons que dans votre code vous créez une méthode qui fonctionne uniquement avec les objets des classes prenant en charge une certaine interface.
void doSomething(CanFly f)
c'est-à-dire que vous définissez l'interface à l'aide du paramètre method. Pas une classe concrète, mais une interface. Et tous les autres programmeurs, lorsqu'ils appellent votre méthode void doSomething(CanFly), doivent passer comme arguments soit un objet explicite d'une classe qui implémente CanFly, soit un objet d'une classe qui peut y être converti :
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
C’est ainsi que les interfaces peuvent être utiles. Et ce ne sont pas toutes leurs capacités et méthodes d'application. Nous en apprendrons probablement plus plus tard dans le cours =) Merci d'avoir lu jusqu'au bout =)
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION