Archives Wild Code School
Java

Java 08 : Interface

Quête 14 sur 43

Tu as vu précédemment qu'une classe ne peut hériter que d'une seule autre classe.

Imagine que tu aies les classes Cat et Dog, qui héritent d'Animal. Ces deux animaux peuvent être domestiqués, mais ça ne sera pas le cas de tous les animaux héritant de la classe Animal.

Tu peux donc créer la classe Pet, qui aura des comportements spécifiques à un animal domestique (caresser, jouer, nourrir). Mais tu es face à un problème : une classe ne peut hériter que d'une seule classe, et Cat et Dog héritent déjà d'Animal : ils ne peuvent pas hériter de Pet ! En plus, tu n'aurais pas envie d'ajouter des comportements dans Animal : tous les animaux ne sont pas domestiques.

Tu vas donc découvrir comme résoudre ce problème avec les interfaces !

Objectifs

  • Comprendre dans quel cas utiliser une interface

  • Savoir créer et implémenter plusieurs interfaces

Contrat

Lorsque tu trouveras de la documentation sur les interfaces en Java, tu liras souvent : une interface est un contrat.

En effet, une interface définit un ensemble de comportements, sans pour autant les décrire. Un animal de compagnie, tu pourras jouer avec, le nourrir, le caresser. Mais selon que c'est un chat ou un chien, tu ne procéderas pas de la même façon.

Le rôle de l'interface est donc de définir des comportements, sans pour autant les décrire : ce sont les classes qui implémentent les interfaces qui définissent le corps des méthodes.

Par exemple, nous avons l'interface Pet qui va définir quelques comportements d'un animal de compagnie :

java
public interface Pet {

    /**
     * feed the animal
     */
    void feed();

    /**
     * play with the animal
     */
    void play();

    /**
     * pet the animal
     * @return is animal happy
     */
    boolean pet();
}

Tu remarques l'utilisation du mot-clé interface au lieu de là où tu pouvais trouver class avant, pour une raison simple : une interface n'est pas une classe !

Implémentation

Une interface n'est pas une classe : quand on souhaite utiliser une interface, on parle donc d'implémentation et non d'héritage.

Si tu souhaites implémenter les comportements de l'interface Pet dans la classe Cat, va utiliser le mot-clé implements :

java
public class Cat extends Animal implements Pet {

    public Cat(String name, int age) {
        super(name, age);
    }
}

Tu peux remarquer dans cet exemple qu'il est possible de cumuler un héritage avec une implémentation d'interface.

Et s'il n'est possible d'hériter que d'une classe, il est possible d'implémenter plusieurs interfaces.

Il suffit de séparer les interfaces par des virgules. Par exemple, voici la classe Car qui implémente les interfaces Motor et FourWheels :

java
public class Car extends Vehicle implements Motor, FourWheels {
    // ...
}

Si tu reprends l'exemple de Cat et de Pet, l'implémentation seule ne fonctionnera pas. En essayant de compiler, tu auras l'erreur suivante :

Cat is not abstract and does not override abstract method pet() in fr.wildcodeschool.Pet

En effet, comme vu dans la première partie de la quête, une interface est un contrat qui oblige la classe qui l'implémente à décrire ses comportements.

Il est donc obligatoire de définir le corps des méthodes implémentées de Pet dans Cat :

java
public class Cat extends Animal implements Pet {

    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void feed() {
        System.out.println("Chomp chomp");
    }

    @Override
    public void play() {
        System.out.println("Meow!");
    }

    @Override
    public boolean pet() {
        Random random = new Random();
        boolean agrees = random.nextBoolean();
        if (agrees) {
            System.out.println("Purr purr");
        } else {
            System.out.println("...");
        }
        return agrees;
    }

}

De la même manière, voici une implémentation possible des méthodes de Pet dans la classe Dog :

java
public class Dog extends Animal implements Pet {

    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void feed() {
        System.out.println("Munch munch");
    }

    @Override
    public void play() {
        System.out.println("Woof!");
    }

    @Override
    public boolean pet() {
        System.out.println("Yap!");
        return true;
    }
}

↗
Programmation Java/Interfaces
fr.wikibooks.org

Comportement par défaut

Comme tu l'as vu précédemment, il est de la responsabilité de la classe implémentant l'interface d'en décrire ses comportements : en clair, de renseigner le corps de ses méthodes.

Cependant, depuis Java 8, il est possible de donner un comportement par défaut aux méthodes d'une interface, grâce au mot-clé default.

Voici un exemple avec la méthode trick() dans la classe Pet :

java
    default void trick() {
        System.out.println("Does a trick.");
    }

Ainsi, du fait que la méthode a un comportement par défaut, tu n'es pas obligé de définir son comportement dans les classes qui l'implémentent.

Cependant, il t'est toujours possible, par polymorphisme, de redéfinir son comportement.

Par exemple dans la classe Cat :

java
    @Override
    public void trick() {
        System.out.println("Nope.");
    }

↗
Default Methods
docs.oracle.com

Récapitulatif

Tu vas pouvoir tester ces nouvelles notions en récupérant le dépôt git donné en ressource.

Il contient quatre classes et une interface:

  • Animal, classe abstraite

  • Pet, l'interface que tu as vue dans la quête

  • Dog, classe héritée de Animal qui implémente Pet

  • Cat, classe héritée de Animal qui implémente Pet

  • Home, que tu compileras et exécuteras, afin de voir en détail :

    • les appels des méthodes implémentées

    • l'appel d'une méthode avec un comportement par défaut

Ensuite, ouvre ces classes et interface pour regarder comment tout ça est organisé. N'hésite pas à les modifier afin d'ajouter de nouvelles méthodes dans l'interface !

↗
Dépôt git
github.com

Challenge

I Believe I Can Fly !

Tu vas pouvoir t'exercer à la création et l'implémentation d'une interface, en premier fais un Fork du dépôt suivant puis clone-le en local.

Pense bien à faire un Fork, sinon tu ne pourras rien pousser !

Exemple de résultat lors de l'exécution de Nature :

shell
Hawkeye takes off in the sky.
Hawkeye flies upward, altitude: 120
Hawkeye flies upward, altitude: 150
glides into the air.
Hawkeye flies downward, altitude: 10
Hawkeye is too high, it can't land.
Hawkeye flies downward, altitude: 1
Hawkeye lands on the ground.

Critères de validation

  • Le dépôt GitHub contient bien le fichier Fly.java
  • Les classes et interfaces respectent les conventions de la POO : attributs privés, utilisation du mot-clef this, getters et setters nommés correctement
  • La classe Eagle implémente l'interface Fly
  • La classe Nature contient l'appel des méthodes d'une instance de Eagle
  • La classe Nature se compile sans erreur et affiche dans le terminal un résultat proche de celui présenté précédemment

Ressources partagées sur cette quête