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

Aymeric Lesert

Ce document décrit une approche du C qui n'est pas très académique. En effet, dès que l'on parle de langages objets, on se tourne vers le Java ou le C++. Le C devient ringard et peu adapté à ses concepts. Or, il est parfaitement possible d'adapter le C pour développer selon les concepts Objets.

Ce document décrit un standard de programmation (permet de réaliser un développement homogène), puis la partie suivante décrit les règles de passage d'un modèle objet (comme UML) à un développement en C.

Votre avis et vos suggestions sur cet article nous intéressent !

Alors après votre lecture, n'hésitez pas : Commentez ♪

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

- Auteur : Aymeric Lesert

- Parution : 1 janvier 2003

- Licence : Copyright ® 2003 Aymeric Lesert. Aucune reproduction, même partielle, ne peut être faite de ce site et 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.

Sommaire

- I. Règles de programmation

- I-A. Règle de nommage

- I-A-1. Les fichiers sources

- I-A-2. Les noms des variables

- I-A-2-a. Les variables locales

- I-A-2-b. Les variables globales

- I-A-2-c. Les noms de variables interdites

- I-A-3. Les noms des constantes

- I-A-4. Les macro-commandes

- I-A-5. Les noms d'une fonction et d'une procédure

- I-A-5-a. Une action

- I-A-5-b. Une vérification

- I-A-5-c. Les noms des paramètres

- I-A-5-d. Les procédures et les fonctions sans aucun paramètre

- I-A-6. Les index de boucles

- I-A-7. Les noms des types

- I-A-7-a. Les types structurés

- I-A-7-b. Les types énumérés

- I-A-7-c. Les types unions

- I-A-8. Les abréviations

- I-B. Présentation du code

- I-B-1. Règles générales

- I-B-2. Description d'un header

- I-B-2-a. Un entête

- I-B-2-b. L'encadrement

- I-B-2-c. Les inclusions

- I-B-2-d. Les constantes

- I-B-2-e. Les types

- I-B-2-f. Les fonctions et les procédures utilisables

- I-B-2-g. Exemple de header

- I-B-3. Description d'un fichier source C

- I-B-3-a. L'entête du corps

- I-B-3-b. L'inclusion

- I-B-3-c. Description des types internes au module

- I-B-3-d. Les fonctions et les procédures internes au module (non accessible par d'autres modules)

- I-B-3-e. Les fonctions et les procédures d'accès au module

- I-B-4. Déclarations des variables

- I-B-5. Les fonctions et les procédures

- I-B-5-a. Ordre d'écriture ou de déclaration des fonctions et des procédures

- I-B-5-b. Les fonctions et les procédures utilisables

- I-B-5-c. Les fonctions et les procédures internes au fichier source

- I-C. Exploitation de la présentation du code

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

- II-A. Objectif

- II-B. Les classes

- II-B-1. Propriétés

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

- II-B-3. Méthodes obligatoires

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

- II-B-3-b. Le destructeur

- II-B-3-c. Les accesseurs

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

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

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

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

- II-B-8. Exemple simple

- II-B-8-a. Définition du type en C dans le header

- II-B-8-b. Définition de la classe en C dans le fichier source C

- II-B-8-c. Utilisation en C

- II-C. Les associations

- II-C-1. Descriptions

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

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

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

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

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

- II-C-3. Ordre d'une association

- II-C-4. Association n-aire

- II-C-5. Attributs d'une association

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

- II-C-7. Exemple d'association

- II-C-7-a. Description d'une relation père enfant (sans attributs)

- II-C-7-b. Description d'une relation père enfant (avec attributs)

- II-D. L'agrégation

- II-D-1. Exactement Un

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

- II-D-3. Zéro ou Un

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

- II-D-5. Algorithme de l'instanciation

- II-D-6. Exemple

- II-E. L'héritage

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

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

- II-E-3. Les classes abstraites

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

- II-E-5. Surcharge des méthodes

- II-E-6. Remarque

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

- II-E-7-a. Le modèle objet

- II-E-7-b. Dans le header

- II-E-7-c. Dans le fichier source

- II-E-7-d. Exemple d'utilisation

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

- II-E-8-a. Modèle objet

- II-E-8-b. Description du header

- II-E-8-c. La description du fichier source C

- II-E-8-d. Un programme d'exemple

- II-F. Les patrons ou les modèles

- II-F-1. Description d'un patron

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

- II-G. Les classes interfaces

- II-G-1. Définition

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

- II-G-2-a. Dans le header

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

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

- II-G-3-a. Description

- II-G-3-b. Dans le header

- II-G-3-c. Dans le fichier source

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

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

I - Règles de programmation

I-A - Règle de nommage

I-A-1 - Les fichiers sources

Le nom d'un fichier source doit être significatif du contenu.

I-A-2 - Les noms des variables

I-A-2-a - Les variables locales

Le nom d'une variable doit définir son contenu.
La première lettre doit être en majuscule et les autres en minuscule.
Tous les mots composants une variable sont séparés par « _ ».

Exemple : Nom_voiture, Nom_magasin, Nb_contraintes…

I-A-2-b - Les variables globales

Il est interdit d'avoir des variables globales.

I-A-2-c - Les noms de variables interdites

Les variables nommées 'temp', 'prov'… sont à proscrire ainsi que les variables proches des mots clés comme 'For', 'Socket', 'New'

I-A-3 - Les noms des constantes

Le nom d'une constante doit définir son contenu.
Toutes les lettres sont en MAJUSCULES.
Tous les mots composants une constante sont séparés par « _ ».

Exemple : TAILLE_MAXI…

I-A-4 - Les macro-commandes

Le nom d'une macro -commande est en MAJUSCULES.
Tous les mots composants une macro-commande sont séparés par « _ ».
Elles permettent de simplifier une séquence de traitement.

Exemples :
EST_NUMERIQUE(a) ((« 0 »<=a)&&(a<=« 9 »)) EST_CORRECTE(Valeur) (Valeur!=(t_Valeur *)NULL) EXISTE(Valeur) (Valeur!=(t_Valeur *)NULL)

I-A-5 - Les noms d'une fonction et d'une procédure

I-A-5-a - Une action

Le nom d'une fonction ou d'une procédure correspondant à une action doit commencer par un verbe à l'infinitif.
La première lettre est en majuscule.
Toutes les autres lettres sont en minuscules.
Tous les mots composant le nom d'une fonction ou d'une procédure sont séparés par « _ ».

Exemple : Traiter_demande()

I-A-5-b - Une vérification

Le nom d'une fonction ou d'une procédure correspondant à une action doit commencer par un verbe conjugué au présent.
La première lettre est en majuscule.
Toutes les autres lettres sont en minuscules.
Tous les mots composants le nom d'une fonction ou d'une procédure sont séparés par « _ ».

Exemple : Est_entier()

I-A-5-c - Les noms des paramètres

Les noms des paramètres respectent la règle des noms de variables décrite ci-dessus.
Ils sont cependant préfixés par :

- « i_ » pour un paramètre d'entrée ;

- « o_ » pour un paramètre de sortie ;

- « io_ » pour un paramètre d'entrée-sortie ;

Exemple : i_Indice, io_Information, o_Resultat…

I-A-5-d - Les procédures et les fonctions sans aucun paramètre

Ces procédures et ces fonctions doivent être suffixées par (void).

Exemple : void Ma_procedure(void);

I-A-6 - Les index de boucles

Nous autorisons l'utilisation des variables i, j et k pour les index de boucles.

I-A-7 - Les noms des types

I-A-7-a - Les types structurés

Les noms des types structurés respectent la règle des noms de variables décrite ci-dessus. Ils sont préfixés par « t_ ».

Exemple : t_Vehicule

I-A-7-b - Les types énumérés

Les noms des types énumérés respectent la règle des noms de variables décrite ci- dessus. Ils sont préfixés par « e_ ».
Les valeurs du type énuméré sont écrites comme les constantes.
Il faut utiliser ces types aussi souvent que possible.

Exemple : enum { ROUGE, VERT, BLEU } e_Couleur

I-A-7-c - Les types unions

Les types unions ne doivent pas être utilisés, cela nuit à la lisibilité du code.

I-A-8 - Les abréviations

Elles sont tolérées dans la limite des abréviations communes.

Exemple : Nb : Nombre , Cpt : Compteur, Maxi : Maximum, Ref : Reference, Mini : Minimum…

I-B - Présentation du code

I-B-1 - Règles générales

Les différentes parties d'une fonction ou d'une procédure sont indiquées par des commentaires explicatifs accompagnés éventuellement d'un algorithme permettant de préciser le fonctionnement.
Les conditions ou les itérations sont décrites dans un commentaire la précédant sauf si le code est suffisamment explicite.
L'indentation doit être d'une tabulation (correspondant à deux espaces).
Les accolades ouvrantes et fermantes respectives doivent être positionnées à la même colonne (l'une en dessous de l'autre sur la même colonne).
Il faut caster les objets ayant des types différents pour les comparer ou les affecter (ex. : comparaison d'un entier avec un réel, affectation du résultat d'une allocation ou comparaison d'un pointeur avec NULL).
Ne pas effectuer d'affectations dans une condition.
Ne pas utiliser les instructions 'break'(sauf dans un switch) et 'continue'.

I-B-2 - Description d'un header

I-B-2-a - Un entête

L'entête d'un header est une zone de commentaire permettant de décrire brièvement le contenu du module ainsi que les différentes évolutions subies par le module, par qui et quand.

Exemple :

/* ------------------------------------------------------------------------- */
/*                     Gestion de la classe t_Information                    */
/* ------------------------------------------------------------------------- */
/* OBJET : Structure de donnees dynamiques permettant de gerer un ensemble   */
/*         d'informations (Cle, Valeur)                                      */
/* ------------------------------------------------------------------------- */
/*     DATE     |   AUTEUR    |             COMMENTAIRES                     */
/* ------------------------------------------------------------------------- */
/*  08/11/1999  |     A.L     | Creation des fichiers                        */
/*  01/12/1999  |     A.L     | Creation de la classe t_Information          */
/*  04/01/2000  |     A.L     | Mise a disposition des outils                */
/*  17/03/2000  |     E.P     | Methodes de lecture ensemble infos           */
/*  09/08/2001  |     A.L     | Extraction, Ajout, Suppression d'une partie  */
/* ------------------------------------------------------------------------- */
/* CARACTERISTIQUE TECHNIQUE DU MODULE :                                     */
/*                                                                           */
/* LIMITES : Une ligne dans un fichier texte a lire ou a ecrire ne peut      */
/*           exceder 512 octets (ATTENTION : Effet de bord possible)         */
/* ------------------------------------------------------------------------- */

I-B-2-b - L'encadrement

Le header est encadré par une directive qui teste si une macro est déjà définie ou pas. Cet encadrement a pour rôle de ne pas avoir de multiples inclusions d'un header.

Exemple :

#IFNDEF _al_<nom fichier>_h
#DEFINE _al_<nom fichier>_h
/* ...
contenu du header
... */
#ENDIF

I-B-2-c - Les inclusions

Cette zone contient l'ensemble des inclusions nécessaires aux fonctionnements du module.

Exemple :

#include <stdio.h>
#include <stdlib.h>

I-B-2-d - Les constantes

Les constantes nécessaires aux types sont définies par DEFINE dans cette partie.

Exemple :

#define TAILLE_MAXI 10

I-B-2-e - Les types

Cette zone contient la description de tous les types utilisables.

Exemple :

typedef struct t_Pays
{
char Nom[TAILLE_MAXI+1];
long Nb_habitants;
} t_Pays;

I-B-2-f - Les fonctions et les procédures utilisables

Le header contient les prototypes des différentes fonctions et procédures d'accès au module. Ce sont les outils manipulés par les autres modules. Le header ne contient que les prototypes d'accès au module. La description de son utilisation, la liste des paramètres en entrées et/ou en sortie ainsi que le résultat du traitement doivent figurer dans le fichier source C.

Il faut précéder le prototype de la fonction par la classe « extern ».

Exemple :

extern int Est_entier(char *i_Chaine);

I-B-2-g - Exemple de header

/* ------------------------------------------------------------------------- */
/*                     Gestion de la classe t_Information                    */
/* ------------------------------------------------------------------------- */
/* OBJET : Structure de donnees dynamiques permettant de gerer un ensemble   */
/*         d'informations (Cle, Valeur)                                      */
/* ------------------------------------------------------------------------- */
/*     DATE     |   AUTEUR    |             COMMENTAIRES                     */
/* ------------------------------------------------------------------------- */
/*  09/08/2001  |     A.L     | Extraction, Ajout, Suppression d'une partie  */
/* ------------------------------------------------------------------------- */
/* CARACTERISTIQUE TECHNIQUE DU MODULE :                                     */
/*                                                                           */
/* LIMITES : Une ligne dans un fichier texte a lire ou a ecrire ne peut      */
/*           exceder 512 octets (ATTENTION : Effet de bord possible)         */
/* ------------------------------------------------------------------------- */

#include <stdio.h>
#include <stdlib.h>
#include "al_journal.h"

/* --------------------------------- */
/* definition des messages d'erreurs */
/* --------------------------------- */

#define INF0001 "INF0001: Erreur d'allocation memoire"

/* ... */

/*--- DEFINITION DES CONSTANTES ---*/

#DEFINE LONGUEUR_IDENTIFIANT 10

/*--- DEFINITION DES MACROS COMMANDES ---*/

#DEFINE EST_NUMERIQUE(a) (('0'<=a)&&(a<='9'))

/*--- DEFINITION DES STRUCTURES ---*/

typedef struct t_Information
{
char Identifiant[LONGUEUR_IDENTIFIANT+1];
int Valeur;
} t_Information;

typedef t_Liste_generique t_Liste_information;

/*--- DEFINITION DES PROTOTYPES UTILISABLES ---*/

extern int Est_message(char *i_Chaine);

#ENDIF

I-B-3 - Description d'un fichier source C

I-B-3-a - L'entête du corps

L'entête d'un corps est une zone de commentaire permettant de décrire brièvement le contenu du module et les remarques techniques ainsi que les différentes évolutions subies par le module, par qui et quand.

Exemple :

/* ------------------------------------------------------------------------- */
/*                     Gestion de la classe t_Information                    */
/* ------------------------------------------------------------------------- */
/* OBJET : Structure de donnees dynamiques permettant de gerer un ensemble   */
/*         d'informations (Cle, Valeur)                                      */
/* ------------------------------------------------------------------------- */
/*     DATE     |   AUTEUR    |             COMMENTAIRES                     */
/* ------------------------------------------------------------------------- */
/*  08/11/1999  |     A.L     | Creation des fichiers                        */
/*  01/12/1999  |     A.L     | Definition des methodes et de la classe      */
/*              |             |                                     t_Info   */
/*  01/12/1999  |     A.L     | Creation de la classe t_Information          */
/*  04/01/2000  |     A.L     | Mise a disposition des outils                */
/*  23/03/2000  |     A.L     | Remplacement de la classe t_Info par une     */
/*              |             |     structure simplifiee                     */
/*  09/08/2001  |     A.L     | Extraction, Ajout, Suppression d'une partie  */
/* ------------------------------------------------------------------------- */

I-B-3-b - L'inclusion

Le module doit inclure le header qui le caractérise.

Exemple :

#include "al_outils.h"

I-B-3-c - Description des types internes au module

Les types communs sont décrits dans le header.
Les types propres au fichier source C sont décrits ici. Ces types ne seront exploitables que par le fichier source.

I-B-3-d - Les fonctions et les procédures internes au module (non accessible par d'autres modules)

Cette partie comprend le codage des fonctions et des procédures propres au module.
Elle doit comprendre un entête (description des fonctionnalités) qui précise la portée de la fonction ou procédure et un corps (codage C).

Les prototypes doivent être précédés de la classe 'static'.

Exemple :

/* -------------------------------------------------------------------- */
/* DESCRIPTION : Permet de déterminer si la chaine est un entier ou pas */
/* -------------------------------------------------------------------- */
/* ENTREES : Chaine : chaine de caractères à analyser                   */
/* -------------------------------------------------------------------- */
/* SORTIES : Valeur : conversion de la Chaine                           */
/* -------------------------------------------------------------------- */
/* RETOUR : 0 : Chaine n'est pas un entier, Valeur n'est pas modifiée   */
/*          1 : Chaine est un entier, Valeur contient la conversion     */
/* -------------------------------------------------------------------- */

static int Est_entier(char *i_Chaine,int *o_Valeur)
{
/* ... */
}

I-B-3-e - Les fonctions et les procédures d'accès au module

Cette partie comprend le codage des fonctions et des procédures, dont les prototypes sont décrits dans le header. Les prototypes ne sont précédés d'aucune classe d'appartenance.

Exemple :

/* -------------------------------------------------------------------- */
/* DESCRIPTION : Permet de déterminer si la chaine est un message       */
/* -------------------------------------------------------------------- */
/* ENTREES : Chaine : chaine de caractères à analyser                   */
/* -------------------------------------------------------------------- */
/* RETOUR : 0 : Chaine n'est pas un message                             */
/*          1 : Chaine est un message                                   */
/* -------------------------------------------------------------------- */

int Est_message(char *i_Chaine)
{
/* ... */
}

I-B-4 - Déclarations des variables

Une variable doit être initialisée dés sa déclaration. Si elle doit prendre des valeurs particulières pour indiquer un sens, ces valeurs doivent être décrites dans un commentaire à côté. Dans ce cas, si le nombre de valeurs pouvant être prises est dénombrable, il est préférable d'utiliser un type énuméré.

Exemple :

int i = 0 ;
t_Vehicule Vehicule = (t_Vehicule *)NULL;/* NULL: pas de véhicules */
t_Individu *Individu=Instancier_individu("LESERT Aymeric") ;

I-B-5 - Les fonctions et les procédures

I-B-5-a - Ordre d'écriture ou de déclaration des fonctions et des procédures

Il faut TOUJOURS spécifier la fonction avant de l'utiliser.
REMARQUE : Dans le cas de récursivité croisée, il faudra déclarer le prototype de la fonction appelée avant la fonction appelante.

I-B-5-b - Les fonctions et les procédures utilisables

Elles comportent quatre parties :

- déclaration et initialisation des variables ;

- vérifie la cohérence des données (programmation défensive) ;

- traite les données ;

- retourne les résultats.

La partie 3 ne doit pas effectuer de « return ».

Exemple :

/* -------------------------------------------------------------------- */
/* DESCRIPTION : Permet de déterminer si la chaine est un message       */
/* -------------------------------------------------------------------- */
/* ENTREES : Chaine : chaine de caractères à analyser                   */
/* -------------------------------------------------------------------- */
/* RETOUR : 0 : Chaine n'est pas un message                             */
/*          1 : Chaine est un message                                   */
/* -------------------------------------------------------------------- */

int Est_message(char *i_Chaine)
{
/* déclaration et initialisation des variables */

int Longueur = 0 ;
int Nb_informations = 0 ;

/* contrôle de la cohérence des données */

Longueur = strlen(i_Chaine) ;
if ((0>Longueur)||(Longueur>200))
return(-1);

/* traitement */

/* ... */

/* retour */
return (Nb_informations) ;
}

I-B-5-c - Les fonctions et les procédures internes au fichier source

Elles comportent trois ou quatre parties :

- déclaration et initialisation des variables ;

- vérifie la cohérence des données (programmation défensive) ;

- traite les données ;

- retourne les résultats.

La partie 3 ne doit pas effectuer de « return ».

I-C - Exploitation de la présentation du code

Grâce à la normalisation décrite ci-dessus, il est concevable de mettre en place un outil permettant de réaliser une documentation on-line (au format HTML afin de les consulter avec un browser).

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

II-B-8-a - Définition du type en C dans le header

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

/* --- 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

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 :

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 :

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 :

II-C-7 - Exemple d'association

II-C-7-a - Description d'une relation père enfant (sans attributs)

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)

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 :

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 :

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

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

II-E-7-b - Dans le header

#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

#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

#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

II-E-8-b - Description du header

#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

#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

#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

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

II-G-3-b - Dans le header

#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

#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.