ENPC - Programmation - Séance 8

Classes abstraites

Nous avons vu qu'une classe C permettait de créer des instances, sur lesquelles chacune des méthodes (définies dans la classe ou héritées de ses super-classes) peut être appliquée puisqu'elle dispose d'un corps spécifiant une suite d'instructions à exécuter.

Nous avons vu qu'à l'opposé, une interface I permettait uniquement de décire un type I, associé à un ensemble de déclarations de méthodes. Le principe est alors que toute instance d'une classe qui implémente une telle interface a, en plus du type de la classe qui l'a créé et du type de ses super-classes qu'elle paratage déjà (par exemple, elle est aussi de type Object), le type de l'interface qu'implante sa classe: elle est aussi de type I et peut donc être stockée dans une variable i de type I. Cela signifie qu'on peut appliquer sur i (sur la référence qu'elle stocke) toute méthode déclarée dans I: en effet, même si ces méthodes n'ont pas de code spécifié dans I, le fait que la classe qui à créé la référence stockée dans i implémente I (i.e., se doit de fournir une implantation pour chacune de ses méthodes), plus le fait que la résolution dynamique permet d'appliquer à l'exécution la méthode la plus précise relativement au receveur, font qu'il est toujours possible d'appeler les méthodes sur i et qu'elles disposeront d'un code lors de l'appel. En revanche, les interfaces ne permettent pas de décrire un état (pas de variable d'instance) et il n'est pas possible d'en créer des instances.

Les classes abstraites sont un peu à mi-chemin entre les classes (dites concrètes) et les interfaces. Les classes abstraites permettent:

En revanche, les classes abstraites ne peuvent pas être instanciées (on ne peut pas en créer d'instances avec new). Il est cependant possible de faire hériter une classe abstraite d'une autre classe, abstraite ou non, de lui faire implémenter une ou plusieurs interface, etc... De même, une classe concrète peut hériter d'une classe abstraite. En fait, elle devra fournir l'implantation (par redéfinition) de toutes les méthodes dont elle hérite qui étaient déclarées abstraites pour avoir le droit d'être concrète.

Le principal avantage d'une classe abstraite CA est de pouvoir fournir en même temps:

Du point de vue syntaxique, la déclaration d'une classe ou d'une méthode abstraite se fait avec le mot-clé abstract. Dès qu'une méthode est abstract dans une classe, la classe doit obligatoirement être abstract. Voici un exemple de déclaration:

public abstract class CA extends C implements I {
  double d;                      // on peut déclarer des champs
  Object o;
  public double foo() {         // on peut définir des méthodes "concrètes"
    return d * m(2);            // qui utilisent des méthodes abstraites
  }
  public abstract void m(int i); // et déclarer des méthodes abstraites 
}

Reprenons l'exemple de notre interface Mesurable qui déclarait une méthode double surface(). Une première remarque est de constater qu'en fait, dans une interface, toute méthode est forcément publique et abstraite. Autrement dit:

public interface Mesurable {
  double surface();
}
// est absoluement équivalent à
public interface Mesurable {
  public abstract double surface();
}

Nous aimerions maintenant disposer d'une méthode min() sur les Mesurable qui retourne la référence du plus petit Mesurable entre le receveur et l'argument. Puisque cette méthode n'utilise que la méthode surface(), il est possible de définir son code une seule fois dans une classe abstraite afin qu'il soit hérité dans toutes les classes concrètes telles que Disque Anneau Carre, etc...

Exercice 1: remplacer l'interface Mesurable par une classe abstraite AbstractMesurable qui déclare surface() et définit min(). Faire hériter les classes concrètes de cette classe abstraite et tester la méthode min().

Exercice 2: comment était il possible de conserver en même temps Mesurable et AbstractMesurable? Essayer cette solution.

Piles et implémentation

Les piles sont une des structures de données classiques, largement utilisées. Elles sont quelquefois appelées structure de données LIFO (Last In First Out): le dernier élément ajouté à une pile est le premier à en être retiré. Cette appellation fait également référence à une autre structure de données, les files ou queues, qui elles sont FIFO (First In First Out), pour lesquelles le premier élément ajouté à une file sera le premier à être retiré.

Intuitivement, tandis qu'une queue FIFO correspond à une file d'attente, une pile LIFO correspond à une pile d'assiettes sur laquelle on ne peut ajouter qu'une assiette à la fois et sur le dessus, et de laquelle on ne peut prendre qu'une assiette à la fois et sur le dessus.

Exercice 3: écrire une interface Stack représentant les fonctionnalités qu'on attend d'une pile d'Object. Classiquement, ces fonctionnalités sont l'ajout (void push(Object o)), le retrait (Object pop()) et le test permettant de savoir si la pile est vide (boolean isEmpty()).

Exercice 4: écrire une première implémentation de l'interface Stack sous la forme d'une classe ArrayStack qui stocke, en interne, les éléments de la pile dans un tableau d'objet.

Exercice 5: écrire une seconde implémentation de l'interface Stack sous la forme d'une classe VectorStack qui stocke, en interne, les éléments de la pile dans un vecteur (de la classe java.util.Vector).

Empiler les éléments d'une pile

Nous voulons maintenant disposer d'une fonctionnalité un peu particulière, permettant d'empiler tous les éléments d'une pile p2 sur une pile p1. Néanmoins, avec l'interface Stack dont on dispose, l'instruction p1.push(p2) a pour effet d'ajouter sur le dessus de la pile p1 un seul objet qui est la pile p2 elle-même. On voudrait avoir maintenant une méthode pushAll qui, lorsqu'elle est appelée sur une pile p1 et qu'elle accepte un objet en argument qui est une pile p2, ajoute séparément chacun des éléments de la pile p2 à la pile p1, dans le même ordre où ils étaient empilés dans p2.

Exercice 6: complétez la hierarchie de types formée par Stack, ArrayStack et VectorStack par des classes, interfaces ou classes abstraites de sorte à disposer de cette méthode pushAll.


Etienne.Duris[at]univ-mlv.fr - © École Nationale des Ponts et Chaussées - Janvier 2002 - http://www-igm.univ-mlv.fr/~duris/ENPC/