Interaction entre objets

Interfaces

L’interface entre deux domaines

Dans le monde réel, une interface est la zone, la frontière commune entre deux domaines ou milieux. L’interface permet les échanges entre ces deux éléments aux propriétés différentes.

Si on prend l’exemple d’un ordinateur, on pourra le brancher au secteur électrique quel que soit le pays où l’on se trouve. L’interface entre l’ordinateur et le réseau est multiple et se fait grâce à un adaptateur secteur.

Ce même adaptateur possède plusieurs interfaces interchangeables permettant de le brancher à une prise électrique. Et cette prise de courant est une interface vers l’alimentation électrique présente dans le bâtiment.

L’interface ici est le câble. C’est le lien entre l’appareil électrique et le réseau électrique.

Utilisation des interfaces en Java

Déclaration

En Java, une interface est une classe totalement abstraite.

package com.example.java.interfaces;

public interface InterfaceUSB {
    public void communiquerParPortUSB(byte[] information);
}

Implémentation

Pour qu’une classe implémente une interface, on utilise le mot clé implements.

package com.example.java.interfaces;

public class Telephone implements InterfaceUSB{
    private String nom ;
    public Telephone(String nom) {
        this.nom = nom;
    }

    @Override
    public void communiquerParPortUSB(byte[] information) {
        System.out.println("Données reçues par Telephone("+nom+")");
        System.out.println(new String(information));
        System.out.println("Fin de la réception");
    }

}

Contrairement au mot clé extends, implements peut être suivi de plusieurs valeurs, séparées par des virgules :

package com.example.java.interfaces;

public class Imprimante implements InterfaceUSB, InterfaceReseau{
  //...
}

De cette manière on a une forme d’héritage multiple.

Le terme employé est polymorphisme (on le verra plus tard).

Exemple d’utilisation d’une interface

La classe Ordinateur utilise l’interface InterfaceUSB.

package com.example.java.interfaces;

public class Ordinateur {

    public void envoyerDonneesUSB(InterfaceUSB ir, byte[] information) {
        ir.communiquerParPortUSB(information);
    }
}

La méthode envoyerDonneesUSB utilise une variable du type InterfaceUSB.

Cette variable référence une instance qui implémente cette interface pour envoyer des données.

La classe Communication permet de brancher tout ces éléments entre eux:

public class Communication {
    public static void main(String[] args) {
        Ordinateur ordi = new Ordinateur();
        byte[] donnees = "Bonjour".getBytes();
        ordi.envoyerDonneesUSB(new Telephone("rouge"), donnees);

        donnees = "Page à imprimer".getBytes();


        InterfaceUSB imprimanteAnonyme = new InterfaceUSB() {
            @Override
            public void communiquerParPortUSB(byte[] information) {
                System.out.println("//Je suis une imprimante et j'imprime une page:");
                for (int i = 0; i < information.length; i++) {
                    System.out.println(String.valueOf(information[i]));
                }
                System.out.println("//Fin de l'impression");
            }
        };

        ordi.envoyerDonneesUSB(imprimanteAnonyme, donnees);
    }
}

Qu’observez-vous dans ce code ?

Pensez-vous logique que l’on appelle un constructeur sur l’interface InterfaceUSB alors que c’est une classe abstraite ?

Eléments de réponse:

Une interface est toujours implémentée par une classe.

Cependant, certaines classes peuvent être anonymes.

C’est le cas ici de imprimanteAnonyme.

On affecte à cet attribut une classe anonyme, c’est à dire :

Autres exemples

D’autres exemples sont disponibles dans le cours : https://en.wikibooks.org/wiki/Java_Programming/Interfaces

Contrat d’utilisation

L’interface représente un contrat d’utilisation entre les classes qui l’implémentent et les classes qui l’utilisent.

On peut éventuellement ajouter des constantes à une interface, qui seront utilisables directement par les classes qui implémenteront l’interface.

On ajoute aussi des commentaires et on peut même indiquer des traitements d’exception.

package com.example.java.interfaces;

public interface InterfaceReseau {
    public static final int VITESSE_RESEAU_1GB = 2^1000;
    public static final int VITESSE_RESEAU_100MB = 2^100;
    public static final int VITESSE_RESEAU_10MB = 2^10;

    /**
     * Permet de traiter les informations données en paramètre
     * Cette méthode ne peut a priori être appelée qu'une fois
     * que l'adresse réseau de l'imprimante a été affectée
     * @param information l'information à traiter
     * @throws IllegalAccessException si l'adresse réseau n'est pas configurée pour cet équipement
     */
    public void communiquerParReseauFilaire(byte[] information) throws IllegalStateException;

    /**
     * Affecte l'adresse réseau à cet équipement.
     *
     * Cette méthode modifie l'état interne de l'instance
     * pour permettre l'appel des autres méthodes de la classe
     * @param adresse l'adresse à affecter sous forme d'un
     * tableau de byte (pour prendre en charge IPv4 et IPv6)
     */
    public void affecterAdresseReseau(byte[] adresse);
}

Etudiez les commentaires Javadoc.

Quelle est leur utilité ?

Application

Faire un schéma UML des classes précédentes

Implémentez-les et faites exécuter ce programme.

Complétez la classe Imprimante pour qu’elle affiche des informations lorsqu’elle est connectée au réseau (c’est-à-dire qu’elle a une adresse réseau affectée):

package com.example.java.interfaces;

public class Imprimante implements InterfaceUSB, InterfaceReseau{
    // Ici on utilise une constante définie dans l'interface
    int vitesse = VITESSE_RESEAU_100MB;

    @Override
    public void communiquerParReseauFilaire(byte[] information) throws IllegalStateException {
        //...
    }

    @Override
    public void communiquerParPortUSB(byte[] information) {
        //...
    }

    @Override
    public void affecterAdresseReseau(byte[] adresse) {
        // TODO Auto-generated method stub

    }
}

Ajouter à la classe Ordinateur la méthode suivante qui utilisera l’interface InterfaceReseau pour envoyer des données:

public void envoyerDonneesReseau(InterfaceReseau ir, byte[] information) ;

Chaque communication sera affichée dans la console avec un préfixe: USB ou Réseau respectivement.

Testez votre implémentation en faisant envoyer des messages via les deux ports de communication de l’ordinateur à une instance d’Imprimante

Polymorphisme

Un objet, plusieurs types

Les interfaces permettent aux objets d’être polymorphes. C’est-à-dire, pour une instance, d’être de plusieurs types.

Une classe peut implémenter plusieurs interfaces.

Une variable est typée

On donne un type à la variable qui référence l’instance.

C’est le type de la variable qui détermine les méthodes appelables.

Une même instance peut être référencée par des variables de types différents:

Héritage multiple ?

En Java, l’héritage multiple n’est pas possible. Vous ne trouverez jamais ce genre de chose:

Héritage Multiple
Héritage Multiple

Et c’est tant mieux !

Il existe bien d’autres moyens de partager des fonctionnalités entre des classes différentes.

Les interfaces en Java permettent de définir une liste de fonctionnalités que fournira une classe.

Ces interfaces sont, on l’a dit, des classes totalement abstraites.

Héritage Multiple
Héritage Multiple

Code de l’interface VehiculeMotorise :

package com.example.transport;

public interface VehiculeMotorise {

    /**
     * Augmente la vitesse du véhicule de 1m/s
     */
    public void accelerer();
}

On remarque qu’ici, la Javadoc donne le contrat de fonctionnement de l’interface.

En Java, une classe implémente une interface mais n’en hérite pas.

Donc, l’inconvénient est qu’il faut réimplémenter la méthode de l’interface dans la classe.

Heureusement, on peut (par exemple) utiliser ce type de conception afin de réutiliser le code de la classe Moteur:

Héritage Multiple
Héritage Multiple

Code

Tester les classes en utilisant les codes ci-dessous.

Code de la classe Vehicule:

package com.example.transport;

public class Vehicule {
    private int vitesse;
    private int position;
    public void setVitesse(int vitesse){
        this.vitesse = vitesse;
    }
    public int getVitesse(){
        return vitesse;
    }
    public int getPosition(){
        return position;
    }
    public void metAJourPosition(){
        position += vitesse ;
    }
}

Code de la classe Moteur:

package com.example.transport;

public class Moteur {
    private Vehicule vehicule;
    public void setVehicule(Vehicule v){
        vehicule = v;
    }
    public void propulse(){
        vehicule.setVitesse(
                vehicule.getVitesse() + 1);
    }
}

Code de la classe Automobile:

package com.example.transport;

public class Automobile extends Vehicule implements VehiculeMotorise {

    private Moteur moteur = new Moteur();
    public Automobile(){
        moteur.setVehicule(this);
    }
    public void accelerer(){
        moteur.propulse();
    }
}

Code de la classe FaireRoulerVoiture:

package com.example.transport;

public class FaireRoulerVoiture {
    public static void main(String[] args) {
        Automobile auto = new Automobile();
        //On accélère
        auto.accelerer(); //v = 1
        //On avance
        auto.metAJourPosition();
        System.out.println(auto.getPosition());

        auto.accelerer(); //v = 2
        auto.metAJourPosition();
        System.out.println(auto.getPosition());
        auto.metAJourPosition();
        System.out.println(auto.getPosition());
    }
}

Délégation

Ici, c’est bien le moteur qui modifie la vitesse du véhicule.

Son code est réutilisable pour n’importe quel type de véhicule motorisé (Bateau, Avion, etc).

Le véhicule motorisé délègue au moteur la tâche d’accélération.

On parle donc ici d’une délégation. Le code est réutilisable, sans faire d’héritage.

Posez la question à l’intervenant pour éclaircir ce point si nécessaire.

Utilisation d’objets par d’autres objets

Vous allez maintenant mettre en application ce qui précède.

Pour cela, créer trois classes selon le diagramme de classe suivant:

Diagramme de classe: Stylo
Diagramme de classe: Stylo

La méthode ecrire(String chaine) de stylo produira l’écriture sur la sortie standard.

La méthode ecrire(String chaine) de ses classes filles rajoutera ECRIRE_EN_couleur{ au début de la chaîne avant de l’écrire puis y rajoutera } à la fin de la chaîne.

Par exemple si on a dans la fonction main(String[]):

StyloRouge styloR = new StyloRouge();
styloR.ecrire("Bonjour");
// affiche: "ECRIRE_EN_ROUGE{Bonjour}"

Petite difficulté supplémentaire: vous n’avez pas le droit d’utiliser de System.out.println dans les classes StyloRouge ni StyloBleu

Utilisation par un autre objet

Nous allons maintenant faire en sorte que notre stylo soit utilisé par un poète qui souhaite rédiger un poème. Il va simplement l’afficher à l’écran grâce au stylo.

Voici un diagramme de classe représentant notre programme:

Diagramme de classe: Stylo
Diagramme de classe: Stylo

Et voici le programme correspondant:

public class Poete {
    private String monNouveauPoeme = "";
    private Stylo stylo ;

    public void ajouterVersAuPoeme(String vers){
        monNouveauPoeme = monNouveauPoeme + "\n" ;
        monNouveauPoeme += vers;
    }

    public void setStylo(Stylo stylo){
        this.stylo = stylo;
    }

    public void ecrirePoeme(){
        stylo.ecrire(monNouveauPoeme);
    }
}

La classe main(String[]) ressemble à ceci:

public class Main {
    public static void main(String[] args) {
        Poete poete = new Poete();
        poete.ajouterVersAuPoeme("Maître Corbeau, sur un arbre perché");
        poete.ajouterVersAuPoeme("Tenait en son bec un fromage");

        poete.setStylo(new StyloRouge());

        poete.ecrirePoeme();
    }
}

Modifiez ce code en supprimant la ligne suivante du main(String[]):

poete.setStylo(new StyloRouge());

Que constate-t-on ?

Cette erreur vient du fait que la variable monStylo n’est plus initialisée. Comment lui donner une valeur par défaut, de sorte que même si on n’appelle pas la méthode setStylo(Stylo) le programme ne plante pas ?

Exercice

Réfléchissez à un moyen de vous passer des classes StyloRouge et StyloBleu tout en gardant les fonctionnalités qu’ils offrent.

Vous allez utiliser pour cela des attributs et modifier le comportement de la méthode ecrire(String).

Dessinez le diagramme de classe correspondant.

Exercices

Interface Hierarchisable

On s’intéresse ici à l’interface Hierarchisable :

public interface Hierarchisable {
  boolean estPlusGrandQue(Object O);
}

Les implémentations de cette interface doivent respecter le contrat suivant:

la méthode estPlusGrandQue doit retourner true si l’objet courant (this) est considéré comme plus grand que l’objet reçu en paramètre. Sinon, elle renvoie false. Si l’objet reçu en paramètre n’est pas de la même classe que l’objet courant, la méthode retournera une exception de type IllegalArgumentException.

On définit maintenant une classe nommée Personne qui implémente l’interface Hierarchisable et qui comporte au minimum :

Vous pouvez si vous le souhaiter réutiliser la classe Personne déjà créée précédemment.

La classe String implémente la méthode Comparable.compareTo(...). Etudiez cette méthode et vérifier si elle peut vous servir ici.

Créer un programme principal permettant de classer dans l’ordre alphabétique plusieurs personnes.

interface Salutation

Voici l’interface Salutation:

public interface Salutation {
  /**
   * Renvoie une chaîne de caractère représentant
   * une forme de salutation selon la langue à employer
   */
  public String salut() ;

}

Voici une méthonde principale utilisant cette interface:

Salutation francais = new Bonjour();
System.out.println(francais.salut()); //Doit afficher Bonjour
Salutation [] salutations = new Salutation [] {
  new Bonjour() , //Français
  new Hello() , //Anglais
  new BuenosDias(),  //Espagnol
  new GutenTag() //Allemand
};
for (int i = 0; i < salutations.length; i++){
  System.out.println(salutations[i].salut());
}

Implémenter les classes Bonjour, Hello, BuenosDias et GutenTag.