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.
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.
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
client
Modifications possibles:
client
)
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) ;
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
).