Skip to main content

Objet et encapsulation - Les objets

Définition : L'objet est l'entit de base de l'exécution. À chaque objet est attribu une zone mémoire différente.

Plusieurs objets mettant en oeuvre la même encapsulation, il est nécessaire d'identifier de manière unique un objet.

Définition : L'identité d'un objet est assurée par l'adresse de la zone mémoire attribuée à cet objet.

La zone mémoire d'un objet contient la valeur de ses attributs (suivant la description de l'encapsulation). Un objet encapsule les méthodes. En pratique, tous les objets mettant en oeuvre la même encapsulation partagent le code des méthodes. Ce code commun est exécuté avec l'identité (la zone mémoire) de l'objet sur lequel est déclenchée une action.

Un langage orienté objet doit fournir des mécanismes pour :

  • créer un objet : instanciation
  • déclencher une action sur un objet : envoi de messages
  • assurer la séparation utilisation/ réalisation : masquage d'information

Instanciation

Définition : L'instanciation désigne l'opération de créer un objet à partir de la description de l'encapsulation. Elle attribue une zone mémoire à l'objet et initialise ses attributs.

Définition : Dans un langage à classe, un objet est instancié à partir d'une classe. Un objet est souvent désigné par le terme instance.

Pour respecter l'encapsulation, la classe fournit le code d'initialisation par l'intermédiaire d'un traitement appelé constructeur. Comme une méthode, un constructeur admet une liste de paramètres. En Java, l'instanciation est une expression atomique construire à partir de l'opérateur new et d'un constructeur de la classe.

Le constructeur dans la classe PorteCharniere

class PorteCharniere{
//La définition d'un constructeur
PorteCharniere(){
estFerme = false;
//...
}

//La définition des variables/attributs
boolean estFerme;

//La définition des méthodes
void fermer(){
//...
estFerme=true;
}

void ouvrir(){
//...
estFerme=false;
}

boolean estFerme(){
rrturn estFerme;
}
}

Affectation dans une variable

Comme habituellement dans le code, les variables / paramètres (de méthode) vont permettre de manipuler les objets / instances. Dans un langage typé, le compilateur vérifie la compatibilité des types entre le type de la variable / paramètre et l'instance affectée à cette variable. Une variable de type PorteCharniere contient une instance de la classe PorteCharniere

PorteCharniere sesame;
sesame = new PorteCharniere();
Portecharniere paradis = new Portecharniere();
sesame = new Portecharniere();
paradis = sesame;
Telecommande maTelecommande = new Telecommande(3);
maTelecommande = sesame; // erreur type non compatible

En Java, les variables / paramètres correspondant à des objets sont des références. La variable paradis contient l'adresse d'un objet. La libération de la zone mémoire d'un objet est prise en charge par le ramasse miettes de la machine virtuelle de Java ("jvm")

  • Les variables des type de base (comme int, float, boolean, byte, char) contiennent des valeurs. Les comportements entre type de base et type référence sont différents dans l'affectation et le passage de paramètres.
  • Les chaînes de caractères constantes sont des instances de la classe String.
  • Les tableaux Java sont des objets avec des méthodes d'accès et possède l'attribut de longueur.

Le constructeur dans la classe Telecommande

class Telecommande {
int nbPortes;
Portecharniere[] mesPortes;

Telecommande(int capacite) {
nbPortes = 0;
mesPortes = new PorteCharnière [capacite];
}
voir positionner(Portecharniere p){
mesPortes[nbPortes]=p;
nbPortes++;
/* Todo depassement */
}
void activer(int numero){
/*Todo*/
}
void desactiver(int numero){
/*Todo*/
}
void desactiverTout(){
/*Todo*/
}
}

Pour respecter l'encapsulation, le code du constructeur doit s'assurer d'initiliser les attributs avec des valeurs cohérentes par rapport à l'encapsulation. Sinon les futures actions déclenchées sur l'objet ne respecteront pas le service proposé par l'encapsulation. Le cas le plus fréquent est une valeur invalide par les paramètres du constructeur. Les actions d'une instance de télécommande sont elles cohérentes avec le service de l'encapsulation si la valeur du paramètre d'initialisation est égale à zéro ?

Envoi de messages

Définition : Un message est formé d'un nom et d'une liste de valeur de paramètres (cette liste peut être vide). À la réception d'un message, l'objet fait la correspondant entre le message et l'action à déclencher.

La notion d'envoi de messages cherche à abstraire la manière dont est choisie l'action. Un même message peut déclencher des actions différentes sur des encapsulations différentes : Quel est le code déclenché par l'appel à une méthode ? Est-ce un accès à un attribut ou l'appel à une méthode ? en général la notation pointée est utilisée pour indiquer l'envoie du'n message à un objet. L'envoi du message s'écrit en utilisant la variable qui référence l'objet. Envoie de messages avec les variables de l'exemple précédent :

// appel d'un traitement
paradis.fermer();
sesame.ouvrir();
sesame.estFerme();
maTelecommande.positionner(sesame);
maTelecommande.activer(1);
maTelecommande.desactiver(2);
maTelecommande.desactivertout();
// accès à un attribut
sesame.estFerme = true;
maTelecommande.mesPortes[1].estFerme = false;
maTelecommande.mesPortes[1].ouvrir();

En java, l'abstraction n'est pas complète car la syntaxe est différente entre un attribut et un méthode. C'est au moment de l'exécution que l'envoi de messages est réalisé. Le message est transmis à l'objet référencé par la variable. Du coup, la java ne peut pas vérifier l'existence d'un message sur un objet. La seule vérification possible est par rapport au type de la variable.

Construire une encapsulation

Encapsulation et masquage d'information

Un objet met en oeuvre une encapsulation, il y a donc abstraction sur la réalisation de l'objet. Pour permettre de changer cette réalisation sans modifier le code utilisant cet objet (code client), nous cherchons à masquer l'information sur la réalisation au coude client. Les détails de la réalisation d'un objet doivent être visible le moins possible. Dans un langage à classe, ce masque d'information est défini au niveau de la classe avec le mécanisme de portée des identificateurs. De base, deux portées sont nécessaires pour séparer utilisation et réalisation :

  • portée publique, l'identificateur est accessible par un code écrit en dehors de la classe

  • portée privée, l'identificateur est accessible uniquemeent par le code contenu dans la classe

    Dans le code de la classe Telecommande, l'instruction mesPortes[i].estFerme ne respecte pas l'encapsulation. La définition de l'attribut risque de changer :

    • le choix de stocker la valeur plutôt que la calculer
    • le choix de son type ou de la signification de sa valeur

Le choix de définir un attribut est une décision de réalisation.

Définition : Le principe de masquage des attributs : Afin de respecter l'encapsulation, tous les attributs d'un objet doivent être déclarés privés et seules les méthodes peuvent être déclarées publiques.

class Telecommande {
private int nbPortes;
private PorteCharniere[] mesPortes;

public Telecommande(int capacite){
nbPortes = 0;
mesPortes = new Portecharniere[capacite];
}

public boolean estRempli ( ) {
return mesPortes . length <= nbPortes ;
}
public void positionner ( PorteCharniere p ) {
i f ( mesPortes . length <= nbPortes )
return ; /* Todo erreur de depassement */
mesPortes [ nbPortes ] = p ;
nbPortes ++;
}
public void activer ( int numero ) {
mesPortes [numero ] . ouvr ir ( ) ;
}
public void desactiver ( int numero ) {
mesPortes [numero ] . fermer ( ) ;
}
public void desactiverTout ( ) {
for ( int i = 0 ; i < nbPortes ; i ++ )
if ( ! mesPortes [ i ] . estFerme ( ) )
mesPortes[i].fermer();
}
}

Objet constant

Un objet possède un état défini à partir des valeurs contenues dans l’ensemble de ses attributs. Un objet est constant si son état ne peut-être modifié. En Java, un objet est constant que si aucune de ses méthodes ne modifie son état : exemple dans l’A.P.I standard Java, la différence entre les instances de la classe String et les instances de la classe StringBuffer. Cette encapsulation des chaînes constantes montre l’utilisation habituelle des objets constants en Java : le partage d’objets.

Communication entre objets

Un objet est passif. Il déclenche des actions uniquement en réponse à des sollicitations d'autres objets. Prenons le cas où un objet t (instance de la classe Petit) doit envoyer le message voler à un objet r (instance de la classe Coeur).

Pour communiquer, l'objet t doit posséder une référence sur l'objet r au moins au moment de l'exécution de l'envoi du message. La solution directe est de stocker cette référence dans les attributs de l'objet t; l'objet t "a un" objet r. La classe Petit doit déclarer un attribut / une variable d'instance de type Coeur.

Définition : Le lien "a-un" entre deux classes. Une classe P possède un lien "a un" avec une classe R si elle définit un attribut de type R. Une instance de P possède dans ses attributs une instance de R.

Le lien "a-un" ne fixe pas la manière d'obtenir la référence stockée dans l'attribut : instanciation de la classe Coeur dans le constructeur de la classe Petit ou passage de la référence comme paramètre d'un constructeur ou d'une méthode de la classe Petit.

Le lien "a-un" dans la classe Telecommande

class Telecommande{
int nbPortes ;
PorteCharniere[] mesPortes ;
Telecommande(int capacite){
nbPortes = 0;
mesPortes = new PorteCharniere[capacite];
}

void positionner(PorteCharniere p){
if(nbPortes >= mesPortes.length)
return; /* Todo erreur de depassement */
mesPortes[nbPortes] = p ;
nbPortes ++;
}
void activer(int numero) {
mesPortes[numero].ouvrir();
}
void desactiver(int numero){
mesPortes[numero].fermer();
}
void desactiverTout(){
for(int i = 0 ; i < nbPortes ; i++)
if(!mesPortes[i].estFerme)
mesPortes[i].fermer();
}
}

Des traitements applicables à toutes les encapsulations

Dans la pratique, il existe un petit nombre de traitements particuliers qui peuvent s'appliquer à tous les objets. Voici les plus fréquents :

Pour vérifier l'égalité entre deux instances de la même classe. L'opérateur Java == indique l'égalité de références (c'est la même instance). Mais deux instances différentes peuvent être considérées comme égales par rapport à la valeur de leurs attributs. C'est l'objectif de la méthode equals().

Pour copier ou cloner un objet. Le passage d'un paramètre objet se fait exclusiement par référence / adresse. Pour éviter un effet de bord, il est donc parfois nécessaire d'effectuer une copie d'un objet. C'est l'objectif de la méthode clone().

Le langage Java regroupe ces méthodes particulières dans la classe Object. Ces méthodes sont définies avec un traitement par défaut qu'il faut adapter à chaque classe : par exemple, la méthode equals() a pour traitement par défaut l'égalité de référence.

Dans la classe Object nous trouvons la méthode toString(). Elle retourne une chaîne de caractère qui va permettre de tracer, par exemple, la valeur des attributs d'un objet. Son code par défaut retourne une chaîne correspondant à l'identité de l'objet.

Voici une manière d'écrire son code dans la classe Telcommande pour tracer la valeur de deux attributs

public String toString(){
return "<nbPorte:" + nbPortes + ", capacite :" + mesPortes.length + '>';
}