Cours 12 du 11 avril :

Notion de types abstraits de données

Exemple:

Une Pile est une structure de données avec (essentiellement) les deux opérations empiler et dépiler, a priori on peut manipuler une Pile sans pour autant savoir comment elle est réalisée.

Quelques principes:

En supposant qu'un programme n'utilise que ce qui est définit par le type abstrait de données, on peut développer le programme indépendamment des implémentations du type de abstrait. 

Type abstrait de données: exemple de la Pile

 

 

Définition du type Pile de X (ici X est un type quelconque)

·        Nom du type : Pile[X]

·        types des opérateurs (signature des méthodes)

o       création

§         Pile :-> Pile[X]

o       test à vide

§         estVide: Pile[X] ->  Boolean

§         empiler: XxPile[X] -> Pile[X]

§         dépiler: Pile[X] -> XxPile[X]

 

·        propriétés des opérateurs:

o       préconditions:

§         dépiler(X): non etsVide(X)

o       axiomes:

§         Pour tout x de X pour tout s de Pile[X]

·        estVide(Pile()) (une pile créée est initialement vide)

·        non estVide(empiler(x,s))

·        dépiler(empiler(x,s))=(x,s)

 

Toutes les Piles au sens usuel vérifient ces propriétés et ces propriétés caractérisent les Piles.

Ces définitions et propriétés sont indépendantes de toute représentations (on ne sait même pas s'il peut y avoir une représentation) et avec un peu de formalisme supplémentaire on pourrait montrer d'autres propriétés sur les Piles uniquement à partir des axiomes.

 

Interface Java pour une Pile

On a vu dans le chapitre précédent le type Object qui peut contenir n'importe quel objet d'u type référence. On va donc construire un type Pile de Object. On pourra ainsi construire des piles sur n'importe quel objet. (On notera cependant que dans ce cas, on ne peut assurer que tous les objets de la pile sont du même type. Il existe en java un moyen –la généricité- permettant d'avoir des Piles construites avec des objets d'un type particulier).

 

 

Une interface en Java est composée de déclarations de méthodes mais sans définition de méthodes ni présence de variables d'instance:

// Interface Pile simple

interface Pile {

    public Object depiler();

    public void empiler(Object o);

    public boolean estVide();

}

 

On peut ensuite avoir diverses implémentations de la cette interface.

 

Implémentation avec des listes:

// listes d Object

class Noeud {

    Object item;

    Noeud suivant;

    public Noeud(Object o) {

        item=o;

        suivant=null;

    }

    public Noeud(Object o, Noeud n){

        item=o;

        suivant=n;

    }

}

// implémenter les Pile avec des listes

class PileListe implements Pile{

    private Noeud sommet;

    public PileListe(){sommet=null;}

    public boolean estVide(){ return (sommet==null);}

    public void empiler(Object o){

        sommet=new Noeud(o,sommet);

    }

    public Object depiler(){

        Object tmp=sommet.item;

        sommet=sommet.suivant;

        return tmp;

    }

}

 

Implémentation avec des tableaux :

 

// implémenter les piles avec une table

class PileTable implements Pile{

    private Object[] v;

    private int taillemax=1000;

    private int sommet;

    public PileTable(){

        v=new Object[taillemax];

        sommet=0;

    }

    public PileTable(int n){

        taillemax=n;

        v=new Object[taillemax];

        sommet=0;

    }

    public boolean estVide(){return sommet==0;}

    public Object depiler(){

        return v[--sommet];

    }

    public void empiler(Object o){

        v[sommet++]=o;

    }

}

 

On notera que:

 

La déclaration d'une variable de type  PileTable ou PileListe, comme la création d'un objet correspondant ne change pas de l'habitude:

 

PileListe q=new PileListe();

PileTable r=new PileTable(100);

 

Bien sûr il ne peut y avoir d'objet Pile général: aucune implémentation n'est présente, mais un objet PileTable ou PileListe peut toujours être considéré comme une Pile.

 

Pile q=new PileListe();

PileTable r

q=r;

r= (PileTable)q;

/* ce qui suit est incorrect

Pile t=new Pile();

PileListe u=new PileTable();

*/

On notera:

 

Notons que:

§         au moment de la compilation le compilateur vérifie la compatibilité des types d'après les déclarations des variables.

§         au moment de l'exécution la machine virtuelle java vérifie que les affectations ont bien un sens de façon à assurer par exemple que tout objet PileTable est bien une PileTable (de façon similaire pour les tableaux le machine virtuelle Java vérifie qu'on ne sort pas des indices du tableau).

 

On peut ensuite travailler avec une pile de façon générale sans se préoccuper de savoir s'il s'agit d'une PileTable ou d'une PileListe (ou de n'importe quelle autre implémentation de Pile!):

    public static Noeud Pile2Liste(Pile p){

        Noeud tmp;

        Noeud tete=tmp=new Noeud(null);

        while(!p.estVide())

            tmp=tmp.suivant=new Noeud(p.depiler());

        return tete;

    }

 

Cette méthode transforme une pile en liste chaînée: elle peut être utilisée avec n'importe quelle implémentation de Pile. Elle est utilisée dans le morceau de code suivant:

 

        Pile q=new PileListe();

        for (int i = 0; i < 100; i++)

            q.empiler(i);

        Noeud n=Pile2Liste(q);

 

Il est important de noter qu'au moment de la compilation le compilateur ne sait pas à quelle méthode concrète sera associée empiler ou depiler, il ne connaît que les méthodes abstraites depiler et empiler qui ont été déclarées dans l'interface Pile. Ce n'est qu'au moment de l'exécution que, suivant l'objet qui sera passé en paramètre (PileTable, PileListe ou autre Pile) dans le premier cas  ou qui a été affecté dans le deuxième cas, à quelle méthode concrète (empiler ou depiler de PileTable, PileListe ou autre Pile) sera associée à p.depiler ou q.depiler. Il faut noter qu'au moment de la compilation du programme, il est tout à fait possible que la méthode concrète ne soit pas encore définie. Il s'agit d'un mécanisme nouveau: jusqu'ici l'association entre un nom (p.depiler) et le code se faisait à la compilation. On parle dans ce cas de liaison dynamique[1].

 

Pour résumer:

 

Ces mécanismes sont à la base de la programmation orientée objets. Ils se généralisent en Java à d'autres constructions que les interfaces. Pour l'essentiel on a les deux notions liées:



[1] D'une façon générale, ce qui concerne ce qui se passe à la compilation est dit statique et ce qui concerne l'exécution est dit dynamique.