Membres privés et membres publics dans la pratique (quelle est l'importance de l'encapsulation?) [Fermé]

StackOverflow https://stackoverflow.com/questions/99688

  •  01-07-2019
  •  | 
  •  

Question

L’encapsulation est l’un des plus grands avantages de la programmation orientée objet et l’une des "vérités". On nous a appris (ou du moins je l’ai appris) que les membres devraient toujours être privés et rendus accessibles via des méthodes d’accesseur et de mutateur, garantissant ainsi la possibilité de vérifier et de valider les modifications.

Je suis curieux de savoir à quel point cela est important dans la pratique. En particulier, si vous avez un membre plus compliqué (comme une collection), il peut être très tentant de le rendre public plutôt que de créer un tas de méthodes pour obtenir les clés de la collection, ajouter / supprimer des éléments de la collection, etc.

Suivez-vous la règle en général? Votre réponse change-t-elle selon que le code est écrit pour vous-même ou pour être utilisé par d'autres? Y a-t-il des raisons plus subtiles qui me manquent pour cette obfuscation?

Était-ce utile?

La solution

Cela dépend. C’est l’une de ces questions qui doivent être réglées de manière pragmatique.

Supposons que j'ai une classe pour représenter un point. Je pouvais avoir des getters et des setters pour les coordonnées X et Y, ou simplement les rendre publics et autoriser un accès en lecture / écriture gratuit aux données. À mon avis, cela ne pose pas de problème car la classe agit comme une structure glorifiée - une collection de données avec peut-être quelques fonctions utiles attachées.

Cependant, il existe de nombreuses circonstances dans lesquelles vous ne souhaitez pas fournir un accès complet à vos données internes et utilisez les méthodes fournies par la classe pour interagir avec l'objet. Un exemple serait une requête HTTP et une réponse. Dans ce cas, il est déconseillé de permettre à quiconque d’envoyer quoi que ce soit sur le réseau - cela doit être traité et formaté par les méthodes de classe. Dans ce cas, la classe est conçue comme un objet réel et non comme un simple magasin de données.

Cela dépend vraiment de savoir si les verbes (méthodes) contrôlent la structure ou non, si les données le sont.

Autres conseils

En tant que personne obligée de conserver un code vieux de plusieurs années sur lequel beaucoup de personnes ont travaillé, il est très clair pour moi que si un attribut de membre est rendu public, il est finalement maltraité. J'ai même entendu des personnes en désaccord avec l'idée des accesseurs et des mutateurs, car cela ne correspond toujours pas à l'objectif d'encapsulation, qui consiste à "cacher le fonctionnement interne d'une classe". C’est évidemment un sujet controversé, mais mon opinion serait de "rendre chaque variable membre privée, pensez principalement à ce que la classe a à faire (méthodes) plutôt qu’à comment allons laisser les gens changer les variables internes ".

Oui, l’encapsulation est importante. Exposer l’implémentation sous-jacente comporte au moins deux erreurs:

  1. Confondez les responsabilités. Les appelants ne devraient ni avoir besoin ni vouloir comprendre l’implémentation sous-jacente. Ils devraient juste vouloir que la classe fasse son travail. En exposant l'implémentation sous-jacente, votre classe ne fait pas son travail. Au lieu de cela, il ne fait que pousser la responsabilité sur l'appelant.
  2. vous lie à l'implémentation sous-jacente. Une fois que vous avez exposé l'implémentation sous-jacente, vous y êtes lié. Si vous dites aux appelants, par exemple, qu’une collection se trouve en dessous, vous ne pourrez pas facilement échanger la collection contre une nouvelle mise en œuvre.

Ces problèmes (et d’autres) s’appliquent que vous donniez un accès direct à l’implémentation sous-jacente ou que vous dupliquiez simplement toutes les méthodes sous-jacentes. Vous devriez exposer la mise en œuvre nécessaire, et rien de plus. Garder la mise en œuvre privée rend le système global plus facile à gérer.

Je préfère garder les membres privés le plus longtemps possible et n’y accéder que par des accesseurs, même à l’intérieur de la même classe. J'essaie également d'éviter les setters comme premier brouillon pour promouvoir les objets de style valeur aussi longtemps que cela est possible. Lorsque vous utilisez souvent l’injection de dépendance, vous avez souvent des paramètres, mais pas d’objet d’accès, car les clients devraient pouvoir configurer l’objet, mais (les autres) ne peuvent pas savoir ce qui est configuré réellement, il s’agit là d’un détail d’implémentation.

Cordialement, Ollie

J'ai tendance à suivre la règle assez strictement, même s'il ne s'agit que de mon propre code. J'aime vraiment les propriétés en C # pour cette raison. Il est très facile de contrôler les valeurs qui lui sont données, mais vous pouvez toujours les utiliser comme variables. Ou rendre le jeu privé et le rendre public, etc.

Fondamentalement, masquer des informations concerne la clarté du code. Il est conçu pour permettre à une autre personne d'étendre votre code plus facilement et de l'empêcher de créer accidentellement des bogues lors de l'utilisation des données internes de vos classes. Il repose sur le principe suivant: personne ne lit jamais les commentaires , en particulier ceux qui contiennent des instructions.

Exemple: j'écris du code qui met à jour une variable et je dois absolument m'assurer que l'interface utilisateur change pour refléter le changement. Le moyen le plus simple consiste à ajouter une méthode d'accès. "Setter"), appelé au lieu de mettre à jour, les données sont mises à jour.

Si je rends ces données publiques et que quelque chose change la variable sans passer par la méthode Setter (et cela se produit chaque fois que le mot est juré), quelqu'un devra passer une heure à déboguer pour savoir pourquoi les mises à jour ne sont pas. être affiché. Il en va de même, dans une moindre mesure, pour "Obtenir". Les données. Je pourrais mettre un commentaire dans le fichier d'en-tête, mais il y a de fortes chances que personne ne le lise avant que quelque chose ne se passe terriblement mal. Le fait de l'appliquer de manière privée signifie que l'erreur ne peut pas être , car elle apparaîtra comme un bogue de compilation facilement localisé, plutôt que comme un bogue d'exécution.

Par expérience, les seules fois où vous voudriez rendre une variable membre publique, en laissant de côté les méthodes Getter et Setter, sont si vous voulez indiquer clairement que le fait de changer cela n'aura aucun effet secondaire; surtout si la structure de données est simple, comme une classe qui contient simplement deux variables sous la forme d'une paire.

Cela devrait être une occurrence assez rare, car normalement vous voudriez des effets secondaires, et si la structure de données que vous créez est si simple que vous n'en avez pas (par exemple, une association), il en existe déjà une plus efficace dans une bibliothèque standard.

Cela dit, pour la plupart des petits programmes sans extension, comme ceux que vous recevez à l'université, il s'agit davantage d'une "bonne pratique". que tout, parce que vous vous en souviendrez au cours de leur rédaction, puis vous les remettrez et ne toucherez plus jamais le code. En outre, si vous écrivez une structure de données pour découvrir comment ils stockent les données plutôt que comme un code de publication, il existe un argument de poids selon lequel Getters and Setters ne vous aidera pas et entravera votre expérience d'apprentissage. .

Ce n'est que lorsque vous arrivez sur le lieu de travail ou dans un grand projet, où la probabilité est que votre code soit appelé par des objets et des structures écrits par différentes personnes, qu'il devient vital de créer ces "rappels". fort. Qu'il s'agisse ou non d'un projet à un seul homme est étonnamment indifférent, pour la simple raison que "vous êtes dans six semaines". est une personne aussi différente qu'un collègue. Et "moi il y a six semaines" s'avère souvent être paresseux.

Un dernier point est que certaines personnes sont assez zélées pour la dissimulation d’informations et seront ennuyées si vos données sont inutilement publiques. Il est préférable de les humilier.

Les propriétés C # 'simulent' des champs publics. C'est plutôt cool et la syntaxe accélère vraiment la création de ces méthodes get / set

Gardez à l'esprit la sémantique d'appeler des méthodes sur un objet. Une invocation de méthode est une abstraction de très haut niveau qui peut être implémentée par le compilateur ou le système d’exécution de différentes manières.

Si l'objet qui est la méthode que vous appelez existe dans le même processus / mappage de mémoire, une méthode pourrait bien être optimisée par un compilateur ou une machine virtuelle pour accéder directement au membre de données. D'autre part, si l'objet réside sur un autre nœud d'un système distribué, vous ne pouvez pas accéder directement à ses membres de données internes, mais vous pouvez toujours invoquer ses méthodes pour lui envoyer un message.

En codant sur des interfaces, vous pouvez écrire du code qui ne tient pas compte de l'emplacement de l'objet cible ni de la façon dont ses méthodes sont invoquées, ni même de son écriture dans le même langage.

Dans votre exemple d'objet qui implémente toutes les méthodes d'une collection, cet objet est sûrement en réalité une collection. alors peut-être que ce serait un cas où l'héritage serait mieux que l'encapsulation.

Il s’agit de contrôler ce que les gens peuvent faire avec ce que vous leur donnez. Plus vous maîtrisez, plus vous pouvez formuler d’hypothèses.

De même, théoriquement, vous pouvez modifier l’implémentation sous-jacente ou autre chose, mais puisque pour l’essentiel, c’est:

private Foo foo;
public Foo getFoo() {}
public void setFoo(Foo foo) {}

C'est un peu difficile à justifier.

L'encapsulation est importante lorsqu'au moins une des actions suivantes est maintenue:

  1. Toute personne autre que vous utilisera votre classe (ou cassera vos invariants car elle ne lit pas la documentation).
  2. Quiconque ne lira pas la documentation utilisera votre classe (ou cassera vos invariants soigneusement documentés). Notez que cette catégorie vous inclut dans deux ans.
  3. Dans le futur, quelqu'un héritera de votre classe (car une action supplémentaire doit peut-être être entreprise lorsque la valeur d'un champ change, il doit donc y avoir un séparateur).

Si c'est juste pour moi, et utilisé dans quelques endroits, et que je ne vais pas en hériter, et que le changement de champs n'invalidera aucun invariant supposé par la classe, alors seulement I rendra parfois un champ public.

J'ai tendance à essayer de tout rendre privé si possible. Cela permet de définir le plus clairement possible les limites des objets et de les découpler le plus possible. Cela me plaît parce que, lorsque je dois réécrire un objet que j’ai bâclé la première (deuxième, cinquième?) Fois, les dommages sont limités à un nombre réduit d’objets.

Si vous couplez les objets assez étroitement, il sera peut-être plus simple de les combiner en un seul objet. Si vous assouplissez suffisamment les contraintes de couplage, vous revenez à la programmation structurée.

Il se peut que, si vous trouvez qu'un groupe de vos objets ne sont que des fonctions d’accesseurs, vous devez repenser vos divisions d’objets. Si vous n'effectuez aucune action sur ces données, elles peuvent appartenir à un autre objet.

Bien sûr, si vous écrivez quelque chose comme une bibliothèque, vous voulez une interface aussi claire et précise que possible afin que d'autres puissent la programmer.

Adapter l'outil à la tâche ... Récemment, j'ai vu du code comme celui-ci dans ma base de code actuelle:

private static class SomeSmallDataStructure {
    public int someField;
    public String someOtherField;
}

Ensuite, cette classe a été utilisée en interne pour transmettre facilement plusieurs valeurs de données. Cela n’a pas toujours un sens, mais si vous n’avez que DATA, sans méthodes, et que vous ne l’exposez pas aux clients, je trouve que c’est un modèle assez utile.

L’utilisation la plus récente de cette page était une page JSP dans laquelle une table de données était affichée, définie en haut de façon déclarative. Donc, au départ, c’était dans plusieurs tableaux, un tableau par champ de données ... cela se terminait par le fait qu’il était assez difficile de parcourir le code avec des champs qui n’étaient pas côte à côte dans la définition et qui seraient affichés ensemble ... classe comme ci-dessus qui le rassemblerait ... le résultat était un code VRAIMENT lisible, bien plus qu’avant.

Moral ... vous devez parfois considérer "accepté comme mauvais". Si elles simplifient le code et le rendent plus lisible, si vous y réfléchissez bien et si vous envisagez les conséquences, n'acceptez pas aveuglément TOUT ce que vous entendez.

Cela dit ... les accesseurs et les setters publics sont à peu près équivalents aux terrains publics ... du moins en substance (il y a un peu plus de flexibilité, mais c'est toujours un mauvais modèle à appliquer à CHAQUE terrain que vous avez).

Même les bibliothèques standard java ont des cas de champs publics .

Lorsque je donne du sens aux objets, ils sont plus faciles à utiliser et plus faciles à gérer .

Par exemple: Person.Hand.Grab (howquick, howmuch);

Le truc consiste à ne pas considérer les membres comme de simples valeurs, mais des objets en eux-mêmes.

Je dirais que cette question confond le concept d'encapsulation avec le "masquage d'informations"

(Il ne s'agit pas d'une critique, car cela semble correspondre à une interprétation commune de la notion d '"encapsulation".)

Cependant, pour moi, "encapsulation" est soit:

  • le processus de regroupement de plusieurs éléments dans un conteneur
  • le conteneur lui-même regroupant les éléments

Supposons que vous concevez un système de contribuable. Pour chaque contribuable, vous pouvez incorporer la notion d’enfant dans

.
  • une liste d'enfants représentant les enfants
  • une carte de à prend en compte les enfants de différents parents
  • un objet Children (not Child) qui fournirait les informations nécessaires (comme le nombre total d'enfants)

Vous avez ici trois types différents d'encapsulations, 2 représentées par un conteneur de bas niveau (liste ou carte), une par un objet.

En prenant ces décisions, vous ne

  • rendre cette encapsulation publique ou protégée ou privée: le choix de "masquage d'informations" doit encore être effectué
  • faites une abstraction complète (vous devez affiner les attributs de l'objet Children et vous pouvez décider de créer un objet Child, qui ne conserverait que les informations pertinentes du point de vue d'un système de contribuable)

    L'abstraction est le processus qui consiste à choisir quels attributs de l'objet sont pertinents pour votre système et lesquels doivent être complètement ignorés.

Donc, mon point est:
Cette question peut être intitulée:
Membres privés et membres publics dans la pratique (quelle est l’importance de cacher des informations ?)

Seulement mes 2 centimes, cependant. Je respecte tout à fait le fait que l'on puisse considérer l'encapsulation comme un processus incluant une décision de "dissimulation d'informations".

Cependant, j'essaie toujours de différencier "abstraction" - "encapsulation" - "masquage d'informations ou visibilité".

@VonC

Vous trouverez peut-être le "Modèle de référence du traitement distribué ouvert" de l'Organisation internationale de normalisation, " une lecture intéressante. Elle définit: "Encapsulation: la propriété selon laquelle les informations contenues dans un objet ne sont accessibles que par le biais d'interactions aux interfaces prises en charge par l'objet."

J'ai essayé de démontrer que la dissimulation d'informations constituait un élément essentiel de cette définition: http://www.edmundkirwan.com/encap/s2.html

Cordialement,

Ed.

Je trouve que beaucoup de getters et de setters constituent un odeur de code indiquant que la structure du programme n'est pas bien conçu. Vous devriez regarder le code qui utilise ces accesseurs et ces setters, et chercher une fonctionnalité qui devrait vraiment faire partie de la classe. Dans la plupart des cas, les champs d'une classe doivent être des détails d'implémentation privés et seules les méthodes de cette classe peuvent les manipuler.

Avoir à la fois des getters et des setters équivaut à rendre le champ public (lorsque les getters et les setters sont triviaux / générés automatiquement). Parfois, il peut être préférable de simplement déclarer les champs publics afin que le code soit plus simple, sauf si vous avez besoin de polymorphisme ou si une structure requiert des méthodes get / set (et vous ne pouvez pas modifier la structure).

Mais il y a aussi des cas où avoir des getters et des setters est un bon modèle. Un exemple:

Lorsque je crée l'interface graphique d'une application, j'essaie de conserver le comportement de l'interface graphique dans une classe (FooModel), de manière à ce qu'il puisse être testé par unité facilement et que la visualisation de l'interface graphique soit dans une autre classe (FooView). peut être testé que manuellement. La vue et le modèle sont joints avec un code de colle simple; lorsque l'utilisateur modifie la valeur du champ x , la vue appelle setX (String) sur le modèle, ce qui peut déclencher un événement qu'une autre partie du modèle a changé, et la vue obtiendra les valeurs mises à jour du modèle avec des accesseurs.

Dans l’un des projets, il existe un modèle d’interface graphique qui comporte 15 méthodes d’accès et d’installation, dont 3 seulement sont simples (de sorte que l’EDI puisse les générer). Tous les autres contiennent des fonctionnalités ou des expressions non triviales, telles que:

public boolean isEmployeeStatusEnabled() {
    return pinCodeValidation.equals(PinCodeValidation.VALID);
}

public EmployeeStatus getEmployeeStatus() {
    Employee employee;
    if (isEmployeeStatusEnabled()
            && (employee = getSelectedEmployee()) != null) {
        return employee.getStatus();
    }
    return null;
}

public void setEmployeeStatus(EmployeeStatus status) {
    getSelectedEmployee().changeStatusTo(status, getPinCode());
    fireComponentStateChanged();
}

En pratique, je ne respecte qu'une seule règle, la règle "Aucune taille ne convient à tous". règle.

L’encapsulation et son importance sont un produit de votre projet. Quel objet accédera à votre interface, comment vont-ils l'utiliser, importera-t-il s'ils ont des droits d'accès inutiles aux membres? vous devez vous poser ces questions et ce genre de choses lorsque vous travaillez sur la mise en œuvre de chaque projet.

Je base ma décision sur la profondeur du code dans un module. Si j'écris du code interne à un module et n'interfère pas avec le monde extérieur, je n'encapsule pas autant de choses avec private car cela affecte les performances de mon programmeur (à quelle vitesse je peux écrire et réécrire mon code).

Mais pour les objets qui servent de serveur en tant qu'interface du module avec le code utilisateur, je respecte des règles de confidentialité strictes.

Il est certain que le fait d’écrire du code interne ou un code destiné à une autre personne (ou même à vous-même, mais en tant qu’unité confinée) fait toute la différence. Tout code qui sera utilisé en externe doit avoir un code bien défini / documenté. interface que vous voudrez changer le moins possible.

Pour le code interne, selon la difficulté, vous constaterez peut-être moins de travail de faire les choses simplement maintenant et de payer une petite pénalité plus tard. Bien sûr, la loi de Murphy garantira que le gain à court terme sera effacé à maintes reprises, car il vous faudra apporter de vastes modifications plus tard, lorsque vous aurez besoin de modifier les éléments internes d'une classe que vous n'avez pas réussi à encapsuler.

En ce qui concerne plus particulièrement votre exemple d'utilisation d'une collection que vous renverriez, il semble possible que la mise en oeuvre d'une telle collection puisse changer (contrairement aux variables de membre plus simples), ce qui rend l'utilitaire d'encapsulation plus performant.

Cela étant dit, j'aime un peu la façon dont Python s'en occupe. Les variables membres sont publiques par défaut. Si vous souhaitez les masquer ou ajouter une validation, des techniques sont fournies, mais elles sont considérées comme des cas particuliers.

Je suis les règles à ce sujet presque tout le temps. Pour moi, il existe quatre scénarios: la règle elle-même et plusieurs exceptions (toutes influencées par Java):

  1. Utilisable par n'importe quoi en dehors de la classe actuelle, accessible via des accesseurs / configurateurs
  2. Une utilisation interne à la classe, généralement précédée de 'this' pour indiquer clairement que ce n'est pas un paramètre de méthode
  3. Quelque chose qui devait rester extrêmement petit, comme un objet de transport - consiste essentiellement en une série d'attributs; tout public
  4. Nécessité d'être non privé pour une extension quelconque

Il existe un problème concret auquel la plupart des réponses existantes ne répondent pas. L'encapsulation et l'exposition d'interfaces propres et sûres au code extérieur sont toujours bonnes, mais elles sont beaucoup plus importantes lorsque le code que vous écrivez est destiné à être consommé par un "utilisateur" spatialement et / ou temporellement important. base. Ce que je veux dire, c'est que si vous envisagez que quelqu'un (même vous) maintienne le code longtemps à l'avenir, ou si vous écrivez un module qui interfacera avec le code de plus d'une poignée d'autres développeurs, vous devrez réfléchir beaucoup plus loin. avec soin que si vous écrivez un code unique ou entièrement écrit par vous.

Honnêtement, je sais de quelle mauvaise pratique de génie logiciel il s’agit, mais je vais souvent tout rendre public au début, ce qui rend les choses un peu plus rapides à mémoriser et dactylographier, puis ajoute l’encapsulation comme cela a du sens. Les outils de refactoring de la plupart des IDE les plus populaires rendent l’approche que vous utilisez (ajouter l’encapsulation ou la retirer) beaucoup moins pertinente qu’elle ne l’était auparavant.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top