IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Règles de la programmation orientée objet en C


précédentsommaire

II. Règles de transformation d'un modèle objet en C

II-A. Objectif

Pour modéliser des programmes complexes, la méthodologie objet s'impose aujourd'hui. Cependant, pour des raisons techniques, il est parfois nécessaire de développer en C.

De prime abord, le C n'est pas un langage adapté à une modélisation objet. Mais cela est possible si nous nous équipons de règles de transformation d'un modèle objet en C afin d'avoir une programmation des différents objets homogènes et cohérents.

II-B. Les classes

II-B-1. Propriétés

Les propriétés d'une classe sont un ensemble d'attributs caractérisant un objet (ex. : la couleur d'un stylo : couleur est un attribut de l'objet stylo, il est alors aisé d'instancier un stylo bleu ou vert). Nous considérerons que les propriétés sont privées (Accessible uniquement avec des accesseurs). En C, une propriété est un composant d'une structure.

Le nom des propriétés respecte la règle des noms de variables.

Les propriétés doivent être décrites après les méthodes.

II-B-2. Méthodes privées, protégées et publiques

Les méthodes sont un ensemble de fonctions ou de procédures agissant sur un objet. Elles correspondent aux méthodes décrites dans le modèle (ex. : la méthode écrire pour un stylo). En C, nous ne pouvons pas faire la distinction entre une méthode publique, privée ou protégée. Nous considérerons que les méthodes sont publiques. Les méthodes se matérialiseront par des pointeurs sur des fonctions définies en interne dans un module.

Le premier paramètre d'une méthode est un pointeur sur un objet de type de la classe contenant la méthode (elle est nommée « this »). Cette pratique permet d'accéder aux propriétés de la classe.

Les méthodes doivent être décrites avant les propriétés.

Chaque méthode a une fonction associée. Une fonction associée a une méthode est nommée avec le nom de la méthode infixée par le nom de la classe.

Exemple : Soit la méthode Ecrire de la classe Stylo, la fonction associée à la méthode se nommera : Ecrire_stylo.

II-B-3. Méthodes obligatoires

II-B-3-a. Le(s) constructeur(s)

Cette méthode permet d'instancier proprement une classe. Elle alloue une nouvelle instance pour laquelle elle initialise les propriétés de la classe et positionne les méthodes sur les bonnes fonctions. Le constructeur se nomme « Instancier_<Nom de la classe> » (équivalent d'un new) Cette méthode n'est pas une méthode de l'objet, elle est une fonction d'accès au fichier source C. Le constructeur doit obligatoirement comporter les phases :

  • allocation d'un objet (et teste le bon fonctionnement de l'allocation) ;
  • affectation des méthodes ;
  • initialisation des propriétés ;
  • instanciation des agrégations ou des classes mères ;
  • retour du nouvel objet alloué.

REMARQUE : Il n'est pas interdit d'avoir plusieurs constructeurs. Dans ce cas, il faut préciser la nature du constructeur : « Instancier_<Nature>_<Nom de la classe> ». Un constructeur peut éventuellement faire appel à un autre constructeur de la même classe (Exemple : Instancier_copie_stylo).

II-B-3-b. Le destructeur

Cette méthode permet de libérer proprement une instance. Elle libère l'ensemble des objets instanciés dans sa classe et la mémoire allouée lors du constructeur. Elle initialise le pointeur sur l'instance à NULL. Le destructeur est une méthode et se nomme « Liberer » (équivalent d'un delete).

Cette méthode est une méthode de l'objet. La particularité de cette méthode est que le premier paramètre est un pointeur sur un pointeur d'instance.

Le destructeur doit obligatoirement respecter les phases :

  • suppression des associations liées à l'objet instancié ;
  • libération des objets agrégations instanciés dans la classe ;
  • libération de l'instance ;
  • initialisation de l'instance à NULL.

REMARQUE : il ne peut y avoir qu'un seul destructeur par classe.

II-B-3-c. Les accesseurs

Les propriétés d'une classe ne sont accessibles directement que dans les méthodes de la classe. Quand un objet d'une classe est utilisé, il faut accéder aux propriétés de la classe par des accesseurs.

BUT : encapsulation maximum (garantit l'intégrité de l'objet).

II-B-4. Les méthodes conseillées

Pour chaque classe, il faudra être capable de :

  • instancier par copie (Instancier_copie_<nom de la classe>) ;
  • comparer deux instances d'une même classe (Comparer) ;
  • comparer une instance avec une valeur autre qu'une instance du même type (Comparer_valeur) ;
  • tracer le contenu de l'instance d'une classe : dans le journal interne (Tracer) ou à l'écran (Ecrire) ;
  • contrôler la cohérence de l'instance (Est_coherente).

II-B-5. Les méthodes surchargées d'un objet

Elles comportent cinq parties :

  • déclare et initialise les variables ;
  • vérifie la cohérence des données (programmation défensive) ;
  • appelle les méthodes de la classe mère surchargée ;
  • traite les données ;
  • retourne les résultats.

II-B-6. Les méthodes réécrites d'un objet

Elles comportent quatre parties (comme une fonction et une procédure) :

  • déclare et initialise les variables ;
  • vérifie la cohérence des données (programmation défensive) ;
  • traite les données ;
  • retourne les résultats.

II-B-7. Règle de nommage des méthodes dans le fichier source C

Comme il n'est pas possible de décrire les méthodes à l'intérieur d'une classe, il faut définir une fonction ou une procédure par méthode. Le nom de la fonction ou de la procédure est postface par le nom de l'objet.

Exemple : Soit la méthode Liberer d'un stylo, la fonction associée sera Liberer_stylo.

II-B-8. Exemple simple

Classe Stylo
II-B-8-a. Définition du type en C dans le header
 
Sélectionnez
typedef enum { ROUGE, VERT, BLEU }e_Couleur ;

/* --- DESCRIPTION DE LA CLASSE STYLO --- */

typedef struct t_Stylo
{
  /* le destructeur */
  
  void (*Liberer)(struct t_Stylo **this) ;
  
  /* les méthodes */
  
  void (*Ecrire)(struct t_Stylo *this,char *i_Texte);
  e_Couleur (*Lire_encre)(struct t_Stylo *this) ;
  
  /* une propriété */
  e_Couleur Couleur;
} t_Stylo;

extern t_Stylo *Instancier_stylo(e_Couleur);
II-B-8-b. Définition de la classe en C dans le fichier source C
 
Sélectionnez
/* --- DESCRIPTION DES FONCTIONS ASSOCIEES AUX METHODES --- */

void Ecrire_stylo(t_Stylo *this,char *i_Texte)
{
  Change_couleur(this->Couleur);
  printf("Texte : <%s>\n",i_Texte);
  return ;
}

e_Couleur Lire_encre_stylo(t_Stylo *this)
{
  return(this->Couleur);
}

/* le destructeur */
void Liberer_stylo(t_Stylo **this)
{
  AL_FREE(*this);
  *this=(t_Stylo *)NULL;
  return;
}

/* le constructeur */
t_Stylo *Instancier_stylo(e_Couleur i_Couleur)
{
  /* allocation d'une instance */
  t_Stylo *this=(t_Stylo *)AL_MALLOC(sizeof(t_Stylo)) ;
  if (this==(t_Stylo *)NULL)
    return((t_Stylo *)NULL) ;

  /* affectation des méthodes */
  
  this->Ecrire=Ecrire_stylo ;
  this->Lire_encre=Lire_encre_stylo;
  this->Liberer=Liberer_stylo;

  /* initialisation des propriétés */
  
  this->Couleur=i_Couleur;

  /* retour de l'objet instancié */

  return(this);
}
II-B-8-c. Utilisation en C
 
Sélectionnez
void ma_fonction()
{
  TStylo *Stylo_Bleu=Instancier_stylo (Bleu);
  /* ... */
  Stylo_Bleu->Ecrire(Stylo,"Coucou");  
  /* ... */  
  Stylo_Bleu->Liberer(&Stylo);
  
  return;
}

II-C. Les associations

II-C-1. Descriptions

Les associations sont des liens entre deux (ou plus) d'objets.

II-C-2. Multiplicité d'une association (cardinalité)

II-C-2-a. (Zéro, un ou plusieurs) et (Exactement Un)

Exemple :

Image non disponible

La Classe 1 doit ajouter une propriété qui est une référence (un pointeur) sur une instance de la Classe 2 (ce pointeur ne doit pas être NULL).
La Classe 2 doit avoir une liste de références sur des objets de la Classe 1.

II-C-2-b. (Zéro, un ou plusieurs) et (Zéro, un ou plusieurs)

Exemple :

Image non disponible

La Classe 1 doit ajouter une liste de références sur des instances de la Classe 2.
La Classe 2 doit ajouter une liste de références sur des instances de la Classe 1.

II-C-2-c. Zéro ou Un

Dans ce cas, il faut créer une référence sur une instance associée. Cette référence peut ne pas être renseignée (mise à NULL). (Cf. Exactement Un.)

II-C-2-d. Spécifié numériquement

Dans ce cas, comme le nombre d'éléments est connu à l'avance, il faut créer un tableau de références sur les instances associées de la taille spécifiée numériquement.

II-C-3. Ordre d'une association

La notion d'ordre est un facteur qui entre en compte à partir du moment où une association peut comprendre une cardinalité n. Cette information constitue une propriété de la liste ou du tableau : Ordre de classement des informations.

La liste ou le tableau peut être classé par ordre croissant, décroissant, pile ou file.

II-C-4. Association n-aire

Il n'y aura pas d'associations n-aire. Si une association n-aire se dessine, il faut la décomposer en plusieurs associations binaires.

II-C-5. Attributs d'une association

Si une association dispose d'attributs, il faut la transformer en une classe possédant comme propriétés les attributs de l'association et positionner deux associations entre cette nouvelle classe et les deux classes associées. Après quoi, il faut appliquer les règles décrites ci-dessus.

II-C-6. Méthodes obligatoires pour la gestion des associations

Il faut définir les méthodes suivantes :

  • Ajouter_reference_<nom de la classe associée> : ajoute une référence à une liste ;
  • Modifier_reference_<nom de la classe associée> : modifie une référence unique ;
  • Supprimer_reference_<nom de la classe associée> :supprime une référence d'une liste.

Exemple :

Image non disponible

II-C-7. Exemple d'association

II-C-7-a. Description d'une relation père enfant (sans attributs)
Image non disponible
 
Sélectionnez
typedef struct t_Individu
{
  /* méthodes obligatoires */
  
  void (*Liberer)(struct t_Individu **);
  int (*Comparer) (struct t_Individu *,struct t_Individu *);
  void (*Tracer)(struct t_Individu *,e_Trace,char *);
  e_Booleen (*Est_coherente)(struct t_Individu *);
  void (*Interroger)(struct t_Individu *,t_Requete *,t_Reponse *);
  
  /* déclaration des méthodes */
  
  e_Booleen (*Verifie_numero) (struct t_Individu *);
  void (*Modifier)(struct t_Individu *,char *,char *,int);
  int (*Lire_age)(struct t_Individu *);
  void (*Lire_nom_prenom)(struct t_Individu *,int,char *,int,char *);

  /* déclaration des propriétés */
  
  char INSEE[16];
  char Nom[32];
  char Prenom[64];
  int Age;
  
  /* déclaration de l'association */
  
  t_Liste_generique *Ref_enfant;
  struct t_Individu *Pére; /* NULL quand pas de père */
} t_Individu;

extern t_Individu *Instancier_individu(char *,char *,char *,int);

extern t_Individu *Instancier_copie_individu(t_Individu *);
II-C-7-b. Description d'une relation père enfant (avec attributs)
Image non disponible
 
Sélectionnez
typedef struct t_Individu
{
  /* méthodes obligatoires */
  void (*Liberer)(struct t_Individu **);
  int (*Comparer) (struct t_Individu *,struct t_Individu *);
  void (*Tracer)(struct t_Individu *,e_Trace,char *);
  e_Booleen (*Est_coherente)(struct t_Individu *);
  void (*Interroger)(struct t_Individu *,t_Requete *,t_Reponse *);
  
  /* déclaration des méthodes */
  
  e_Booleen (*Verifie_numero) (struct t_Individu *);
  void (*Modifier)(struct t_Individu *,char *,char *,int);
  int (*Lire_age)(struct t_Individu *);
  void (*Lire_nom_prenom)(struct t_Individu *,int,char *,int,char *);
  int (*Demander_pere)(struct t_Individu *,struct t_Individu **);
  
  /* déclaration des propriétés */

  char INSEE[16];
  char Nom[32];
  char Prenom[64];
  int Age;

  /* déclaration de la relation */
  
  t_Liste_générique *Ref_garde;
  void *Garde_par; /* Pointeur sur une instance de t_Garde */
} t_Individu;

extern t_Individu *Instancier_individu(char *,char *,char *,int);

extern t_Individu *Instancier_copie_individu(char *,char *,char *,int);

typedef struct t_Garde
{
  /* méthodes obligatoires */
  void (*Liberer)(struct t_Garde **);

  /* déclaration des liens de la relation */
  t_Individu *Père;
  t_Individu *Enfant;
  
  /* déclaration des propriétés */
  
  e_Booleen Garde;
} t_Garde;

/* constructeur */

extern t_Garde *Instancier_garde (t_Individu *,t_Individu *,e_Booleen i_Garde);

II-D. L'agrégation

L'agrégation sert à définir l'ensemble des composants attachés à une classe.

II-D-1. Exactement Un

Exemple :

Image non disponible

La Classe 1 doit ajouter une propriété qui est une référence (un pointeur) sur une instance de la Classe 2 (le pointeur ne peut pas être NULL).

II-D-2. Zéro, Un ou plus

Exemple :

Image non disponible

La Classe 1 doit ajouter une liste de références sur des instances de la Classe 2 (Liste éventuellement vide).

II-D-3. Zéro ou Un

Dans ce cas, il faut créer une référence sur une instance agrégée. Cette référence peut ne pas être renseignée (le pointeur peut être éventuellement NULL).

II-D-4. Spécifié numériquement

Dans ce cas, comme le nombre d'éléments est connu à l'avance, il faut créer un tableau de références sur les instances agrégées de la taille spécifiée numériquement.

II-D-5. Algorithme de l'instanciation

Lors de l'instanciation d'une classe, utilisant des agrégations, il ne faut pas oublier d'initialiser à NULL les composants si cela est nécessaire ou de les instancier.

II-D-6. Exemple

Image non disponible
 
Sélectionnez
typedef struct t_Feuille
{
  /* ... */
} t_Feuille;

typedef struct t_Branche
{
  /* ... */
  t_Liste_feuille *Feuilles; /* ensemble de feuilles situées sur la branche */
  /* ... */
} t_Branche;

typedef struct t_Arbre
{
  /* ... */
  t_Liste_feuille *Feuilles; /* ensemble des feuilles sur l'arbre */
  t_Branche *Branche;        /* la seule branche de l'arbre */
  /* ... */
} t_Arbre;

II-E. L'héritage

L'héritage est un mécanisme permettant de mettre en commun un ensemble de propriétés et de méthodes liées à des classes plus spécifiques les unes que les autres (ex. : un fonctionnaire hérite des informations d'un individu, un professeur hérite des informations d'un fonctionnaire, donc d'un individu).

Comme nous considérons que les propriétés d'une classe sont des propriétés privées et les méthodes sont protégées, nous devons accéder aux propriétés de la classe mère avec les accesseurs de la classe mère.

II-E-1. L'héritage simple sans opérations abstraites

L'héritage sans opérations abstraites entre la fille et la mère est géré comme une agrégation de cardinalité (1,1). La différence entre une agrégation et un héritage est la nécessité d'instancier la mère en même temps que la fille (c'est-à-dire que lors de l'instanciation d'une fille, il faut instancier obligatoirement une mère : surcharge du constructeur de la mère).

Les méthodes et les propriétés de la classe mère sont considérées privées.

II-E-2. L'héritage multiple

L'héritage multiple sans opérations abstraites entre la fille et les mères est géré comme autant d'agrégations de cardinalité (1,1) entre la fille et les mères. Dans ce cas, il ne faut pas oublier d'instancier automatiquement toutes les mères lorsqu'une fille est instanciée.

II-E-3. Les classes abstraites

Si une classe manipule des méthodes abstraites (prototype défini au niveau de la mère dont le corps doit être écrit spécifiquement pour les classes filles), c'est une classe abstraite. La classe mère est déclarée comme une classe simple et s'utilise comme un héritage simple.

Si la classe abstraite ne contient que des méthodes abstraites, il n'est pas nécessaire d'appliquer une agrégation. Il faut toujours respecter la règle suivante :

Les méthodes de la classe fille doivent être décrites dans le même ordre que les méthodes de la classe mère en tête de la définition de la structure.

II-E-4. Les classes héritières particulières

Il existe des classes héritières qui ne possèdent pas plus de méthodes et d'attributs que la classe mère. Quelques méthodes sont spécifiques, mais dans l'ensemble, l'aspect fonctionnel et technique est le même. Dans ce cas-là, il n'est pas nécessaire de créer les classes filles, il suffit de positionner un indicateur qui spécifie la nature de la classe mère.

Exemple : Soit la classe mère Liste_générique, qui décline en liste générique croissante et décroissante, toutes les fonctions sont quasiment identiques à l'exception des méthodes de rangement. Dans ce cas, il n'est pas nécessaire de décrire les classes liste générique croissante ou décroissante, il suffit de positionner une nature dans la classe mère.

II-E-5. Surcharge des méthodes

La méthode surchargée doit faire appel à la méthode de la mère à surcharger avant d'effectuer le traitement propre à la classe fille.

II-E-6. Remarque

Pour le codage des héritages en C, il ne faut pas accéder directement aux informations de la mère. Il faut utiliser les accesseurs de la mère (Définition des propriétés privées).

II-E-7. Exemple d'héritage d'une classe simple sans opération abstraite

II-E-7-a. Le modèle objet
Image non disponible
II-E-7-b. Dans le header
 
Sélectionnez
#ifndef _individu_h
#define _individu_h

typedef struct t_Individu
{
  void (*Liberer)(struct t_Individu **); /* destructeur */
  char *(*Lire_nom)(struct t_Individu *);
  int (*Lire_age)(struct t_Individu *);

  char Nom[32];
  char Prenom[64];
} t_Individu;

extern t_Individu *Instancier_individu(char *,int);

typedef struct t_Etudiant /* dérive de t_Individu */
{
  void (*Liberer)(struct t_Etudiant **); /* destructeur */
  char *(*Lire_nom)(struct t_Etudiant *); /* méthode surchargée */
  int (*Lire_age)(struct t_Etudiant *); /* méthode surchargée */
  
  char *(*Lire_classe)(struct t_Etudiant *);
  
  t_Individu *Individu;
  char Classe[256];
} t_Etudiant;

extern t_Etudiant *Instancier_etudiant(char *,int,char *);

#endif
II-E-7-c. Dans le fichier source
 
Sélectionnez
#include "individu.h"

void Liberer_individu(t_Individu **this)
{
  AL_FREE(*this);
  *this=(t_Individu *)NULL;
  return;
}

char *Lire_nom_individu(t_Individu *this)
{
  return(this->Nom);
}

int Lire_age_individu(t_Individu *this)
{
  return(this->Prenom);
}

t_Individu *Instancier_individu(char *i_Nom,int i_Age)
{
  t_Individu *Nouveau=(t_Individu *)AL_MALLOC(sizeof(t_Individu));
  
  /* création d'une nouvelle instance */
  if (Nouveau==(t_Individu *)NULL)
    return((t_Individu *)NULL);
  
  /* positionnement des méthodes */
  Nouveau->Liberer=Liberer_individu;
  Nouveau->Lire_nom=Lire_nom_individu;
  Nouveau->Lire_age=Lire_age_individu;
  
  /* positionnement des propriétés */
  (void) strcpy(Nouveau->Nom,i_Nom);
  Nouveau->Age=i_Age;
  return(Nouveau);
}

void Liberer_etudiant(t_Etudiant **this)
{
  if (*this!=(t_Etudiant *)NULL)
  {
    /* surcharge du destructeur de la classe mère */
    (*this)->Individu->Liberer(&(*this)->Individu);
    
    AL_FREE(*this);
  }
  
  *this=(t_Etudiant *)NULL;
  return;
}

char *Lire_nom_etudiant(t_Etudiant *this) /* surcharge de Lire_nom_individu */
{
  return(this->Individu->Lire_nom(this->Individu));
}

int Lire_age_etudiant(t_Etudiant *this) /* surcharge de Lire_age_individu */
{
  return(this->Individu->Lire_age(this->Individu));
}

char *Lire_classe_etudiant(t_Etudiant *this)
{
  return(this->Classe);
}

t_Etudiant *Instancier_etudiant(char *i_Nom,int i_Age,char *i_Classe)
{
  t_Etudiant *Nouveau=(t_Etudiant *)AL_MALLOC(sizeof(t_Etudiant));
  if (Nouveau==(t_Etudiant *)NULL)
  {
    return((t_Etudiant *)NULL);
  }

  /* instanciation de la classe mère */
  Nouveau->Individu=Instancier_individu(i_Nom,i_Age);
  if (Nouveau->Individu==(t_Individu *)NULL)
  {
    AL_FREE(Nouveau);
    return((t_Etudiant *)NULL);
  }

  /* positionnement des méthodes de la classe fille */
  Nouveau->Lire_nom=Lire_nom_etudiant;
  Nouveau->Lire_age=Lire_age_etudiant;
  Nouveau->Lire_classe=Lire_classe_etudiant;
  Nouveau->Liberer=Liberer_etudiant;
  
  /* positionnement des propriétés de la classe fille */
  (void) strcpy(Nouveau->Classe,i_Classe);
  
  return(Nouveau);
}
II-E-7-d. Exemple d'utilisation
 
Sélectionnez
#include "individu.h"
void main(void)
{
  t_Individu *Eleve=(t_Individu *) Instancier_etudiant("DUPONT Jean",10,"DEUG");
  if (Eleve==(t_Individu *)NULL)
  {
    printf("Erreur d'allocation mémoire ...\n");
    return;
  }
  
  printf("Nom : %s\n",Eleve->Lire_nom(Eleve));
  printf("Age : %d\n",Eleve->Lire_age(Eleve));
  
  /* je veux accéder à la méthode Lire_classe de l'étudiant */
  /* ATTENTION : ceci peut être réalisé, car je sais que c'est un étudiant */
  
  printf("Classe : %s\n",((t_Etudiant *)Eleve)->Lire_classe(Eleve));
  
  Eleve->Liberer(&Eleve);

  return;
}

II-E-8. Exemple d'une expression booléenne

II-E-8-a. Modèle objet
Image non disponible
II-E-8-b. Description du header
 
Sélectionnez
#ifndef _expression_h
#define _expression_h

typedef enum { OUI=1, NON=0, VRAI=1, FAUX=0, TRUE=1, FALSE=0 } e_Booleen;
typedef enum { ET, OU } e_Operateur_binaire;
typedef enum { NON } e_Operateur_unaire;

typedef struct t_Expression_booleenne
{
  /* méthode abstraite */
  /* ----------------- */
  void (*Liberer)(struct t_Expression_booleenne **);
  e_Booleen (*Evaluer)(struct t_Expression_booleenne *);
  
} t_Expression_booleenne;

typedef struct t_Constante
{
  /* méthode abstraite */
  /* ----------------- */
  void (*Liberer)(struct t_Constante **);
  e_Booleen (*Evaluer)(struct t_Constante *);
  
  /* propriété de la classe */
  /* ---------------------- */
  int Valeur;
} t_Constante;

extern t_Constante *Instancier_constante(int Valeur);

typedef struct t_Unaire
{
  /* méthode abstraite */
  /* ----------------- */
  void (*Liberer)(struct t_Unaire **);
  e_Booleen (*Evaluer)(struct t_Unaire *);
  
  /* propriété */
  /* --------- */
  e_Operateur_unaire Operateur;
  t_Expression_booleenne *Membre;
} t_Unaire;

extern t_Unaire *Instancier_unaire(char Operateur,t_Expression_booleenne *Membre);

typedef struct t_Binaire
{
  /* méthode abstraite */
  /* ----------------- */
  void (*Liberer)(struct t_Binaire **);
  e_Booleen (*Evaluer)(struct t_Binaire *);
  
  /* propriété */
  /* --------- */
  e_Operateur_binaire Operateur;
  t_Expression_booleenne *Gauche;
  t_Expression_booleenne *Droite;
} t_Binaire;

extern t_Binaire *Instancier_binaire(char Operateur, t_Expression_booleenne *Gauche, t_Expression_booleenne *Droite);

#endif
II-E-8-c. La description du fichier source C
 
Sélectionnez
#include <stdio.h>
#include "expression.h"

e_Booleen Evaluer_constante(t_Constante *this)
{
  return(this->Valeur);
}

void Liberer_constante(t_Constante **this)
{
  /* libération de l'instance */
  AL_FREE(*this);
  
  /* positionnement de l'instance a NULL */
  
  *this=(t_Constante *)NULL;
  return;
}

t_Constante *Instancier_constante(int Valeur)
{
  /* allocation */
  t_Constante *Nouveau=(t_Constante *)AL_MALLOC(sizeof(t_Constante));
  if (Nouveau==(t_Constante *)NULL)
    return((t_Constante *)NULL);
  
  /* positionnement de méthodes */
  Nouveau->Evaluer=Evaluer_Constante;
  Nouveau->Liberer=Liberer_Constante;
  
  /* positionnement de la valeur */
  
  Nouveau->Valeur=Valeur;
  
  return(Nouveau);
}

e_Booleen Evaluer_unaire(t_Unaire *this)
{
  if(this->Operateur==NON)
  {
    return(!this->Membre->Evaluer(this->Membre));
  }
  return(0);
}

void Liberer_unaire(t_Unaire **this)
{
  (*this)->Membre->Liberer(&(*this)->Membre);
  
  AL_FREE(*this);
  *this = (t_Unaire *)NULL;
  return;
}

t_Unaire *Instancier_unaire(e_Operateur_unaire Operateur,
t_Expression_booleenne *Membre)
{
  /* allocation */
  
  t_Unaire *Nouveau=(t_Unaire *)AL_MALLOC(sizeof(t_Unaire));
  if (Nouveau==(t_Unaire *)NULL)
  return((t_Unaire *)NULL);
  
  /* positionnement de méthodes */
  
  Nouveau->Evaluer=Evaluer_Unaire;
  Nouveau->Liberer=Liberer_Unaire;
  
  /* positionnement de la valeur */
  
  Nouveau->Operateur=Operateur;
  Nouveau->Membre=Membre;
  
  return(Nouveau);
}

e_Booleen Evaluer_binaire(t_Binaire *this)
{
  e_Booleen Valeur=FAUX;
  
  switch(this->Operateur)
  {
    case ET : Valeur=this->Gauche->Evaluer(this->Gauche)&&this->Droite->Evaluer(this->Droite);
      break;
    case OU : Valeur=this->Gauche->Evaluer(this->Gauche)||this->Droite->Evaluer(this->Droite);
      break;
  }
  
  return(Valeur);
}

void Liberer_binaire(t_Binaire **this)
{
  (*this)->Gauche->Liberer(&(*this)->Gauche);
  (*this)->Droite->Liberer(&(*this)->Droite);
  
  AL_FREE(*this);
  
  *this=(t_Binaire *)NULL;
  return;
}

t_Binaire *Instancier_binaire(e_Operateur_binaire Operateur, t_Expression_booleenne *Gauche, t_Expression_booleenne *Droite)
{
  /* allocation */
  t_Binaire *Nouveau=(t_Binaire *)AL_MALLOC(sizeof(t_Binaire));
  if (Nouveau==(t_Binaire *)NULL)
    return((t_Binaire *)NULL);
    
  /* positionnement de méthodes */
  
  Nouveau->Evaluer=Evaluer_binaire;
  Nouveau->Liberer=Liberer_binaire;
  
  /* positionnement de la valeur */
  
  Nouveau->Operateur=Operateur;
  Nouveau->Gauche=Gauche;
  Nouveau->Droite=Droite;
  
  return(Nouveau);
}
II-E-8-d. Un programme d'exemple
 
Sélectionnez
#include <stdio.h>
#include "expression.h"
void main()
{
  e_Booleen Valeur;
  t_Expression_booleenne *Expr=(t_Expression_booleenne *)Instancier_binaire(ET,
                   (t_Expression_booleenne *)Instancier_unaire(NON,
                   (t_Expression_booleenne *)Instancier_constante(1)),
                   (t_Expression_booleenne *)Instancier_constante(0));
                   
  Valeur = Expr->Evaluer(Expr);
  
  printf("Expr = %s\n",Valeur?"VRAI":"FAUX");
  
  Expr->Liberer(&Expr);
  return;
}

II-F. Les patrons ou les modèles

II-F-1. Description d'un patron

Un patron est une classe générique offrant un ensemble de méthodes communes, quel que soit le type de l'objet manipulé.

Exemple : une liste générique.

Les fonctionnalités (de la gestion d'une liste de produits ou d'individus seront les mêmes (Ajout, Suppression, Parcours…).

Un patron gérera un objet comme étant une référence sur un objet quelconque (void *). Pour manipuler cet objet, il faudra définir un certain nombre de méthodes propres à l'objet défini lors de l'instanciation d'un patron. Le patron n'est donc pas responsable de l'implémentation de ces méthodes, seule la classe de l'objet doit définir ces méthodes. En d'autres termes, ce sont un ensemble de classes partageant un même ensemble de méthodes.

Exemple : Une liste croissante générique.

Cet exemple manipule n'importe quel objet, mais le patron doit savoir comment sont classés les objets. Il a donc besoin d'une fonction de comparaison entre deux objets de liste la pour les classer.

II-F-2. Exemple d'un patron : Liste_Générique_Croissante

 
Sélectionnez
typedef struct t_Liste_generique_croissante
{
  /* destructeur */
  void (*Liberer)(struct t_Liste_generique_croissante **);
  
  /* méthodes du patrons */
  
  void (*Ajouter)(struct t_Liste_generique_croissante *,void *);
  void (*Supprimer)(struct t_Liste_generique_croissante *,void *);
  void *(*Rechercher)(struct t_Liste_generique_croissante *,void *);
  
  /* méthode propre à l'objet à généraliser */
  
  int (*Comparer)(void *,void *);
  
  /* propriétés du patron */
  
  int Nb_Elements;
  void **Liste_element;
  
} t_Liste_générique_croissante;

extern t_Liste_generique_croissante *Instancier_Liste_generique_croissante(int (*Comparer)(void *,void *));

II-G. Les classes interfaces

II-G-1. Définition

Une classe interface est une classe dont seules les méthodes sont visibles de l'extérieure par les classes qui les utilisent.

II-G-2. Codage d'une classe interface

II-G-2-a. Dans le header

Dans le header de la classe interface, doit apparaître uniquement la description de cette dernière avec les méthodes. Les différentes instanciations possibles doivent y figurer aussi.

II-G-2-b. Dans le fichier source

Dans le fichier source de la classe interface, doit figurer la description de toutes les classes implémentant la classe interface. Ces nouvelles classes peuvent disposer de méthodes supplémentaires et/ou de propriétés.

II-G-3. Exemple d'une classe interface

II-G-3-a. Description
Image non disponible
II-G-3-b. Dans le header
 
Sélectionnez
#ifndef _compteur_h
#define _compteur_h

typedef struct t_Compteur
{
  void (*Liberer)(struct t_Compteur **);
  void (*Initialiser)(struct t_Compteur *);
  void (*Incrementer)(struct t_Compteur *);
  void (*Decrementer)(struct t_Compteur *);
  int (*Lire_valeur)(struct t_Compteur *);
} t_Compteur;

extern t_Compteur *Instancier_compteur_sequentiel(int Mini, int Maxi);
extern t_Compteur *Instancier_compteur_circulaire(int Mini,int Maxi);

#endif
II-G-3-c. Dans le fichier source
 
Sélectionnez
#include <stdio.h>
#include "compteur.h"

typedef struct t_Compteur_sequentiel
{
  void (*Liberer)(struct t_Compteur_sequentiel **);
  void (*Initialiser)(struct t_Compteur_sequentiel *);
  void (*Incrementer)(struct t_Compteur_sequentiel *);
  void (*Decrementer)(struct t_Compteur_sequentiel *);
  int (*Lire_valeur)(struct t_Compteur_sequentiel *);

  int Compteur_courant;
  int Mini,Maxi;
} t_Compteur_sequentiel;

void Initialiser_sequentiel(t_Compteur_sequentiel *this)
{
  this->Compteur_courant = this->Mini;
  return;
}

void Incrementer_sequentiel(t_Compteur_sequentiel *this)
{
  if (this->Compteur_courant != this->Maxi)
    this->Compteur_courant ++;
  return;
}

void Decrementer_sequentiel(t_Compteur_sequentiel *this)
{
  if (this->Compteur_courant != this->Mini)
    this->Compteur_courant --;
  return;
}

int Lire_valeur_compteur_sequentiel(t_Compteur_sequentiel *this)
{
  return(this->Compteur_courant);
}

void Liberer_compteur_sequentiel(t_Compteur_sequentiel **this)
{
  AL_FREE(*this);
  *this = (t_Compteur_sequentiel *)NULL;
  return;
}

t_Compteur *Instancier_compteur_sequentiel(int Mini,int Maxi)
{
  t_Compteur_sequentiel *Nouveau=(t_Compteur_sequentiel *) AL_MALLOC(sizeof(t_Compteur_sequentiel));
  
  if (Nouveau == (t_Compteur_sequentiel *)NULL)
    return((t_Compteur *)NULL);
    
  Nouveau->Initialiser=Initialiser_compteur_sequentiel;
  Nouveau->Incrementer=Incrementer_compteur_sequentiel;
  Nouveau->Decrementer=Decrementer_compteur_sequentiel;
  Nouveau->Lire_valeur=Lire_valeur_compteur_sequentiel;
  Nouveau->Liberer=Liberer_compteur_sequentiel;
  
  if (Mini < Maxi)
  {
    Nouveau->Mini = Mini;
    Nouveau->Maxi = Maxi;
  }
  else
  {
    Nouveau->Mini = Maxi;
    Nouveau->Maxi = Mini;
  }
  
  return((t_Compteur *)Nouveau);
}

typedef struct t_Compteur_circulaire
{
  void (*Liberer)(struct t_Compteur_circulaire **);
  void (*Initialiser)(struct t_Compteur_circulaire *);
  void (*Incrementer)(struct t_Compteur_circulaire *);
  void (*Decrementer)(struct t_Compteur_circulaire *);
  int (*Lire_valeur)(struct t_Compteur_circulaire *);
  
  int Compteur_courant;
  int Mini,Maxi;
} t_Compteur_circulaire;

void Initialiser_circulaire(t_Compteur_circulaire *this)
{
  this->Compteur_courant = this->Mini;
  return;
}

void Incrementer_circulaire(t_Compteur_circulaire *this)
{
  if (this->Compteur_courant != this->Maxi)
    this->Compteur_courant ++;
  else
    this->Compteur_courant = this->Mini;
  return;
}

void Decrementer_circulaire(t_Compteur_circulaire *this)
{
  if (this->Compteur_courant != this->Mini)
    this->Compteur_courant --;
  else
    this->Compteur_courant = this->Maxi;
  return;
}

int Lire_valeur_compteur_circulaire(t_Compteur_circulaire *this)
{
  return(this->Compteur_courant);
}

void Liberer_compteur_circulaire(t_Compteur_circulaire **this)
{
  AL_FREE(*this);
  *this = (t_Compteur_circulaire *)NULL;
  return;
}

t_Compteur *Instancier_compteur_circulaire(int Mini,int Maxi)
{
  t_Compteur_circulaire *Nouveau=(t_Compteur_circulaire *) AL_MALLOC(sizeof(t_Compteur_circulaire));

  if (Nouveau == (t_Compteur_circulaire *)NULL)
    return((t_Compteur *)NULL);
  
  Nouveau->Initialiser=Initialiser_compteur_circulaire;
  Nouveau->Incrementer=Incrementer_compteur_circulaire;
  Nouveau->Decrementer=Decrementer_compteur_circulaire;
  Nouveau->Lire_valeur=Lire_valeur_compteur_circulaire;
  Nouveau->Liberer=Liberer_compteur_circulaire;

  if (Mini < Maxi)
  {
    Nouveau->Mini = Mini;
    Nouveau->Maxi = Maxi;
  }
  else
  {
    Nouveau->Mini = Maxi;
    Nouveau->Maxi = Mini;
  }
  
  return((t_Compteur *)Nouveau);
}

II-H. Ordre de déclaration des éléments d'un type

Dans la structure permettant de modéliser une classe, il faut définir :

  1. Le destructeur ;
  2. Les méthodes abstraites (publiques) ;
  3. Les méthodes surchargées (protégées) ;
  4. Les méthodes propres à la classe (privées) ;
  5. Les propriétés ;
  6. Les agrégations ;
  7. Les associations.

II-I. La gestion des erreurs (mécanisme d'exceptions)

Pour gérer les erreurs survenant dans une classe, un peu comme des exceptions, il faut ajouter une propriété : Erreur de type e_Erreur. Ce type énuméré recense toutes les exceptions utilisées dans cette bibliothèque. Cette propriété doit alors être mise à jour dans toutes les méthodes ou accesseurs (sauf Lire_erreur) pour savoir si le dernier appel est responsable d'une anomalie ou pas.

Pour accéder à cette propriété, il faut alors ajouter un accesseur : Lire_erreur.


précédentsommaire

Copyright © 2003 Aymeric Lesert. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.