Introduction à la programmation informatique

Bernard Amade

Chapitre 7. Les types composites §§

Les langages de programmation permettent aux programmeurs de définir leurs propres types.

Ce sont des ensembles de données auquel le programmeur va attribuer des propriétés diverses (par ex. limites, contraintes de cohérence, etc.) et pour lesquelles ils fournira des codes de service sachant manipuler ces données.

7.1. Agrégats §§

Un "agrégat" est une donnée composée de plusieurs données.

Prenons un exemple en langage C:

// définition d'un type composite
struct aeroport {
  // un tableau de 3 caractères
  char codeAITA[3] ;
  // un nombre flottant
  float longitude ;
  float latitude ;
}

On pourra alors créer des variables de type aeroport, les initialiser, les consulter.

On peut décomposer les valeurs en consultant les champs (field) : les membres nommés de chaque variable.

 // on déclare une variable du type 'aeroport'
 aeroport cdg ;
 // initialisation non montrée ici
 // ensuite on consulte les "variables membres" : remarquer la notation pointée
 float gps1 = cdg.longitude ; // la longitude de cdg
 float gps2 = cdg.latitude ;

Dans certains cas ces structures ont une longueur fixe (ici 3 caractères + 2 flottants). On peut alors les stocker dans des fichiers de données à accès direct (random access file); ces fichiers sont relativement simples à exploiter: pour aller chercher le 12333° élément il suffit de se déplacer de 12333 x taille de la structure.

7.2. "Objets" §§

Considérons maintenant un type composite un peu plus sophistiqué.

Supposons que pour les besoins d’une banque le programmeur ait défini un type Client (qui regroupe toutes les informations concernant un client). On veut maintenant définir un type "Compte en Banque".

Un code Groovy :

// "class" permet de définir un type utilisateur
class CompteEnBanque {
    int numero ;
    // Cette variable membre utilise le type "Client" défini par ailleurs
    Client client ;
    // En réalité sera un autre type permettant de définir un montant monétaire
    // on simplifie pour l'exemple
    double solde ;
    double découvertAutorisé;
    // autres champs
}

La manipulation de cette donnée doit donner lieu à des contrôles stricts pour suivre au plus près la "logique métier":

  • Initialisation d’une variable: correspond à la création d’un Compte

    • Logique d’attribution du numéro de compte
    • Contrôle sur les informations données pour le client
    • éventuelles initialisations d’un solde et du découvert autorisé
  • Modifications possibles:

    • Le numéro du compte n’est pas modifiable (nous ne nous apesantirons pas sur l’aspect modification du champ client)
    • La modification du solde est une opération complexe qui doit être soigneusement contrôlée. Par exemple on ne peut pas retirer plus d’argent que le "découvert autorisé" le permet!

Ces codes doivent être écrits par le programmeur responsable du code CompteEnBanque: c’est lui qui a lu les spécifications et qui doit implanter un code conforme.

Il est imprudent (pour ne pas dire irresponsable!) de laisser d’autres programmeurs créer et modifier "directement" les données de ce type! Par contre ils pourront utiliser ces données pour écrire leur propre programme (par exemple pour réaliser des opérations sur des comptes en banques).

Comment régler ce problème de partage des responsabilités? En associant des "services" à cette donnée.

Exemple de code Groovy écrit par le programmeur responsable de la définition:

public class CompteEnBanque {
    final int numero ;
    private Client client ;
    private double solde ;
    private double découvertAutorisé;

    // code "constructeur"
    public CompteEnBanque (Client client) {
      // vérifie client
      // initialise le numero de compte
      // initialise le solde à 0
      // initialise le découvertAutorisé (en fonction de la richesse du client!)
    }

    // "méthode" de modification du solde
    public void dépôt(double montant) {
       // vérifie l'argument
       // ajoute au solde
    }

    public void retrait(double montant) {
       // vérifie l'argument
       // vérifie si l'opération est possible
       // réalise le retrait
    }

    // "mutateur"
    public void setDécouvert(double découvert) {
  // vérifie l'argument
    }

    // "accesseur"
    // ici "this" désigne l'objet courant
    public double getDécouvert() {
      return this.découvertAutorisé ;
     }

     // autres codes
}
  • Le code du Constructeur est le seul habilité à créer des CompteEnBanque. Terminologie: on créée ainsi des instances de la classe CompteEnBanque (on dit aussi "des objets").

    Exemple de code appelant:

    // on crée un client avec le constructeur défini par cette classe
    // les variables nom, prenom, etc. ont été initialisées auparavant
    Client nouveauClient = new Client(nom, prenom, adresse) ;
    // on crée un compte
    CompteEnBanque nouveauCompte = new CompteEnBanque(client) ;

    Remarque: le code du constructeur est le seul habilité à initialiser le champ numero: une fois initialisé il devient non-modifiable (final).

  • Quand un code appelant ("client" du code "réalisant") veut utiliser une variable de ce type, il invoque des codes appelés méthodes.

    // on dispose d'une variable de type CompteEnBanque et de nom "compteCourant"
    // on dispose d'une variable "montant"
    // invocation d'une méthode sur une instance
    compteCourant.dépôt(montant) ;
  • Faire attention aux méthodes d’accès nommées accesseur et mutateur: ici elles permettent un accès contrôlé à la variable découvertAutorisé qui est private (inaccessible par les autres codes). On pourrait créér un accesseur sur le solde: public double getSolde() par contre la définition d’un mutateur public void setSolde(double mnt) serait une grave erreur de conception: les codes "clients" doivent impérativement passer par les règles de gestion du solde (dépôt, retrait).

texte

Table of contents

previous page start next page