pdf - e-book - archive - github.com

1.10  Encapsulation

1.10.1  Exemple

Implémentons une file de nombre entiers. Nous avons un exemple dans le fichier suivant :

package encapsulation;

public class FilePublic
{
 /*
  * Elements de la file
  */

 int[] entiers;

 /*
  * Indice de la tête de file et du premier emplacement libre dans le
  * tableau.
  */

 int first, firstFree;

 /*
  * Initialise les attributs de la file.
  */

 public void init(int taille)
 {
  entiers = new int[taille + 1];
  first = firstFree = 0;
 }

 /*
  * Décale i d'une position vers la droite dans le tableau, revient au debut
  * si i déborde.
  */

 public int incrementeIndice(int i)
 {
  i++;
  if (i == entiers.length)
   i = 0;
  return i;
 }

 /*
  * Retourne vrai si et seulement si la file est pleine.
  */

 public boolean estPlein()
 {
  return first == incrementeIndice(firstFree);
 }

 /*
  * Retourne vrai si et seulement si la file est vide.
  */

 public boolean estVide()
 {
  return first == firstFree;
 }

 /*
  * Ajoute l'élément n dans la file.
  */

 public void enfile(int n)
 {
  if (!estPlein())
  {
   entiers[firstFree] = n;
   firstFree = incrementeIndice(firstFree);
  }
 }

 /*
  * Supprime la tête de file.
  */

 public void defile()
 {
  if (!estVide())
   first = incrementeIndice(first);
 }

 /*
  * Retourne la tête de file.
  */

 public int premier()
 {
  if (!estVide())
   return entiers[first];
  return 0;
 }
}

Télécharger le fichier

Un exemple typique d’utilisation de cette file est donné ci-dessous :

package encapsulation;

public class TestFilePublic
{
 public static void main(String[] args)
 {
  FilePublic f = new FilePublic();
  f.init(20);
  for (int i = 0; i < 30; i += 2)
  {
   f.enfile(i);
   f.enfile(i + 1);
   System.out.println(f.premier());
   f.defile();
  }
  while (!f.estVide())
  {
   System.out.println(f.premier());
   f.defile();
  }
 }
}

Télécharger le fichier

Si vous travaillez en équipe, et que vous êtes l’auteur d’une classe FilePublic, vous n’aimeriez pas que vos collègues programmeurs s’en servent n’importe comment ! Vous n’aimeriez pas par exemple qu’ils manipulent le tableau entiers ou l’attribut first sans passer par les méthodes, par exemple :

 
 FilePublic f = new FilePublic();
 f.init(20);
        f.entiers[4] = 3;
        f.first += 5;
        /* ... arretons la les horreurs ... */

Il serait appréciable que nous puissions interdire de telles instructions. C’est-à-dire forcer l’utilisateur de la classe à passer par les méthodes pour manier la file.

1.10.2  Visibilité

La visibilité d’un identificateur (attribut, méthode ou classe) est l’ensemble des endroits dans le code ou il est possible de l’utiliser. Si un identificateur et précédé du mot clé public, cela signifie qu’il est visible partout. Si un identificateur et précédé du mot clé private, cela signifie qu’il n’est visible qu’à l’intérieur de la classe. Seule l’instance à qui cet identificateur appartient pourra l’utiliser. Par exemple :

package encapsulation;

public class FilePrivate
{
 private int[] entiers;
 private int first, firstFree;

 public void init(int taille)
 {
  entiers = new int[taille + 1];
  first = firstFree = 0;
 }

 private int incrementeIndice(int i)
 {
  i++;
  if (i == entiers.length)
   i = 0;
  return i;
 }

 public boolean estPlein()
 {
  return first == firstFree + 1;
 }

 public boolean estVide()
 {
  return first == incrementeIndice(firstFree);
 }

 public void enfile(int n)
 {
  if (!estPlein())
  {
   entiers[firstFree] = n;
   firstFree = incrementeIndice(firstFree);
  }
 }

 public void defile()
 {
  if (!estVide())
   first = incrementeIndice(first);
 }

 public int premier()
 {
  if (!estVide())
   return entiers[first];
  return 0;
 }
}

Télécharger le fichier

On teste cette classe de la même façon :

package encapsulation;

public class TestFilePrivate
{
 public static void main(String[] args)
 {
  FilePrivate f = new FilePrivate();
  f.init(20);
  for (int i = 0; i < 30; i += 2)
  {
   f.enfile(i);
   f.enfile(i + 1);
   System.out.println(f.premier());
   f.defile();
  }
  while (!f.estVide())
  {
   System.out.println(f.premier());
   f.defile();
  }
 }
}

Télécharger le fichier

S’il vous vient l’idée saugrenue d’exécuter les instructions :

 
 FilePrivate f = new FilePrivate();
 f.init(20);
        f.entiers[4] = 3;
        f.first += 5;
        /* ... arretons la les horreurs ... */

vous ne passerez pas la compilation. Comme les champs entiers et first sont private, il est impossible de les utiliser avec la notation pointée. Cela signifie qu’ils ne sont accessibles que depuis l’instance de la classe FilePrivate qui sert de contexte à leur exécution. De cette façon vous êtes certain que votre classe fonctionnera correctement. En déclarant des champs privés, vous avez caché les divers détails de l’implémentation, cela s’appelle l’encapsulation. Celle-ci a pour but de faciliter le travail de tout programmeur qui utilisera cette classe en masquant la complexité de votre code. Les informations à communiquer à l’utilisateur de la classe sont la liste des méthodes publiques. A savoir

 
public class FilePrivate
{
    public void init(int taille){/*...*/}
    public boolean estPlein(){/*...*/}
    public boolean estVide(){/*...*/}
    public void enfile(int n){/*...*/}
    public void defile(){/*...*/}
    public int premier(){/*...*/}
}

On remarque non seulement qu’il plus aisé de comprendre comment utiliser la file en regardant ces quelques méthodes mais surtout que la façon dont a été implémenté la file est totalement masquée.

1.10.3  Constructeur

Supposons que notre utilisateur oublie d’invoquer la méthode init(int taille), que va-t-il se passer ? Votre classe va planter. Comment faire pour être certain que toutes les variables seront initialisées ? Un constructeur est un sous-programme appelé automatiquement à la création de tout objet. Il porte le même nom que la classe et n’a pas de valeur de retour. De plus, il est possible de lui passer des paramètres au moment de l’instanciation. Remplaçons init par un constructeur :

package encapsulation;

public class FileConstructeur
{
 private int[] entiers;
 private int first, firstFree;

 public FileConstructeur(int taille)
 {
  entiers = new int[taille + 1];
  first = firstFree = 0;
 }

 private int incrementeIndice(int i)
 {
  i++;
  if (i == entiers.length)
   i = 0;
  return i;
 }

 public boolean estPlein()
 {
  return first == firstFree + 1;
 }

 public boolean estVide()
 {
  return first == incrementeIndice(firstFree);
 }

 public void enfile(int n)
 {
  if (!estPlein())
  {
   entiers[firstFree] = n;
   firstFree = incrementeIndice(firstFree);
  }
 }

 public void defile()
 {
  if (!estVide())
   first = incrementeIndice(first);
 }

 public int premier()
 {
  if (!estVide())
   return entiers[first];
  return 0;
 }
}

Télécharger le fichier

On peut alors l’utiliser sans la méthode init,

package encapsulation;

public class TestFileConstructeur
{
 public static void main(String[] args)
 {
  FileConstructeur f = new FileConstructeur(20);
  for (int i = 0; i < 30; i += 2)
  {
   f.enfile(i);
   f.enfile(i + 1);
   System.out.println(f.premier());
   f.defile();
  }
  while (!f.estVide())
  {
   System.out.println(f.premier());
   f.defile();
  }
 }
}

Télécharger le fichier

1.10.4  Accesseurs

Il est de bonne programmation de déclarer tous les attributs en privé, et de permettre leur accès en forçant l’utilisateur à passer par des méthodes. Si un attribut X est privé, on crée deux méthodes getX et setX permettant de manier X. Par exemple,

package encapsulation;

public class ExempleAccesseurs
{
 private int foo;

 public int getFoo()
 {
  return foo;
 }

 public void setFoo(int value)
 {
  foo = value;
 }
}

Télécharger le fichier

On ainsi la possibilité de manier des attributs privés indirectement.

1.10.5  Surcharge

Il est possible de definir dans une même classe plusieurs fonctions portant le même nom. Par exemple,

package encapsulation;

public class FileSurcharge
{
 private int[] entiers;
 private int first, firstFree;

 public FileSurcharge(int taille)
 {
  entiers = new int[taille + 1];
  first = firstFree = 0;
 }

 public FileSurcharge(FileSurcharge other)
 {
  this(other.entiers.length - 1);
  for (int i = 0; i < other.entiers.length; i++)
   entiers[i] = other.entiers[i];
  first = other.first;
  firstFree = other.firstFree;
 }

 private int incrementeIndice(int i)
 {
  i++;
  if (i == entiers.length)
   i = 0;
  return i;
 }

 public boolean estPlein()
 {
  return first == firstFree + 1;
 }

 public boolean estVide()
 {
  return first == incrementeIndice(firstFree);
 }

 public void enfile(int n)
 {
  if (!estPlein())
  {
   entiers[firstFree] = n;
   firstFree = incrementeIndice(firstFree);
  }
 }

 public void defile()
 {
  if (!estVide())
   first = incrementeIndice(first);
 }

 public int premier()
 {
  if (!estVide())
   return entiers[first];
  return 0;
 }
}

Télécharger le fichier

On remarque qu’il y a deux constructeurs. L’un prend la taille du tableau en paramètre et l’autre est un constructeur de copie. Selon le type de paramètre passé au moment de l’instanciation, le constructeur correspondant est exécuté. Dans l’exemple ci-dessous, on crée une file g en applicant le constructeur de copie à la file f.

package encapsulation;

public class TestFileSurcharge
{
 public static void main(String[] args)
 {
  FileSurcharge f = new FileSurcharge(20);
  for (int i = 0; i < 30; i += 2)
  {
   f.enfile(i);
   f.enfile(i + 1);
   System.out.println(f.premier());
   f.defile();
  }
  FileSurcharge g = new FileSurcharge(f);
  while (!f.estVide())
  {
   System.out.println(f.premier());
   f.defile();
  }
  while (!g.estVide())
  {
   System.out.println(g.premier());
   g.defile();
  }
 }
}

Télécharger le fichier

Visibilité package

En l’absence de mots-clés public (tout le monde), private (seulement la classe) et protected (classe + classes dérivées), une visibilité par défaut est appliquée. Par défaut, la visibilité des éléments est limitée au package.

1.10.6  Collections

Exemple

Considérons l’exemple suivant :

package encapsulation;

public class NativeArray
{
 public static void main(String[] args)
 {
  int taille = (47 - 2) / 3 + 1;
  int[] a = new int[taille];
  for (int value = 2, index = 0; value < 50; value += 3, index++)
   a[index] = value;
  for (int index = 0; index < taille; index++)
  {
   int valeur = a[index];
   System.out.println(valeur);
  }
 }

}

Télécharger le fichier

Dans ce programme, les valeurs entières 2, 5, 8, …, 47 sont placées dans un tableau puis affichées. Vous remarquerez que la principale difficulté est liée à l’utilisation des indices.

Il existe des classes en Java permettant de s’affranchir de ce type d’inconvénient en prenant en charge les problèmes d’indices. Par exemple :

package encapsulation;

import java.util.ArrayList;

public class CollectionArray
{
 public static void main(String[] args)
 {
  ArrayList a = new ArrayList();
  for (int value = 2; value < 50; value += 3)
   a.add("a " + value);
  for (int index = 0; index < a.size(); index++)
  {
   int valeur = (int) a.get(index);
   System.out.println(valeur);
  }
 }
}

Télécharger le fichier

La classe ArrayList est un tableau dont la taille s’adapte au nombre de données contenues. Les méthodes suivantes ont été utilisées dans l’exemple ci-avant :

Types paramétrés

L’utilisation des collections se paye avec des instructions peu élégantes :

package encapsulation;

import java.util.ArrayList;

public class Cast
{
 public static void main(String[] args)
 {
  ArrayList a = new ArrayList();
  a.add("toto");
  String s = (String) a.get(0);
  System.out.println(s);
  a.add(5);
  int i = (int) a.get(1);
  System.out.println(i);
 }
}

Télécharger le fichier

Il est nécessaire d’effectuer un cast particulièrement laid pour passer la compilation. Les types paramétrés fournissent un moyen élégant de résoudre ce problème.

Utilisation

Lorsque l’on déclare une référence vers un type paramétré, on doit préciser au moment de la déclaration le type que l’on souhaite utiliser. Si par exemple, on souhaite créer un ArrayList ne pouvant contenir que des objets de type String, la référence devra être du type ArrayList<String>.

package encapsulation;

import java.util.ArrayList;

public class ArrayListParametre
{
 public static void main(String[] args)
 {
  ArrayList<Integer> a = new ArrayList<Integer>();
  for (int value = 2; value < 50; value += 3)
   a.add(value);
  for (int index = 0; index < a.size(); index++)
   System.out.println(a.get(index));
 }
}

Télécharger le fichier

Il est possible lors d’une instanciation, d’omettre les le type, java prendra automatiquement le type correct.

Parcours

Il existe, de façon analogue à que l’on observe en C++, une syntaxe permettant simplement de parcourir une collection qui est :

for (T x : c)
{
  /* ... */
}

Dans la spécification ci dessus, c est la collection, T le type (ou un sur-type) des objets de la collection et x une variable (muette) dans laquelle sera placé tour à tour chaque objet de la collection.

package encapsulation;

import java.util.ArrayList;

public class ArrayListForEach
{
 public static void main(String[] args)
 {
  ArrayList<Integer> a = new ArrayList<>();
  for (int value = 2; value < 50; value += 3)
   a.add(value);
  for (int value : a)
   System.out.println(value);
 }
}

Télécharger le fichier