Vous êtes sur la page 1sur 11

Les interfaces

Chapitres traités
Notion et intérêt des interfaces

Notion et intérêt des interfaces

Protection des objets


Nous avons déjà longuement parlé du principe d'encapsulation qui consiste, entre autre, de protéger au
maximum les attributs d'un objet. Dans ce cas de figure (qui est normalement le cas courant) la modification des
attributs passe obligatoirement par des méthodes adaptées. Il peut être intéressant de protéger encore plus un
objet afin qu'il ne soit accessible que par un intermédiaire (un représentant). Java permet d'utiliser cette
technique au moyen de l'interface. Seules quelques méthodes sont autorisées à être utilisées. Ce sont les
méthodes désignées par l'interface. Comme son nom l'indique, l'interface joue le rôle d'intermédiaire entre
l'application et l'objet sollicité.

Objets distribués
Dans l'exemple ci-dessus, il existe une classe FichePersonne. On désire qu'elle ne soit pas directement
accessible par les applications n°1 et n°2. La seule tolérance admise pour ces deux applications est de permettre
la visualisation de la fiche de chacune des personnes. On utilise donc un représentant de la classe qui est
l'interface Visualisation et qui valide la seule méthode affiche pour ce cas particulier.
Il existe une technologie récente qui est à la fois performante et séduisante qui permet de construire des objets
comme nous avons l'habitude de faire, mais qui ont la particularité d'être accessibles depuis n'importe quel
ordinateur du réseau. C'est la technologie des objets distribués. Elle est intéressante puisqu'elle permet de
construire les objets une fois pour toute sur une machine, et ainsi, il n'est pas nécessaire de les recopier sur tous
les autres ordinateurs du réseau. Attention toutefois, ces objets restent sur l'ordinateur où ils ont été créés. Ce
sont des objets distants qui fonctionnent en permanence, et donc la seule possibilité de les solliciter est de
passer par des requêtes sur le réseau, et donc de passer par des interfaces qui utilisent les méthodes
appropriées. Dans l'exemple ci-dessus, l'application du client désire récupérer l'adresse d'une personne, elle
passe donc par le représentant de la classe FichePersonne qui est l'interface RequêtePersonne qui dispose de la
méthode getAdresse (généralement, il existe plusieurs méthodes pour ce genre d'interface). On retrouve ici le
même principe que pour les bases de données.

Hiérarchies différentes

Il arrive assez souvent qu'on désire mettre en relation des objets qui n'ont apparemment aucun rapport entre
eux. C'est notamment le cas lorsqu'on désire afficher des objets qui sont issus de hiérarchies différentes. Il suffit
alors de prévoir une interface qui proposera une méthode commune à l'ensemble des objets à afficher. Dans
l'exemple ci-dessus, les classes Cercle et Carré héritent de la classe Forme qui dispose de la méthode Dessine.
Par ailleurs, la classe Texte qui est totalement indépendante de la première hiérarchie dispose également de la
méthode Dessine. Pour être sûr de dessiner un objet quelconque sur une fenêtre, par exemple, il suffit de passer
par l'interface Présentation qui dispose de la méthode Dessine et qui spécifie donc que les objets associés à
cette interface vont obligatoirement implémenter cette méthode.

Gestion événementielle
Tout système d'exploitation qui supporte des interfaces graphiques doit constamment surveiller l'environnement
afin de détecter des événements tels que la pression sur une touche du clavier ou sur un bouton de la souris.
Java contrôle complètement la manière dont les événements sont transmis de la source d'événement (par
exemple, un bouton ou un élément de menu) à l'écouteur d'événement (event listener). Vous pouvez désigner
n'importe quel objet comme écouteur d'événement. Lorsqu'un événement arrive à la source, celle-ci envoie une
notification à tous les objets écouteurs d'événements.

Comme il est possible d'avoir n'importe quel objet (issu d'une hiérarchie quelconque) comme écouteur
d'événement, il est nécessaire de passer par le système d'interface. En fait, un objet écouteur est une instance
d'une classe qui implémente l'interface spéciale appelée interface écouteur (listener interface). Dans l'exemple
ci-dessus, deux écouteurs ont été mis en oeuvre, l'objet relatif à un élément du menu et l'objet boutonCercle
(dans ce cas de figure, ils sont à la fois source et écouteur). Ils sont représentés par l'interface ActionListener,
et lorsqu'un un événement se produit (correspondant à un clic sur un bouton de la souris ou la validation par le
clavier) la méthode actionPerformed est exécutée.

Pour en savoir plus sur la gestion événementielle

Mise en oeuvre des interfaces


Mais qu'est-ce qu'une interface ? Il s'agit essentiellement d'une annonce (ou d'une promesse) indiquant qu'une
classe implémentera certaines méthodes et respectera donc le contrat prévu par l'interface. Nous devons
d'ailleurs utiliser le mot clé implements pour signaler qu'effectivement cette classe tiendra cette promesse.

Définition d'une interface


Les interfaces contiennent uniquement des définitions de constante et des déclarations de méthodes, sans leur
définition (autrement dit, les méthodes indiquées dans les interfaces ne comportent que le prototype sans le
corps). La déclaration d'une interface se présente comme celle d'une classe. On utilise simplement le mot clé
interface à la place de class :

En reprenant l'exemple proposé plus haut, nous obtenons :


Par essence, les méthodes d'une interface sont :

1. abstraites (puisque nous ne fournissons pas de définition) et,


2. publiques (puisqu'elles devront être redéfinies plus tard).

Néanmoins, il n'est pas nécessaire de mentionner les mots clés public et abstract (nous pouvons quand même le
faire).

Une interface peut être dotée des mêmes droits d'accès qu'une classe (public ou droit de paquetage). Dans le
cas où nous désirons avoir une interface publique :

Cette interface particulière possède une méthode. Certaines interfaces possèdent plusieurs méthodes. Les
interfaces ne disposent jamais d'attributs (à part les constantes), et les méthodes ne sont jamais implémentées
directement dans l'interface. La fourniture des attributs et des implémentations de méthodes sont pris en charge
par la ou les classes qui implémentent l'interface.

Implémentation d'une interface


Pour qu'une classe implémente une interface et donc respecter le contrat (ou la promesse) prévu par cette
dernière, vous devez exécuter deux étapes :

1. déclarer que votre classe a l'intention d'implémenter l'interface donnée,


2. fournir les définitions de toutes les méthodes de l'interface.

Pour déclarer qu'une classe implémente une interface, employez le mot réservé implements :

Alors, la classe A :
1. dispose des constantes définies dans l'interface I.
2. est contrainte de définir toutes les méthodes prototypées dans l'interface I ; plus exactement, si la classe A ne
définit pas toutes les méthodes prototypées dans I, elle doit être déclarée abstraite et ne pourra donc pas être
instanciée.

Pour en savoir plus sur les classes abstraites

Utilisation de l'interface
Sachant que la classe A implémente l'interface I, nous savons (si A n'est pas abstraite) qu'elle dispose de toutes
les méthodes de cette interface I ; nous possedons donc un renseignement sur ce dont nous pouvons faire avec
les instances de cette classe. Concrètement, en reprenant l'exemple de la classe FichePersonne et en utilisant
l'interface Visualisation, nous pouvons écrire :

On peut donc définir des variables de type interface, ici de type Visualisation ; nous pouvons alors invoquer
uniquement la méthode déclarée dans Visualisation sur un objet référencé par une variable de type Visualisation
ou, si cela avait été le cas, utiliser les constantes définies dans l'interface. Cela a des conséquences
fondamentales sur les possibilités de programmation.

1. Les attributs (constants) et les méthodes d'une interface sont automatiquement publics ; cela implique qu'une
classe qui implémente une interface devra déclarer publiques (avec le modificateur public) les méthodes de
l'interface qu'elle définit.
2. Si une classe A implémente une interface I, une sous-classe B de A implémente aussi automatiquement I ; une
instance B pourra être référencée par une variable de type I et bénéficiera, au moins par héritage, des
définitions des méthodes prototypées dans I.

Une même classe peut implémenter plusieurs interfaces :

Dans ce cas d'utilisation, les interfaces permettent de compenser, en grande partie, l'absence d'héritage
multiple.

Propriétés des interfaces


Les interfaces ne sont pas des classes. En particulier, vous ne devez jamais utiliser l'opérateur new pour créer
un objet de type interface :

x = new Visualisation(); // erreur

Cependant, bien que vous ne puissez pas construire des objets interface, vous pouvez toujours déclarer des
variables interface :
Visualisation personne ;

Une variable interface doit faire référence à un objet d'une classe qui implémente l'interface :

personne = new FichePersonne("lagafe", "gaston", ... );

La variable interface ne peut ensuite utiliser que les méthodes prévues par le contrat, c'est-à-dire, déclarées
dans l'interface :

personne.Affiche();

Variables de type interface et polymorphisme


Bien que la vocation d'une interface soit d'être implémentée par une classe, on peut définir des variables de type
interface :

Bien entendu, on ne pourra pas affecter à i une référence à quelque chose de type I puisqu'on ne peut pas
instancier une interface (pas plus qu'on ne pouvait instancier une classe abstraite !). En revanche, on pourra
affecter à i n'importe quelle référence à un objet d'une classe implémentant l'interface I :

De plus, à travers i, on pourra manipuler des objets de classes quelconques, non nécessairement liées par
héritage, pour peu que ces classes implémentent l'interface I.

Voici un exemple illustrant cet aspect. Une interface Affichable comporte une méthode affiche. Deux classes
Entier et Flottant implémentent cette interface (aucun lien d'héritage n'apparaît ici). On crée un tableau
hétérogène de références de type Affichable qu'on remplit en instanciant des objets de type Entier et Flottant.
Résultat :

Je suis un entier de valeur 25


Je suis un flottant de valeur 1.25
Je suis un entier de valeur 42

Cet exemple est restrictif puisqu'il peut se traiter avec une classe abstraite. Voyons maintenant ce que
l'interface apporte de plus.

Un des principaux intérêts


Une interface permet de mettre en connexion plusieurs hiérarchies de classes, qui à priori n'ont aucuns lien
communs entre elles, par l'intermédiaire de méthodes spécifiques déterminées par l'interface, comme ici la
méthode affiche :
Interfaces et constantes
L'essentiel du concept d'interface réside dans les en-têtes de méthodes qui y figurent. Mais une interface peut
aussi renfermer des constantes symboliques qui seront alors accessibles à toutes les classes implémentant
l'interface :

Ces constantes sont automatiquement considérées comme si elles avaient été déclarées static et final. Il doit
s'agir obligatoirement d'expressions constantes. Elles sont accessibles en dehors d'une classe implémentant
l'interface. Par exemple, la constante MAXI de l'interface I se notera simplement I.MAXI.

Dérivation d'une interface


Tout comme vous pouvez construire des hiérarchies de classes, vous pouvez étendre des interfaces. Cela
autorise plusieurs chaînes d'interfaces allant d'un plus large degré de généralité à un plus petit degré de
spécialisation :
En fait, la définition de I2 est totalement équivalente à :

La dérivation des interfaces revient simplement à concaténer des déclarations.

Exemple de synthèse
Je vous propose un petit exemple de synthèse qui permet de stocker dans un même fichier des objets de classes
totalement différentes.

Principal.java

import java.io.*;
//------------------------------------------------------------------------------
interface Enregistrement extends Serializable {
void sauvegarder(ObjectOutputStream fichier);
void afficher();
}
//------------------------------------------------------------------------------
class Personne implements Enregistrement {
private String nom, prénom;

public Personne(String nom, String prénom) {


this.nom = nom;
this.prénom = prénom;
}
public void sauvegarder(ObjectOutputStream fichier) {
try {
fichier.writeUTF(getClass().getName());
fichier.writeObject(this);
}
catch (IOException ex) { System.err.println("Erreur de fichier"); }
}
public void afficher() {
System.out.println(nom);
System.out.println(prénom);
}
}
//------------------------------------------------------------------------------
class Notes implements Enregistrement {
private double[] notes = new double[10];
private int nombre = 0;

public Notes ajoutNote(double note) {


notes[nombre++] = note;
return this;
}
public void sauvegarder(ObjectOutputStream fichier) {
try {
fichier.writeUTF(getClass().getName());
fichier.writeObject(this);
}
catch (IOException ex) { System.err.println("Erreur de fichier"); }
}
public void afficher() {
for (int i = 0; i < nombre; i++)
System.out.print(notes[i] + " ");
System.out.println();
}
}
//---------------------------------------------------------------------------------------------
public class Principal {
public static void main(String[] args) {
Personne personne = new Personne("nomUn", "prénomUn");
Notes notes = new Notes();
notes.ajoutNote(15.0).ajoutNote(8.0).ajoutNote(12.0);
Enregistrement[] enregistrement = {personne, notes, new Personne("nomDeux", "prénomDeux")};
try {
ObjectOutputStream sortie = new ObjectOutputStream(new FileOutputStream("test.dat"));
sortie.writeInt(enregistrement.length);
for (int i = 0; i < enregistrement.length; i++) enregistrement[i].sauvegarder(sortie);
}
catch (IOException ex) { System.err.println("Erreur création de fichier"); }
Enregistrement[] restitution = null;
try {
ObjectInputStream entrée = new ObjectInputStream(new FileInputStream("test.dat"));
restitution = new Enregistrement[entrée.readInt()];
for (int i = 0; i < restitution.length; i++) {
String classe = entrée.readUTF();
if (classe.equals("Personne")) restitution[i] = (Personne) entrée.readObject();
if (classe.equals("Notes")) restitution[i] = (Notes) entrée.readObject();
}
}
catch (IOException ex) { System.err.println("Erreur création de fichier"); }
catch (ClassNotFoundException ex) { System.err.println("Erreur lecture de l'objet"); }
for (int i = 0; i < restitution.length; i++) restitution[i].afficher();
}
}
//---------------------------------------------------------------------------------------------

Principal.java
Cet exercice consiste à créer une classe Cercle qui sera capable
entre autre d'afficher un cercle de diamètre 100 aux coordonnées
fixées au moment de la création ainsi qu'une classe Carré qui
sera également capable d'afficher un carré de 100 pixels de côté.
Ces deux classes hériterons d'une classe Forme qui possède les
attributs correspondant aux coordonnées centrale de chaque type
de figure ainsi qu'une méthode d'affichage vide. Vous allez
rajouter une classe que vous nommerez Texte qui aura la
particularité de stocker une chaîne de caractères de votre choix à
une position bien déterminée.

Dans la classe Panneau (héritage de JPanel), vous allez créer un


tableau d'éléments affichables, qui comportera un cercle, un
carré, et un objet de la classe Texte. En fait, la première case du
tableau correspond au cercle de coordonnées (70, 70), la
deuxième case au carré de coordonnées (200, 70), et enfin la troisième le texte de bienvenue qui vous est
proposé aux coordonnées (100, 150).

Lorsque vous proposerez l'affichage, il faudra lancer l'affichage d'un élément sans le connaître au préalable.
Etant donné que la classe Texte n'a absolument rien à voir avec toute la hiérarchie de la classe Forme, il est
nécessaire de passer par une interface que vous appellerez Présentation, et la connexion se fera par la méthode
dessine. Le cas échéant, faites toutes les modifications qui vous semblent nécessaires pour avoir une écriture
cohérente.

Vous aimerez peut-être aussi