Conception avec des membres statiques

Bien que Java soit dans une large mesure orienté objet, ce n'est pas un pur langage orienté objet. L'une des raisons pour lesquelles Java n'est pas purement orienté objet est que tout ce qu'il contient n'est pas un objet. Par exemple, Java vous permet de déclarer des variables de types primitifs ( int, float, boolean, etc.) qui ne sont pas des objets. Et Java a des champs et des méthodes statiques, qui sont indépendants et séparés des objets. Cet article donne des conseils sur l'utilisation des champs et des méthodes statiques dans un programme Java, tout en conservant une focalisation orientée objet dans vos conceptions.

La durée de vie d'une classe dans une machine virtuelle Java (JVM) présente de nombreuses similitudes avec la durée de vie d'un objet. Tout comme un objet peut avoir un état, représenté par les valeurs de ses variables d'instance, une classe peut avoir un état, représenté par les valeurs de ses variables de classe. Tout comme la JVM définit les variables d'instance sur les valeurs initiales par défaut avant d'exécuter le code d'initialisation, la JVM définit les variables de classe sur les valeurs initiales par défaut avant d'exécuter le code d'initialisation. Et comme les objets, les classes peuvent être récupérées si elles ne sont plus référencées par l'application en cours d'exécution.

Néanmoins, des différences significatives existent entre les classes et les objets. La différence la plus importante est peut-être la manière dont les méthodes d'instance et de classe sont appelées: les méthodes d'instance sont (pour la plupart) liées dynamiquement, mais les méthodes de classe sont liées statiquement. (Dans trois cas particuliers, les méthodes d'instance ne sont pas liées dynamiquement: appel de méthodes d'instance privée, appel de initméthodes (constructeurs) et invocations avec le supermot - clé. Voir Ressources pour plus d'informations.)

Une autre différence entre les classes et les objets est le degré de masquage des données accordé par les niveaux d'accès privés. Si une variable d'instance est déclarée privée, seules les méthodes d'instance peuvent y accéder. Cela vous permet de garantir l'intégrité des données d'instance et de rendre les objets thread-safe. Le reste du programme ne peut pas accéder directement à ces variables d'instance, mais doit passer par les méthodes d'instance pour manipuler les variables d'instance. Dans le but de faire en sorte qu'une classe se comporte comme un objet bien conçu, vous pouvez rendre les variables de classe privées et définir des méthodes de classe qui les manipulent. Néanmoins, vous n'obtenez pas une aussi bonne garantie de sécurité des threads ou même d'intégrité des données de cette manière, car un certain type de code a un privilège spécial qui leur donne un accès direct aux variables de classe privées: méthodes d'instance,et même les initialiseurs de variables d'instance, peuvent accéder directement à ces variables de classe privée.

Ainsi, les champs statiques et les méthodes des classes, bien que similaires à bien des égards aux champs d'instance et aux méthodes des objets, présentent des différences significatives qui devraient affecter la façon dont vous les utilisez dans les conceptions.

Traiter les classes comme des objets

Lorsque vous concevez des programmes Java, vous rencontrerez probablement de nombreuses situations dans lesquelles vous ressentirez le besoin d'un objet qui agit à certains égards comme une classe. Vous pouvez, par exemple, vouloir un objet dont la durée de vie correspond à celle d'une classe. Ou vous pouvez vouloir un objet qui, comme une classe, se limite à une seule instance dans un espace de nom donné.

Dans des situations de conception telles que celles-ci, il peut être tentant de créer une classe et de l'utiliser comme un objet afin de définir des variables de classe, de les rendre privées et de définir des méthodes de classe publiques qui manipulent les variables de classe. Comme un objet, une telle classe a un état. Comme un objet bien conçu, les variables qui définissent l'état sont privées et le monde extérieur ne peut affecter cet état qu'en invoquant les méthodes de classe.

Malheureusement, certains problèmes existent avec cette approche «classe comme objet». Étant donné que les méthodes de classe sont liées statiquement, votre classe en tant qu'objet ne bénéficiera pas des avantages de flexibilité du polymorphisme et de l'upcasting. (Pour les définitions du polymorphisme et de la liaison dynamique, consultez l'article Techniques de conception, Composition versus héritage.) Le polymorphisme est rendu possible et la conversion ascendante utile par liaison dynamique, mais les méthodes de classe ne sont pas liées dynamiquement. Si quelqu'un sous-classe votre classe en tant qu'objet, il ne pourra pas remplacer vos méthodes de classe en déclarant des méthodes de classe du même nom; ils ne pourront que se cacherleur. Lorsqu'une de ces méthodes de classe redéfinies est appelée, la JVM sélectionne l'implémentation de la méthode à exécuter non pas par la classe d'un objet au moment de l'exécution, mais par le type d'une variable au moment de la compilation.

De plus, la sécurité des threads et l'intégrité des données obtenues par votre implémentation méticuleuse des méthodes de classe dans votre classe en tant qu'objet sont comme une maison construite en paille. La sécurité de votre thread et l'intégrité des données seront garanties tant que tout le monde utilise les méthodes de classe pour manipuler l'état stocké dans les variables de classe. Mais un programmeur imprudent ou désemparé pourrait, avec l'ajout d'une méthode d'instance qui accède directement à vos variables de classe privées, souffler et souffler par inadvertance et anéantir la sécurité des threads et l'intégrité des données.

Pour cette raison, ma principale directive concernant les variables de classe et les méthodes de classe est:

Ne traitez pas les classes comme des objets.

En d'autres termes, ne concevez pas avec des champs et des méthodes statiques d'une classe comme s'il s'agissait des champs d'instance et des méthodes d'un objet.

Si vous voulez un état et un comportement dont la durée de vie correspond à celle d'une classe, évitez d'utiliser des variables de classe et des méthodes de classe pour simuler un objet. Au lieu de cela, créez un objet réel et utilisez une variable de classe pour y contenir une référence et des méthodes de classe pour fournir l'accès à la référence d'objet. Si vous voulez vous assurer qu'une seule instance d'un état et d'un comportement existe dans un seul espace de noms, n'essayez pas de concevoir une classe qui simule un objet. Au lieu de cela, créez un singleton - un objet garanti pour n'avoir qu'une seule instance par espace de nom.

Alors, à quoi servent les élèves?

À mon avis, le meilleur état d'esprit à cultiver lors de la conception de programmes Java est de penser objets, objets, objets. Concentrez-vous sur la conception de grands objets et considérez les classes principalement comme des plans pour les objets - la structure dans laquelle vous définissez les variables d'instance et les méthodes d'instance qui composent vos objets bien conçus. En plus de cela, vous pouvez penser que les classes fournissent quelques services spéciaux que les objets ne peuvent pas fournir, ou ne peuvent pas fournir aussi élégamment. Pensez aux classes comme:

  • le bon endroit pour définir les «méthodes utilitaires» (méthodes qui prennent des entrées et fournissent des sorties uniquement via les paramètres passés et la valeur de retour)
  • un moyen de contrôler l'accès aux objets et aux données

Méthodes utilitaires

Les méthodes qui ne manipulent pas ou n'utilisent pas l'état d'un objet ou d'une classe, j'appelle des «méthodes utilitaires». Les méthodes utilitaires renvoient simplement une ou plusieurs valeurs calculées uniquement à partir des données transmises à la méthode en tant que paramètres. Vous devez rendre ces méthodes statiques et les placer dans la classe la plus étroitement liée au service fourni par la méthode.

Un exemple de méthode utilitaire est la String copyValueOf(char[] data)méthode de classe String. Cette méthode produit sa sortie, une valeur de retour de type String, uniquement à partir de son paramètre d'entrée, un tableau de chars. Comme copyValueOf()n'utilise ni n'affecte l'état d'aucun objet ou classe, il s'agit d'une méthode utilitaire. Et, comme toutes les méthodes utilitaires devraient l'être, copyValueOf()est une méthode de classe.

L'un des principaux moyens d'utiliser les méthodes de classe est donc les méthodes utilitaires - des méthodes qui renvoient une sortie calculée uniquement à partir de paramètres d'entrée. D'autres utilisations des méthodes de classe impliquent des variables de classe.

Variables de classe pour le masquage des données

L'un des préceptes fondamentaux de la programmation orientée objet est le masquage des données - restreindre l'accès aux données pour minimiser les dépendances entre les parties d'un programme. Si une donnée particulière a une accessibilité limitée, ces données peuvent changer sans casser les parties du programme qui ne peuvent pas accéder aux données.

Si, par exemple, un objet n'est nécessaire que par des instances d'une classe particulière, une référence à celui-ci peut être stockée dans une variable de classe privée. Cela donne à toutes les instances de cette classe un accès pratique à cet objet - les instances l'utilisent simplement directement - mais aucun autre code ailleurs dans le programme ne peut y accéder. De la même manière, vous pouvez utiliser l'accès au package et les variables de classe protégées pour réduire la visibilité des objets qui doivent être partagés par tous les membres d'un package et des sous-classes.

Les variables de classe publique sont une autre histoire. Si une variable de classe publique n'est pas définitive, c'est une variable globale: cette méchante construction qui est l'antithèse du masquage des données. Il n'y a jamais d'excuse pour une variable de classe publique, à moins qu'elle ne soit définitive.

Les variables finales de classe publique, qu'il s'agisse de type primitif ou de référence d'objet, ont un objectif utile. Les variables de type primitif ou de type Stringsont simplement des constantes, qui en général contribuent à rendre les programmes plus flexibles (plus faciles à modifier). Le code qui utilise des constantes est plus facile à modifier car vous pouvez modifier la valeur de la constante en un seul endroit. Les variables de classe finale publique des types référence vous permettent de donner un accès global aux objets qui sont nécessaires globalement. Par exemple, System.in, System.outet System.errsont des variables de classe finales publiques qui donnent un accès global à la sortie de l' entrée standard et flux d' erreur.

Ainsi, la principale façon de voir les variables de classe est comme un mécanisme pour limiter l'accessibilité (c'est-à-dire pour cacher) des variables ou des objets. Lorsque vous combinez des méthodes de classe avec des variables de classe, vous pouvez implémenter des stratégies d'accès encore plus complexes.

Utilisation de méthodes de classe avec des variables de classe

En plus d'agir comme des méthodes utilitaires, les méthodes de classe peuvent être utilisées pour contrôler l'accès aux objets stockés dans des variables de classe - en particulier, pour contrôler la façon dont les objets sont créés ou gérés. Deux exemples de ce type de méthode de classe sont les méthodes setSecurityManager()et getSecurityManager()de classe System. Le gestionnaire de sécurité d'une application est un objet qui, comme les flux d'entrée, de sortie et d'erreurs standard, est nécessaire à de nombreux endroits différents. Contrairement aux objets de flux d'E / S standard, cependant, une référence au gestionnaire de sécurité n'est pas stockée dans une variable de classe finale publique. L'objet du gestionnaire de sécurité est stocké dans une variable de classe privée et les méthodes set et get implémentent une politique d'accès spéciale pour l'objet.

Le modèle de sécurité de Java impose une restriction spéciale au gestionnaire de sécurité. Avant Java 2 (précédemment connu sous le nom de JDK 1.2), une application commençait sa vie sans aucun gestionnaire de sécurité ( getSecurityManager()renvoyé null). Le premier appel a setSecurityManager()établi le responsable de la sécurité, qui n'a pas été autorisé par la suite à changer. Tout appel ultérieur à setSecurityManager()produirait une exception de sécurité. Dans Java 2, l'application démarre toujours avec un gestionnaire de sécurité, mais à l'instar des versions précédentes, la setSecurityManager()méthode vous permettra de changer de gestionnaire de sécurité une fois au plus.

Le gestionnaire de sécurité fournit un bon exemple de la façon dont les méthodes de classe peuvent être utilisées conjointement avec des variables de classe privées pour implémenter une politique d'accès spéciale pour les objets référencés par les variables de classe. Mis à part les méthodes utilitaires, pensez aux méthodes de classe comme le moyen d'établir des politiques d'accès spéciales pour les références d'objets et les données stockées dans des variables de classe.

Des lignes directrices

Le principal conseil donné dans cet article est:

Ne traitez pas les classes comme des objets.

Si vous avez besoin d'un objet, créez un objet. Limitez votre utilisation des variables de classe et des méthodes à la définition de méthodes utilitaires et à la mise en œuvre de types spéciaux de stratégies d'accès pour les objets et les types primitifs stockés dans des variables de classe. Bien qu'il ne s'agisse pas d'un pur langage orienté objet, Java est néanmoins orienté objet dans une large mesure, et vos conceptions devraient en tenir compte. Pensez aux objets.

Le mois prochain

L'article du mois prochain sur les techniques de conception sera le dernier de cette chronique. Je vais bientôt commencer à écrire un livre basé sur le matériel Design Techniques, Flexible Java , et je placerai ce matériel sur mon site Web au fur et à mesure. Alors, suivez ce projet et envoyez-moi vos commentaires. Après une pause d'un mois ou deux, je serai de retour à JavaWorld et SunWorld avec une nouvelle chronique consacrée à Jini.

Une demande de participation des lecteurs

J'encourage vos commentaires, critiques, suggestions, flammes - toutes sortes de commentaires - sur le matériel présenté dans cette chronique. Si vous n'êtes pas d'accord avec quelque chose ou avez quelque chose à ajouter, faites-le moi savoir.

Vous pouvez participer à un forum de discussion consacré à ce matériel, saisir un commentaire via le formulaire en bas de l'article, ou m'envoyer un e-mail directement en utilisant le lien fourni dans ma bio ci-dessous.

Bill Venners écrit des logiciels de manière professionnelle depuis 12 ans. Basé dans la Silicon Valley, il fournit des services de conseil et de formation en logiciels sous le nom d'Artima Software Company. Au fil des ans, il a développé des logiciels pour les secteurs de l'électronique grand public, de l'éducation, des semi-conducteurs et de l'assurance-vie. Il a programmé dans de nombreux langages sur de nombreuses plateformes: langage d'assemblage sur différents microprocesseurs, C sous Unix, C ++ sous Windows, Java sur le Web. Il est l'auteur du livre Inside the Java Virtual Machine, publié par McGraw-Hill.