INTRODUCTION A LA PROGRAMMATION ORIENTEE OBJETS
1. UNE APPROCHE DE LA PROGRAMMATION ORIENTEE OBJETS
(la première partie concerne l'algorithme, le codage en java commence à la page 13)
1.1. Rappels
1.1.1. Notion de type
Un type de données est caractérisé par :
– Un ensemble de valeurs pouvant être prises par les variables et constantes du type. Cet ensemble peut être défini en extension (booléen : {vrai, faux} ), ou en intention (entier : ensemble Z),
– Un ensemble de primitives (opérateurs, procédures ou fonctions) utilisables sur les variables et constantes du type.
Par exemple, le type "chaîne de caractères" peut être défini par :
– L’ensemble de valeurs suivant : toute combinaison de caractères (éventuellement vide),
– L’ensemble de primitives suivant :
° concaténation (opérateur // ou +),
° fonction LONGUEUR,
° etc.
1.1.2. Construction de types
Un type structuré comporte plusieurs champs pouvant être d’un type quelconque :
TYPE compte = STRUCT
numéro : entier
nomclient : chaîne de caractères
solde : réel
FSTRUCT
VAR UnCompte : compte
L’accès à un champ se fait à l’aide de l’opérateur "." :
UnCompte.numéro ß12
UnCompte.nomclient ß "Durand"
UnCompte.solde ß 1000
1.2. Problèmes liés à la construction de types
1.2.1. Définition incomplète
L’outil STRUCT permet de définir l’ensemble des valeurs pouvant être prises par les objets d’un type construit (ensemble des triplets <entier, chaîne de caractères, réel> pour notre type compte par exemple). Par contre, rien n’est dit concernant les opérations applicables aux objets de ce type.
Le type structuré compte ne permet pas d’appréhender à lui seul la notion de compte bancaire qu’il est censé représenter. Il est bien entendu nécessaire de savoir qu’un compte est caractérisé par un numéro, le nom de son propriétaire et un solde, mais il est indispensable de compléter cette description en disant qu’un compte peut être créé, crédité, débité, etc.
1.2.2. Problème d’évolution
Le fait de réduire la construction de type à la seule définition d’une représentation en mémoire a par ailleurs une conséquence importante : le type ne peut pas évoluer.
Considérons la variable c de type compte et la séquence d'instructions suivante :
c.numéro ß12
c.nom ß "Durand"
c.solde ß 1000
…
AFFICHER "Entrez le montant à débiter : "
SAISIR montant
SI c.solde >= montant
ALORS
c.solde ß c.solde - montant
SINON
AFFICHER "Solde insuffisant ! "
FSI
Si le système évolue vers une perception plus fine du compte bancaire nécessitant la mémorisation des cumuls des opérations de débit et de crédit, la représentation peut devenir :
TYPE compte = STRUCT
numéro : entier
nomclient : chaîne de caractères
cumuldébits, cumulcrédits : réel
FSTRUCT
Le programme ci-dessus doit être totalement reconsidéré.
1.3. Une première solution : définir des procédures et fonctions permettant de manipuler les données du type
Avec ces procédures et fonctions, on aura une description plus complète du type.
La structure de données pourra évoluer sans remettre en cause les programmes la manipulant du moment que le "mode d'emploi" des procédures et fonctions associées à cette structure reste inchangé.
L'ensemble constitué des données et des procédures et fonctions manipulant ces données est nommé type abstrait de données.
Exemple : le type compte
- Description de la structure :
TYPE compte = STRUCT
numéro : entier
nomclient : chaîne de caractères
solde : réel
FSTRUCT
– Fonction d'initialisation du compte
FONCTION initial (données : num : entier, nom : chaîne de caractères) : compte
VAR cc : compte
DEBUT
cc.numéro ß num
cc.nomclient ß nom
cc.solde ß 0
RETOURNER cc
FIN
– Créditer un compte :
PROCEDURE créditer (donnée : montant : réel, donnée modifiée : c : compte)
DEBUT
c.solde ßc.solde + montant
FIN
– Débiter un compte :
Même principe que créditer.
– Connaître le solde d’un compte :
FONCTION solde (donnée : c: compte) : réel
DEBUT
RETOURNER c.solde
FIN
Cette description fonctionnelle est suffisante pour utiliser le type comme dans la séquence d'instructions suivante :
c ß initial (12, "Durand") /* c est une variable de type compte */
...
AFFICHER "Saisir le montant à débiter"
SAISIR montant
SI solde (c) >= montant alors
ALORS
débiter (montant, c)
SINON
AFFICHER "Solde insuffisant !"
FSI
2. ENCAPSULATION, CLASSE ET OBJET
Rien n'empêche un programmeur d'utiliser le type compte en accédant directement aux données de la structure, voire en se dispensant totalement de l'utilisation des fonctions et procédures prévues (sous prétexte d'écrire un code plus efficace par exemple).
Il faut donc pouvoir interdire l'accès en dehors des procédures et fonctions dédiées à la manipulation des données de la structure, on parlera de procédures et fonctions membres ou encore de méthodes.
L'encapsulation consiste à réunir les données et les procédures et fonctions membres en une seule unité syntaxique : la classe.
Un objet est un représentant particulier d'une classe.
Par exemple, l'objet (1, "Dupont", 15000) ou encore l'objet (2, "Dubois", 12000) sont des objets de la classe compte.
Chaque objet possède ses propres données. Par contre, tous les objets d'une même classe partagent une unique occurrence de chaque méthode.
2.1. Déclaration d'une classe
Les classes sont à déclarer avant les variables.
Syntaxe de la déclaration habituelle d'une classe :
CLASSE nomclasse
privé /* cette partie sera inaccessible en dehors de la classe */
Partie déclarations des données membres de la classe
public /* cette partie sera accessible en dehors de la classe et constitue l'interface de la classe */ Partie prototype des méthodes de la classe (procédures et fonctions membres)
FCLASSE
Exemple : la classe compte
CLASSE compte
privé
co_numéro : entier
co_nomclient : chaîne de caractères
co_solde : réel
public
PROCEDURE initial (données : numéro : entier, nom : chaîne de caractères)
PROCEDURE créditer (donnée : montant : réel)
PROCEDURE débiter (donnée : montant : réel)
FONCTION solde () : réel
FCLASSE
Remarques :
- Une donnée peut être publique (même si cela pénalise l'évolution de la classe), par exemple on peut souhaiter que le nom du client soit public.
- Une méthode peut être privée ; en effet, il est possible que les méthodes publiques effectuent des blocs d'instructions communs, dans ce cas on crée une procédure ou une fonction privée que seules les méthodes de la classe pourront appeler.
- Il peut y avoir plusieurs parties privées ou publiques, par exemple si on souhaite que le nom soit public, la déclaration de la classe devient :
CLASSE compte
privé
co_numéro : entier
public
co_nomclient : chaîne de caractères
privé
co_solde : réel
public
PROCEDURE initial (données : numéro : entier, nom : ch. de car.)
PROCEDURE créditer (donnée : montant : réel)
PROCEDURE débiter (donnée : montant : réel)
FONCTION solde () : réel
FCLASSE
- L'ensemble des parties (données et méthodes) publiques constitue l'interface et est utilisable à l'intérieur et à l'extérieur de la classe.
- Tout membre (donnée ou méthode) appartenant à une partie privée n'est utilisable qu'à l'intérieur de la définition d'une méthode de la classe.
2.2. Définition des méthodes
Syntaxe de la définition d'une procédure membre :
PROCEDURE nomclasse::nomprocédure ([Paramètres formels avec précision du statut et du type])
partie déclarative éventuelle
DEBUT
partie instructions
FIN
Syntaxe de la définition d'une fonction membre :
FONCTION nomclasse::nomfonction ([Paramètres formels avec précision du statut et du type]) : type
du résultat
partie déclarative éventuelle
DEBUT
partie instructions
FIN
Remarque : chaque méthode reçoit l'objet ayant servi à son appel en tant que paramètre implicite.
Exemple : définition des méthodes de la classe compte
– Initialiser un compte :
PROCEDURE compte::initial (données : numéro: entier, nom : chaîne de caractères)
DEBUT
co_numéro ß numéro
co_nomclient ß nom
co_solde ß 0
FIN
– Créditer un compte :
PROCEDURE compte::créditer (donnée : montant : réel)
DEBUT
co_solde ßco_solde + montant
FIN
– Débiter un compte :
Même principe que créditer.
– Connaître le solde d’un compte :
FONCTION compte::solde () : réel
DEBUT
RETOURNER co_solde
FIN
3. UTILISATION DES CLASSES
3.1. Instanciation
L'instanciation d'une classe correspond à la création d'une instance de cette classe, c'est-à-dire d'un objet. Elle se fait :
. par une déclaration (la variable ou l'objet est alors statique),
Exemple :
VAR c : compte /* création d'un objet statique */
DEBUT
…
FIN
3.2. Appel de méthodes
Syntaxe :
NomObjet.NomMéthode ([paramètres effectifs])
Exemples (la classe compte est décrite en haut de la page 5) :
· Initialiser l'objet c avec le compte n°1 appartenant à Dupont :
c.initial(1,"Dupont")
· Créditer le compte de Dupont de 1000 francs :
c.créditer(1000)
3.3. Fonctions d'accès
Pour permettre l'accès aux données en lecture, on est souvent amené à écrire des fonctions d'accès puisque généralement les données sont privées. En fait, une fonction d'accès est ni plus ni moins qu'une méthode.
Si on souhaite mettre à jour la donnée, on écrira une procédure et non une fonction.
Exemple :
On souhaite pouvoir obtenir d'une part le numéro du compte et d'autre part le nom du
client ainsi que pouvoir modifier le nom du client.
La classe compte devient :
CLASSE compte
privé
co_numéro : entier
co_nomclient : chaîne de caractères
co_solde : réel
public
PROCEDURE initial (données : numéro : entier, nom : chaîne de caractères)
PROCEDURE créditer (donnée : montant : réel)
PROCEDURE débiter (donnée : montant : réel)
FONCTION solde () : réel
FONCTION nom () : chaîne de caractères
FONCTION numéro () : entier
PROCEDURE modif_nom (donnée : nouveau_nom : chaîne de caractères)
FCLASSE
FONCTION compte::nom() : chaîne de caractères
DEBUT
RETOURNER co_nom
FIN
FONCTION compte::numéro() : entier
DEBUT
RETOURNER co_numéro
FIN
PROCEDURE compte::modif_nom (donnée : nouveau_nom : chaîne de caractères)
DEBUT
co_nom ß nouveau_nom
FIN
Remarque : nous avons uniquement déclaré les prototypes des méthodes entre CLASSE et FCLASSE mais il est également possible de faire figurer tout le code des méthodes entre CLASSE et FCLASSE au lieu d'écrire le code des méthodes après FCLASSE.
3.4. Exercice
a) Ecrire la classe employe.
Les données sont : numero:entier, nom:chaîne, adr:chaîne, salaire:réel.
Il faut prévoir les méthodes pour :
- l’initialisation,
- l’affichage des informations (en 3 méthodes)
- l’augmentation du salaire en fonction d’un pourcentage (entier) passé en paramètre.
b) Ecrire un programme pour le test de la classe. Le programme devra créer 5 employés (les informations seront saisies) puis les afficher et faire saisir le pourcentage d'augmentation du salaire et enfin afficher les employés avec le salaire actualisé. Le n° d’employé est séquentiel.
Correction :
CLASSE employé
Privé
num : entier
nom : chaine
sal : réel
Public
PROCEDURE init(données : numéro:entier, nomemp:chaine, salemp:réel)
FONCTION affiche_num() : entier
FONCTION affiche_nom() : chaine
FONCTION affiche_sal() : réel
PROCEDURE augmente(données : pourc:entier)
FCLASSE
PROCEDURE employé::init(données : numéro:entier, nomemp:chaine, salemp:réel)
DEBUT
num ç numéro
nom ç nomemp
sal ç salemp
FIN
PROCEDURE employé::augmente(données : pourc:entier)
DEBUT
salçsal+(sal*pourc/100)
FIN
FONCTION employé::affiche_num() : entier
DEBUT
Retourner num
FIN
FONCTION employé::affiche_nom() : chaine
DEBUT
Retourner nom
FIN
FONCTION employé::affiche_sal() : réel
DEBUT
Retourner sal
FIN
ALGO Test
VAR
Temp : Tableau[1..5] de employé
num : entier
nomemp : chaine
salemp, pourc : réel
DEBUT
Pour num de 1 à 5
afficher ("saisir le nom de l'employé numéro " num); saisir(nomemp)
afficher("Saisir le salaire de l'employé"); saisir(salemp)
Temp[num].init(num, nomemp, salemp)
Fpour
Pour num de 1 à 5
afficher("Le nom de l'employé : ", Temp[num].affiche_num(), " est ", Temp[num].affiche_nom(), " et son salaire est de ", Temp[num].affiche_sal()
Fpour
Afficher("Saisir le pourcentage d'augmentation"); Saisir(pourc)
Pour num de 1 à 5
T[num].augmente(pourc)
Afficher("L'employé : ", Temp[num].affiche_num(), ", ", Temp[num].affiche_nom(), " a un nveau salaire de:", Temp[num].affiche_sal()
FPour
4. PROPRIETES DES CLASSES
4.1. Membre donnée statique
Nous avons vu que chaque instance d'une classe dispose d'une occurence propre de chacune des données. Il existe une alternative à cette situation. Il est possible de définir un membre donnée statique, il n'en existe alors qu'un seul exemplaire, partagé par toutes les instances de la classe.
Exemple :
CLASSE compte
privé
statique nbcomptes : entier
co_numéro : entier
co_nomclient : chaîne de caractères
co_solde : réel
public
PROCEDURE initial (données : numéro : entier, nom : chaîne de caractères)
PROCEDURE créditer (donnée : montant : réel)
PROCEDURE débiter (donnée : montant : réel)
FONCTION solde () : réel
FONCTION nom () : chaîne de caractères
FONCTION numéro () : entier
PROCEDURE modif_nom (donnée : nouveau_nom : chaîne de caractères)
FCLASSE
entier compte::nbcomptes ß 0 /* Définition et initialisation */
Remarque : un membre donnée statique doit être défini et initialisé en dehors de la déclaration de la classe.
4.2. Méthode membre statique
Un membre donnée statique existe en dehors de toute instance de la classe. De la même façon, une méthode membre statique peut être appelée indépendamment de tout objet.
Exemple :
CLASSE compte
privé
statique nbcomptes : entier
co_numéro : entier
co_nomclient : chaîne de caractères
co_solde : réel
public
…
statique FONCTION totalcomptes () : entier
FCLASSE
entier compte::nbcomptes ß 0 /* Définition et initialisation */
FONCTION compte::totalcomptes() : entier
DEBUT
RETOURNER nbcomptes
FIN
Affichage du nombre total :
AFFICHER "Il y a ", compte::totalcomptes(), " comptes"
Remarques :
- La méthode membre statique est accessible même s'il n'existe aucune instance de compte.
- Les méthodes membres statiques sont le plus souvent utilisées pour manipuler les membres données statiques. Elles ne peuvent pas désigner directement une donnée non statique ou une méthode non statique de la classe (il faut qu'elles créent un objet). En revanche, une méthode non statique peut utiliser directement une donnée statique ou une méthode statique de la même classe.
5. CODAGE EN JAVA
5.1. Déclaration d'une classe
class Compte // Déclaration de la classe Compte d'origine
// Par défaut, toutes les données et méthodes sont considérées comme étant
// publiques à moins qu'on ne spécifie private devant la donnée ou devant la méthode.
// En fait, pour améliorer la lisibilité, on spécifie systématiquement le statut (pour
// l'instant private ou public) comme en pseudo-code.
{
private int co_numero;
private String co_nomclient;
private float co_solde;
public void initial (int numero, String nom)
{
co_numero = numero;
co_nomclient= nom;
co_solde = 0;
}
// Créditer un compte :
public void crediter (float montant)
{
co_solde += montant;
}
// Débiter un compte : même principe que créditer.
// Connaître le solde d’un compte :
public float solde ()
{
return co_solde;
}
} // Le ; est facultatif à la fin de la déclaration d'une classe en Java
5.2. Instanciation
Compte c = new Compte(); // c est créé comme compte
…
En java, l’instanciation d’une classe est toujours dynamique.
Contrairement à C++, il n’y a aucune manipulation de pointeurs.
5.3. Appel de méthodes
· Initialiser l'objet c avec le compte n°1 appartenant à Dupont :
c.initial (1, "Dupont");
· Créditer le compte de Dupont de 1000 francs :
c.crediter (1000);
5.4. Membre donnée statique
class Compte
{
private static int nbcomptes;
private int co_numero;
…
private float co_solde;
public …
}
La donnée statique nbcomptes est implicitement initialisée à 0.
5.5. Méthode membre statique
class Compte
{
private static int nbcomptes;
private int co_numero;
…
private float co_solde;
…
public static int totalcomptes() // On aurait pu avoir nbcomptes()
{
return nbcomptes;
}
}
class Compte
{
private static int nbcomptes;
private int co_numero;
private String co_nom;
private float co_solde;
public void initial (int numero, String nom)
{
co_numero = numero;
co_nom= nom;
co_solde = 0;
nbcomptes++;
}
public void crediter (float montant)
{
co_solde += montant;
}
public float solde ()
{
return co_solde;
}
public void affiche()
{
System.out.println("Numéro : "+co_numero);
System.out.println("Nom : "+co_nom);
System.out.println("Solde : "+co_solde);
}
public static int totalcomptes()
{
return nbcomptes;
}
}
Ceci est à placer dans un fichier Compte.java et à pré-compiler.
L'utilisation de la classe Compte pourra être effectuée dans n'importe quel autre programme. En Java, tout programme appartient à une classe. Nous allons donc écrire une classe pour utiliser la classe Compte.
class Prog1compte // Création d’une classe pour tester la classe Compte
{
public static void main (String argv[ ]) // méthode principale statique
{
Compte compte1=new Compte(); // création objet compte1
compte1.initial(1,"dupont"); // appel de la méthode initial
compte1.affiche();
compte1.crediter(1000);
compte1.affiche();
System.out.println("Nombre de comptes : "+Compte.totalcomptes());
}
};
On pré-compile et on exécute Prog1compte.
Remarque :
Si la classe Compte n'est pas dans le même répertoire, il faut que le classpath comporte le répertoire dans lequel figure Compte.class.
main : point d'entrée du programme
On lance l'exécution d'un programme Java en démarrant une machine virtuelle Java (la JVM) avec, en paramètre, le nom de la classe de démarrage, laquelle doit forcément; contenir une méthode main. Une fois que la JVM s'est mise en place, elle lance le programme proprement dit, par la première instruction de la méthode main, sans créer d'objet de la classe de démarrage. Cela fonctionne, car la méthode est déclarée static.
EXERCICE :
Traduire l'algorithme de la page 9 (classe employé) en Java.
Correction :
Classe employé :
public class employé
{
private int num;
private String nom;
private double sal;
public void init(int numéro, String nom, double salemp)
{
num=numéro;
this.nom=nom;
sal=salemp;
}
public void augmente(int pourc)
{
sal += sal*pourc/100;
}
public int affiche_num()
{
return num;
}
public String affiche_nom()
{
return nom;
}
public double affiche_sal()
{
return sal;
}
}
Classe Test_employé :
import java.io.*;
public class test_employe
{
public static void main(String argv[]) throws IOException
{
int numéro,pourc,x;
employé e1=new employé();
String nomemp, mess;
double sal;
// Déclaration du tableau
employé T[]=new employé[6];
for (numéro=1;numéro<=5;numéro++)
{
nomemp=Entree.chaine("Saisir le nom de l'employé");
sal=Entree.reel("Saisir le salaire de l'employé");
// Initialisation du tableau
T[numéro]=new employé();
T[numéro].init(numéro,nomemp,sal);
}
for (numéro=1;numéro<=5;numéro++)
{
mess="Le nom de l'employé numéro " + T[numéro].affiche_num();
mess+=" est " + T[numéro].affiche_nom() + " et son salaire est de ";
mess+=T[numéro].affiche_sal();
System.out.println(mess);
}
pourc=Entree.entier("Saisir le pourcentage d'augmentation");
for (numéro=1;numéro<=5;numéro++)
{
T[numéro].augmente(pourc);
}
for (numéro=1;numéro<=5;numéro++)
{
mess="L'employé n°"+T[numéro].affiche_num()+", "+T[numéro].affiche_nom();
mess+=" a un nouveau salaire de : " + T[numéro].affiche_sal();
System.out.println(mess);
}
}
}