IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java

  Chapitre 5 - Cacher l'implémentation

pages : 1 2 3 

Le contrôle d'accès est souvent appelé cacher l'implémentation [implementation hiding] . L'enveloppement [wrapping] des données et méthodes à l'intérieur des classes combiné au masquage de l'implémentation est souvent appelé encapsulation [34]. Le résultat est un type de données avec des caractéristiques et des comportements.

Le contrôle d'accès pose des limites sur un type de données pour deux raisons importantes. La première est de déterminer ce que les programmeurs clients peuvent et ne peuvent pas utiliser. On peut construire les mécanismes internes dans la structure sans se préoccuper du risque que les programmeurs clients prennent ces mécanismes internes comme faisant partie de l'interface qu'ils doivent utiliser.

Ceci amène directement à la deuxième raison, qui est de séparer l'interface de son implémentation. Si la structure est utilisée dans un ensemble de programmes, mais que les programmeurs clients ne peuvent qu'envoyer des messages à l'interface public, alors on peut modifier tout ce qui n'est pas public (c'est à dire « amical », protected, ou private) sans que cela nécessite des modifications du code client.

Nous sommes maintenant dans le monde de la programmation orientée objet, dans lequel une class est en fait la description d' « une classe d'objets », comme on décrirait une classe des poissons ou une classe des oiseaux. Tout objet appartenant à cette classe partage ces caractéristiques et comportements. La classe est une description de la façon dont les objets de ce type vont nous apparaître et se comporter.

Dans le langage de POO d'origine, Simula-67 , le mot-clé class était utilisé pour décrire un nouveau type de données. Le même mot-clé a été repris dans la plupart des langages orientés objet. Ceci est le point central de tout le langage : la création de nouveaux types de données qui sont plus que simplement des boîtes contenant des données et des méthodes.

La classe est le concept de POO fondamental en Java. C'est l'un des mots-clés qui ne sera pas mis en gras dans ce livre, ça devient lourd pour un mot aussi souvent répété que « class ».

Pour plus de clarté, il est préférable d'utiliser un style de création des classes qui place les membres public au début, suivi par les membres protected, amicaux et private. L'avantage de ceci est que l'utilisateur de la classe peut voir ce qui est important pour lui (les membres public, parce qu'on peut y accéder de l'extérieur du fichier), en lisant depuis le début et en s'arrêtant lorsqu'il rencontre les membres non-public, qui font partie de l'implémentation interne :

public class X {
  public void pub1( ) { /* . . . */ }
  public void pub2( ) { /* . . . */ }
  public void pub3( ) { /* . . . */ }
  private void priv1( ) { /* . . . */ }
  private void priv2( ) {/* . . . */ }
  private void priv3( ) { /* . . . */ }
  private int i;
  // . . .
}

Ceci ne la rendra que partiellement plus lisible parce que l'interface et l'implémentation sont encore mélangés. C'est-à-dire qu'on voit toujours le code source (l'implémentation) parce qu'il est là dans la classe. Cependant, la documentation sous forme de commentaires supportée par javadoc (décrite au Chapitre 2) diminue l'importance de la lisibilité du code par le programmeur client. Afficher l'interface au consommateur d'une classe est normalement le travail du class browser, un outil dont le travail consiste à inspecter toutes les classes disponibles et de montrer ce qu'on peut en faire (c'est à dire quels membres sont disponibles) de façon pratique. Au moment où vous lisez ceci, les browsers devraient faire partie de tout bon outil de développement Java.

L'accès aux classes

En Java, les spécificateurs d'accès peuvent aussi être utilisés pour déterminer quelles classes d'une bibliothèque seront accessibles aux utilisateurs de cette bibliothèque. Si on désire qu'une classe soit disponible pour un programmeur client, on place le mot-clé public quelque part devant l'accolade ouvrante du corps de la classe. Ceci permet de contrôler le fait même qu'un programmeur client puisse créer un objet de cette classe.

Pour contrôler l'accès à la classe, le spécificateur doit apparaître avant le mot-clé class. Donc on peut dire :

public class Widget {

Maintenant, si le nom de la bibliothèque est mylib, tout programmeur client peut accéder à Widget en disant

import mylib.Widget;

ou

import mylib.*;

Il y a cependant un ensemble de contraintes supplémentaires :

  1. Il ne peut y avoir qu'une seule classe public par unité de compilation (fichier). L'idée est que chaque unité de compilation a une seule interface publique représentée par cette classe public . Elle peut avoir autant de classes « amicales » de support qu'on veut. Si on a plus d'une classe public dans une unité de compilation, le compilateur générera un message d'erreur.
  2. Le nom de la classe public doit correspondre exactement au nom du fichier contenant l'unité de compilation, y compris les majuscules et minuscules. Par exemple pour Widget, le nom du fichier doit être Widget.java, et pas widget.java ou WIDGET.java. Là aussi on obtient des erreurs de compilation s'ils ne correspondent pas.
  3. Il est possible, bien que non habituel, d'avoir une unité de compilation sans aucune classe public. Dans ce cas, on peut appeler le fichier comme on veut.

Que se passe-t-il si on a une classe dans mylib qu'on utilise uniquement pour accomplir les tâches effectuées par Widget ou une autre classe public de mylib ? On ne veut pas créer de documentation pour un programmeur client, et on pense que peut-être plus tard on modifiera tout et qu'on refera toute la classe en lui en substituant une nouvelle. Pour garder cette possibilité, il faut s'assurer qu'aucun programmeur client ne devienne dépendant des détails d'implémentation cachés dans mylib. Pour réaliser ceci il suffit d'enlever le mot-clé public de la classe, qui devient dans ce cas amicale. (Cette classe ne peut être utilisée que dans ce package.)

Remarquez qu'une classe ne peut pas être private (cela ne la rendrait accessible à personne d'autre que cette classe), ou protected [35]. Il n'y a donc que deux choix pour l'accès aux classes : « amical » ou public. Si on ne veut pas que quelqu'un d'autre accède à cette classe, on peut rendre tous les constructeurs private, ce qui empêche tout le monde de créer un objet de cette classe, à part soi-même dans un membre static de la classe [36]. Voici un exemple :

//: c05:Lunch.java
// Démontre les spécificateurs d'accès de classes.
// Faire une classe effectivement private
// avec des constructeurs private :
class Soup {
  private Soup() {}
  // (1) Permettre la création à l'aide d'une méthode static :
  public static Soup makeSoup() {
    return new Soup();
  }
  // (2) Créer un objet static et
  // retourner une référence à la demande.
  // (le patron "Singleton"):
  private static Soup ps1 = new Soup();
  public static Soup access() {
    return ps1;
  }
  public void f() {}
}

class Sandwich { // Utilise Lunch
  void f() { new Lunch(); }
}

// Une seule classe public autorisée par fichier :
public class Lunch {
  void test() {
// Ne peut pas faire ceci ! Constructeur privé :
    //! Soup priv1 = new Soup();
    Soup priv2 = Soup.makeSoup();
    Sandwich f1 = new Sandwich();
    Soup.access().f();
  }
} ///:~

Jusqu'ici, la plupart des méthodes retournaient soit void soit un type primitif, ce qui fait que la définition :

public static Soup access() {
    return ps1;
  }

pourrait paraître un peu confuse. Le mot devant le nom de la méthode (access) dit ce que retourne la méthode. Jusqu'ici cela a été le plus souvent void, ce qui signifie qu'elle ne retourne rien. Mais on peut aussi retourner la référence à un objet, comme c'est le cas ici. Cette méthode retourne une référence à un objet de la classe Soup.

La classe Soup montre comment empêcher la création directe d'une classe en rendant tous les constructeurs private. Souvenez-vous que si vous ne créez pas explicitement au moins un constructeur, le constructeur par défaut (un constructeur sans arguments) sera créé pour vous. En écrivant ce constructeur par défaut, il ne sera pas créé automatiquement. En le rendant private, personne ne pourra créer un objet de cette classe. Mais alors comment utilise-t-on cette classe ? L'exemple ci-dessus montre deux possibilités. Premièrement, une méthode static est créée, elle créee un nouveau Soup et en retourne la référence. Ceci peut être utile si on veut faire des opérations supplémentaires sur Soup avant de le retourner, ou si on veut garder un compteur du nombre d'objets Soup créés (peut-être pour restreindre leur population).

La seconde possibilité utilise ce qu'on appelle un patron de conception [design pattern], qui est expliqué dans Thinking in Patterns with Java, téléchargeable sur www.BruceEckel.com. Ce patron particulier est appelé un « singleton » parce qu'il n'autorise la création que d'un seul objet. L'objet de classe Soup est créé comme un membre static private de Soup, ce qui fait qu'il n'y en a qu'un seul, et on ne peut y accéder qu'à travers la méthode public access().

Comme mentionné précédemment, si on ne met pas de spécificateur d'accès il est « amical » par défaut. Ceci signifie qu'un objet de cette classe peut être créé par toute autre classe du package, mais pas en dehors du package (souvenez-vous que tous les fichiers dans le même répertoire qui n'ont pas de déclaration package explicite font implicitement partie du package par défaut pour ce répertoire). Cependant, si un membre static de cette classe est public, le programmeur client peut encore accéder à ce membre static même s'il ne peut pas créer un objet de cette classe.

Résumé

Dans toute relation il est important d'avoir des limites respectées par toutes les parties concernées. Lorsqu'on crée une bibliothèque, on établit une relation avec l'utilisateur de cette bibliothèque (le programmeur client) qui est un autre programmeur, mais qui construit une application ou qui utilise la bibliothèque pour créer une bibliothèque plus grande.

Sans règles, les programmeurs clients peuvent faire tout ce qu'ils veulent des membres de la classe, même si vous préféreriez qu'ils ne manipulent pas directement certains des membres. Tout est à découvert dans le monde entier.

Ce chapitre a décrit comment les classes sont construites pour former des bibliothèques ; d'abord, comment un groupe de classes est empaqueté [packaged]dans une bibliothèque, et ensuite comment la classe contrôle l'accès à ses membres.

On estime qu'un projet programmé en C commence à s'écrouler losqu'il atteint 50K à 100K de lignes de code, parce que le C a un seul « espace de nommage », et donc les noms commencent à entrer en conflit, provoquant un surcroît de gestion. En Java, le mot-clé package, le principe de nommage des packages et le mot-clé import donnent un contrôle complet sur les noms, et le problème de conflit de nommage est facilement évité.

Il y a deux raisons pour contrôler l'accès aux membres. La première est d'écarter les utilisateurs des outils qu'ils ne doivent pas utiliser ; outils qui sont nécessaires pour les traitements internes des types de données, mais qui ne font pas partie de l'interface dont les utilisateurs ont besoin pour résoudre leurs problèmes particuliers. Donc créer des méthodes et des champs private est un service rendu aux utilisateurs parce qu'ils peuvent facilement voir ce qui est important pour eux et ce qu'ils peuvent ignorer. Cela leur simplifie la compréhension de la classe.

La deuxième raison, et la plus importante, pour le contrôle d'accès, est de permettre au concepteur de bibliothèque de modifier les fonctionnements internes de la classe sans se soucier de la façon dont cela peut affecter le programmeur client. On peut construire une classe d'une façon, et ensuite découvrir qu'une restructuration du code améliorera grandement sa vitesse. Si l'interface et l'implémentation sont clairement séparées et protégées, on peut y arriver sans forcer l'utilisateur à réécrire son code.

Les spécificateurs d'accès en Java donnent un contrôle précieux au créateur d'une class. Les utilisateurs de la class peuvent voir clairement et exactement ce qu'ils peuvent utiliser et ce qu'ils peuvent ignorer. Encore plus important, on a la possiblité de garantir qu'aucun utilisateur ne deviendra dépendant d'une quelconque partie de l'implémentation d'une class. Sachant cela en tant que créateur d'une class, on peut en modifier l'implémentation en sachant qu'aucun programmeur client ne sera affecté par les modifications car il ne peut accéder à cette partie de la class.

Lorsqu'on a la possibilité de modifier l'implémentation, on peut non seulement améliorer la conception plus tard, mais on a aussi le droit de faire des erreurs . Quelles que soient les soins que vous apportez à votre planning et à votre conception, vous ferez des erreurs. Savoir qu'il est relativement peu dangereux de faire ces erreurs veut dire que vous ferez plus d'expériences, vous apprendrez plus vite, et vous finirez plus vite votre projet.

L'interface publique d'une classe est ce que l'utilisateur voit, donc c'est la partie qui doit être « correcte » lors de l'analyse et la conception. Et même ici vous avez encore quelques possibilités de modifications. Si vous n'avez pas la bonne interface du premier coup, vous pouvez ajouter des méthodes , pour autant que vous ne supprimiez pas celles que les programmeurs clients ont déjà utilisé dans leur code.

Exercices

Les solutions des exercices sélectionnés sont disponibles dans le document électronique The Thinking in Java Annotated Solution Guide, disponible à un prix raisonnable sur www.BruceEckel.com.

  1. Ecrivez un programme qui crée un objet ArrayList sans importer explicitement java.util.*.
  2. Dans la section nommée « package : l'unité de bibliothèque », transformez les fragments de code concernant mypackage en un ensemble de fichiers Java qui peuvent être compilés et qui tournent.
  3. Dans la section appelée « Collisions », prenez les fragments de code et transformez les en programme, et vérifiez qu'en fait les collisions arrivent.
  4. Généralisez la classe P définie dans ce chapitre en ajoutant toutes les versions surchargées de rint( ) et rintln( ) nécessaires pour gérer tous les types Java de base.
  5. Modifiez l'instruction import de TestAssert.java pour autoriser ou inhiber le mécanisme d'assertion.
  6. Créez une classe avec public, private, protected, et des membres de données et des méthodes « amicaux ». Faites attention au fait que des classes dans un même répertoire font partie d'un package « par défaut ».
  7. Créez une classe avec des données protected. Créez une deuxième classe dans le même fichier, qui a une méthode qui manipule les données protected de la première classe.
  8. Modifiez la classe Cookie comme spécifié dans la section "protected : sorte d'amical ». Vérifiez que bite() n'est pas public.
  9. Dans la section nommée « Accès aux classes » vous trouverez des fragments de code décrivant mylib etWidget. Créez cette bibliothèque, et ensuite créez un Widget dans une classe qui ne fait pas partie du package mylib.
  10. Créez un nouveau répertoire et modifiez votre CLASSPATH pour inclure ce nouveau répertoire. Copiez le fichier P.class (produit par la compilation de com.bruceeckel.tools.P.java) dans votre nouveau répertoire et changez ensuite le nom du fichier, la classe P à l'intérieur, et les noms de méthodes (vous pouvez aussi ajouter des sorties supplémentaires pour observer comment cela fonctionne). Créez un autre programme dans un autre répertoire, qui utilise votre nouvelle classe.
  11. En suivant la forme de l'exemple Lunch.java, créez une classe appelée ConnectionManager qui gère un tableau fixe d'objets Connection. Le programmeur client ne doit pas pouvoir créer explicitement des objets Connection, mais doit seulement pouvoir les obtenir à l'aide d'une méthode static dans ConnectionManager. Lorsque le ConnectionManager tombe à court d'objets, il retourne une référence null. Testez la classe dans main().
  12. Créez le fichier suivant dans le répertoire c05/local (supposé être dans votre CLASSPATH) :
  13. ///: c05:local:PackagedClass.java
    package c05.local;
    class PackagedClass {
      public PackagedClass() {
        System.out.println(
          "Creating a packaged class");
      }
    }///:~

    Créez enuite le fichier suivant dans un répertoire autre que c05 :

    ///: c05:foreign:Foreign.java
    package c05.foreign;
    import c05.local.*;
    public class Foreign {
       public static void main (String[] args) {
          PackagedClass pc = new PackagedClass();
       }
    } ///:~

    Expliquez pourquoi le compilateur génère une erreur. Le fait de mettre la classe Foreign dans le package c05.local changerait-il quelque chose ?

[32] Rien en Java n'oblige à utiliser un interpréteur. Il existe des compilateurs Java de code natif qui génèrent un seul fichier exécutable.

[33] Il y a un autre effet dans ce cas. Comme le constructeur par défaut est le seul défini, et qu'il est private, il empêchera l'héritage de cette classe. (Un sujet qui sera présenté dans le Chapitre 6.)

[34] Cependant, on parle souvent aussi d'encapsulation pour le seul fait de cacher l'implémentation.

[35] En fait, une classe interne[inner class] peut être private ou protected, mais il s'agit d'un cas particulier. Ceux-ci seront présentés au Chapitre 7.

[36] On peut aussi le faire en héritant (Chapitre 6) de cette classe.

Ce livre a été écrit par Bruce Eckel ( télécharger la version anglaise : Thinking in java )
Ce chapitre a été traduit par Phillipe Boite ( groupe de traduction )
télécharger la version francaise (PDF) | Commandez le livre en version anglaise (amazon) | télécharger la version anglaise
pages : 1 2 3 
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java