Pourquoi les champs privés sont-ils privés pour le type, pas pour l'instance?

153

En C # (et dans de nombreux autres langages), il est parfaitement légitime d'accéder aux champs privés d'autres instances du même type. Par exemple:

public class Foo
{
    private bool aBool;

    public void DoBar(Foo anotherFoo)
    {
        if (anotherFoo.aBool) ...
    }
}

Comme l'indique la spécification C # (sections 3.5.1, 3.5.2), l'accès aux champs privés se fait sur un type et non sur une instance. J'en ai discuté avec un collègue et nous essayons de trouver une raison pour laquelle cela fonctionne comme ça (plutôt que de restreindre l'accès à la même instance).

Le meilleur argument que nous pourrions trouver est pour les vérifications d'égalité où la classe peut vouloir accéder aux champs privés pour déterminer l'égalité avec une autre instance. Y a-t-il d'autres raisons? Ou une raison en or qui signifie absolument que cela doit fonctionner comme ça ou quelque chose serait complètement impossible?

RichK
la source
18
Quels sont les avantages de l'alternative?
Jon Skeet
19
Il est vrai qu'il ne modélise pas très bien le monde réel. Après tout, ce n'est pas parce que ma voiture peut signaler la quantité d'essence qu'il lui reste que ma voiture devrait être en mesure de dire la quantité d'essence restante de CHAQUE voiture. Donc, oui, je peux théoriquement voir avoir un accès "instance privée". J'ai l'impression que je ne l'utiliserais vraiment jamais parce que je craindrais que dans 99% des cas, je VOUDRAIS vraiment qu'une autre instance accède à cette variable.
Aquinas
2
@Jon La position qui a été prise pour les soi-disant «avantages» est que les classes ne devraient pas connaître l'état interne d'une autre instance - indépendamment du fait que ce soit du même type. Ce n'est pas un avantage en soi, mais il y a probablement une très bonne raison de choisir l'un par rapport à l'autre et c'est ce que nous avons essayé de comprendre
RichK
4
Vous pouvez toujours transformer la variable en une paire de fermetures getter / setter, initialisées dans le constructeur, qui échoueront si ce n'est pas la même chose que lors de l'initialisation ...
Martin Sojka
8
Scala fournit des membres privés d'instance - ainsi que de nombreux autres niveaux d'accès introuvables en Java, C # et de nombreux autres langages. C'est une question parfaitement légitime.
Bruno Reis

Réponses:

73

Je pense qu'une des raisons pour lesquelles cela fonctionne de cette façon est que les modificateurs d'accès fonctionnent au moment de la compilation . En tant que tel, il n'est pas facile de déterminer si un objet donné est également l' objet actuel . Par exemple, considérez ce code:

public class Foo
{
    private int bar;

    public void Baz(Foo other)
    {
        other.bar = 2;
    }

    public void Boo()
    {
        Baz(this);
    }
}

Le compilateur peut-il nécessairement comprendre que otherc'est réellement this? Pas dans tous les cas. On pourrait dire que cela ne devrait tout simplement pas être compilé à ce moment-là, mais cela signifie que nous avons un chemin de code dans lequel un membre d'instance privée de l'instance correcte n'est pas accessible, ce qui est encore pire à mon avis.

Le seul fait d'exiger une visibilité au niveau du type plutôt qu'au niveau de l'objet garantit que le problème est résolu, ainsi qu'une situation qui semble devoir fonctionner réellement .

EDIT : Le point de Danilel Hilgarth selon lequel ce raisonnement est à l'envers a du mérite. Les concepteurs de langage peuvent créer le langage de leur choix et les rédacteurs de compilateurs doivent s'y conformer. Cela étant dit, les concepteurs de langage sont incités à faciliter la tâche des rédacteurs de compilateurs dans leur travail. (Bien que dans ce cas, il est assez facile de faire valoir que les simples députés pourraient alors ne sont accessibles viathis (implicitement ou explicitement)).

Cependant, je pense que cela rend la question plus déroutante qu'elle ne devrait l'être. La plupart des utilisateurs (moi y compris) trouveraient inutilement limitant si le code ci-dessus ne fonctionnait pas: après tout, ce sont mes données auxquelles j'essaie d'accéder! Pourquoi devrais-je traverserthis ?

En bref, je pense que j'ai peut-être surestimé le fait que ce soit "difficile" pour le compilateur. Ce que je voulais vraiment faire comprendre, c'est que la situation ci-dessus semble être celle que les concepteurs aimeraient avoir du travail.

dlev
la source
33
À mon humble avis, ce raisonnement est dans le mauvais sens. Le compilateur applique les règles du langage. Cela ne les fait pas. En d'autres termes, si les membres privés étaient "instance private" et non "type private", le compilateur aurait été implémenté d'une autre manière, par exemple en autorisant this.bar = 2mais non other.bar = 2, car il otherpourrait s'agir d'une instance différente.
Daniel Hilgarth
@Daniel C'est un bon point, mais comme je l'ai mentionné, je pense qu'avoir cette restriction serait pire que d'avoir une visibilité au niveau de la classe.
dlev
3
@dlev: Je n'ai rien dit sur la question de savoir si je pense que les membres "instance private" sont une bonne chose. :-) Je voulais juste souligner que vous soutenez qu'une implémentation technique dicte les règles du langage et qu'à mon avis, cette argumentation n'est pas correcte.
Daniel Hilgarth
2
@Daniel Point pris. Je pense toujours que c'est une considération valable, même si vous avez bien sûr raison de dire qu'en fin de compte, les concepteurs de langage comprennent ce qu'ils veulent, et les rédacteurs de compilateurs s'y conforment.
dlev
2
Je ne pense pas que l'argument du compilateur soit valable. Il existe des langages qui n'autorisent que l'instance privée (comme Ruby) et qui autorisent l'instance privée et la classe privée par choix (comme Eiffel). La génération de compilateurs n'était pas nécessaire plus difficile ou plus facile pour ces langages. Pour plus d'informations, consultez également ma réponse dans le fil de discussion.
Abel
61

Parce que le but du type d'encapsulation utilisé en C # et dans les langages similaires * est de réduire la dépendance mutuelle de différents morceaux de code (classes en C # et Java), et non d'objets différents en mémoire.

Par exemple, si vous écrivez du code dans une classe qui utilise certains champs dans une autre classe, ces classes sont très étroitement couplées. Cependant, si vous utilisez du code dans lequel vous avez deux objets de la même classe, il n'y a pas de dépendance supplémentaire. Une classe dépend toujours d'elle-même.

Cependant, toute cette théorie sur l'encapsulation échoue dès que quelqu'un crée des propriétés (ou des paires get / set en Java) et expose directement tous les champs, ce qui rend les classes aussi couplées comme si elles accédaient aux champs de toute façon.

* Pour des éclaircissements sur les types d'encapsulation, voir l'excellente réponse d'Abel.

Goran Jovic
la source
3
Je ne pense pas que cela échoue une fois que les get/setméthodes sont fournies. Les méthodes d'accès conservent toujours une abstraction (l'utilisateur n'a pas besoin de savoir qu'il y a un champ derrière lui, ou quel (s) champ (s) peuvent être derrière la propriété). Par exemple, si nous écrivons une Complexclasse, nous pouvons exposer des propriétés pour obtenir / définir les coordonnées polaires, et un autre get / set pour les coordonnées cartésiennes. Lequel la classe utilise en dessous est inconnu de l'utilisateur (cela pourrait même être quelque chose de complètement différent).
Ken Wayne VanderLinde
5
@Ken: Cela échoue si vous fournissez une propriété pour chaque champ que vous avez sans même considérer si elle doit être exposée.
Goran Jovic
@Goran Bien que je convienne que ce ne serait pas idéal, cela n'échoue toujours pas complètement car les accesseurs get / set vous permettent d'ajouter une logique supplémentaire si les champs sous-jacents changent par exemple.
ForbesLindesay
1
"Parce que le but de l'encapsulation est de réduire la dépendance mutuelle des différents morceaux de code" >> non. L'encapsulation peut aussi signifier l'encapsulation d'instances, cela dépend de la langue ou de l'école de pensée. L'une des voix les plus définitives sur OO, Bertrand Meyer, considère que c'est même le cas par défaut private. Vous pouvez vérifier ma réponse pour mon avis sur les choses si vous le souhaitez;).
Abel
@Abel: J'ai basé ma réponse sur des langages avec encapsulation de classe parce qu'OP a posé des questions sur l'un d'entre eux, et franchement parce que je les connais. Merci pour la correction et pour de nouvelles informations intéressantes!
Goran Jovic
49

De nombreuses réponses ont déjà été ajoutées à ce sujet intéressant, cependant, je n'ai pas tout à fait trouvé la vraie raison pour laquelle ce comportement est tel qu'il est. Laissez-moi essayer:

De mon temps

Quelque part entre Smalltalk dans les années 80 et Java au milieu des années 90, le concept d'orientation objet a mûri. Le masquage d'informations, qui n'était pas initialement considéré comme un concept uniquement disponible pour OO (mentionné pour la première fois en 1978), a été introduit dans Smalltalk car toutes les données (champs) d'une classe sont privées, toutes les méthodes sont publiques. Au cours des nombreux nouveaux développements d'OO dans les années 90, Bertrand Meyer a tenté de formaliser une grande partie des concepts OO dans son livre historique Object Oriented Software Construction (OOSC). qui a depuis lors été considéré comme une référence (presque) définitive sur les concepts OO et la conception du langage. .

En cas de visibilité privée

Selon Meyer, une méthode devrait être mise à la disposition d'un ensemble défini de classes (page 192-193). Cela donne évidemment une très grande granularité de masquage des informations, la fonctionnalité suivante est disponible pour classA et classB et tous leurs descendants:

feature {classA, classB}
   methodName

Dans le cas où privateil dit ce qui suit: sans déclarer explicitement un type comme visible pour sa propre classe, vous ne pouvez pas accéder à cette fonctionnalité (méthode / champ) dans un appel qualifié. Ie si xest une variable, x.doSomething()n'est pas autorisé. L'accès non qualifié est bien entendu autorisé à l'intérieur de la classe elle-même.

En d'autres termes: pour autoriser l'accès par une instance de la même classe, vous devez autoriser l'accès à la méthode par cette classe explicitement. Ceci est parfois appelé instance-privé ou classe-privé.

Instance privée dans les langages de programmation

Je connais au moins deux langues actuellement utilisées qui utilisent le masquage d'informations privées d'instance par opposition au masquage d'informations privées de classe. L'un est Eiffel, un langage conçu par Meyer, qui pousse OO à ses extrêmes. L'autre étant Ruby, une langue beaucoup plus courante de nos jours. En Ruby, privatesignifie: "privé pour cette instance" .

Choix pour la conception du langage

Il a été suggéré qu'autoriser l'instance privée serait difficile pour le compilateur. Je ne pense pas, car il est relativement simple d'autoriser ou d'interdire les appels qualifiés aux méthodes. Si pour une méthode privée, doSomething()est autorisé etx.doSomething() ne l'est pas, un concepteur de langage a effectivement défini l'accessibilité d'instance uniquement pour les méthodes et les champs privés.

D'un point de vue technique, il n'y a aucune raison de choisir l'une ou l'autre manière (surtout si l'on considère qu'Eiffel.NET peut faire cela avec IL, même avec l'héritage multiple, il n'y a aucune raison inhérente de ne pas fournir cette fonctionnalité).

Bien sûr, c'est une question de goût et comme d'autres l'ont déjà mentionné, certaines méthodes pourraient être plus difficiles à écrire sans la fonctionnalité de visibilité au niveau de la classe des méthodes et des champs privés.

Pourquoi C # n'autorise que l'encapsulation de classe et non l'encapsulation d'instance

Si vous regardez les fils Internet sur l'encapsulation d'instances (un terme parfois utilisé pour désigner le fait qu'un langage définit les modificateurs d'accès au niveau de l'instance, par opposition au niveau de la classe), le concept est souvent mal vu. Cependant, étant donné que certains langages modernes utilisent l'encapsulation d'instance, au moins pour le modificateur d'accès privé, vous pensez qu'il peut être et est utile dans le monde de la programmation moderne.

Cependant, C # a certainement regardé le plus dur C ++ et Java pour sa conception de langage. Alors qu'Eiffel et Modula-3 étaient également dans l'image, compte tenu des nombreuses fonctionnalités d'Eiffel manquantes (héritage multiple), je pense qu'ils ont choisi le même chemin que Java et C ++ en ce qui concerne le modificateur d'accès privé.

Si vous voulez vraiment savoir pourquoi vous devriez essayer de mettre la main sur Eric Lippert, Krzysztof Cwalina, Anders Hejlsberg ou toute autre personne qui a travaillé sur le standard de C #. Malheureusement, je n'ai pas trouvé de note définitive dans le langage de programmation annoté C # .

Abel
la source
1
C'est une belle réponse avec beaucoup de fond, très intéressante, merci d'avoir pris le temps de répondre à une question (relativement) ancienne
RichK
1
@RichK: vous êtes le bienvenu, j'ai juste attrapé la question trop tard je suppose, mais j'ai trouvé le sujet suffisamment intrigant pour répondre avec une certaine profondeur :)
Abel
Sous COM, «privé» signifiait «instance-privé». Je pense que c'était dans une certaine mesure parce qu'une définition de classe Fooreprésentait effectivement une interface et une classe d'implémentation; comme COM n'avait pas de concept de référence de classe-objet - juste une référence d'interface - il n'y avait aucun moyen pour une classe de contenir une référence à quelque chose qui était garanti comme une autre instance de cette classe. Cette conception basée sur une interface rendait certaines choses fastidieuses, mais cela signifiait que l'on pouvait concevoir une classe qui était substituable à une autre sans avoir à partager les mêmes éléments internes.
supercat
2
Très bonne réponse. OP devrait envisager de marquer cette question comme la réponse.
Gavin Osborn
18

Ce n'est que mon avis, mais de manière pragmatique, je pense que si un programmeur a accès à la source d'une classe, vous pouvez raisonnablement lui faire confiance pour accéder aux membres privés de l'instance de classe. Pourquoi lier un programmeur à la main droite alors que dans sa gauche vous leur avez déjà donné les clés du royaume?

PoissonPanierGordo
la source
13
Je ne comprends pas cet argument. Disons que vous travaillez sur un programme où vous êtes le seul développeur et que vous êtes le seul à utiliser JAMAIS vos bibliothèques, dites-vous que dans ce cas, vous rendez TOUS les membres publics parce que vous avez accès au code source?
Aquinas
@aquinas: c'est assez différent. Rendre tout public mènera à des pratiques de programmation horribles. Permettre à un type de voir les champs privés d'autres instances de lui-même est un cas très restreint.
Igby Largeman
2
Non, je ne préconise pas de rendre chaque membre public. Dieu non! Mon argument est que si vous êtes déjà dans la source, pouvez voir les membres privés, comment ils sont utilisés, les conventions employées, etc., alors pourquoi ne pas pouvoir utiliser ces connaissances?
FishBasketGordo
7
Je pense que la façon de penser est toujours dans les termes du type. Si le type ne peut pas être approuvé pour traiter correctement les membres du type, que peut-il faire? ( Normalement , ce serait un programmeur contrôlant réellement les interactions, mais à l'ère des outils de génération de code, ce n'est peut-être pas toujours le cas.)
Anthony Pegram
3
Ma question n'est pas vraiment de confiance, plus de nécessité. La confiance est totalement hors de propos lorsque vous pouvez utiliser la réflexion sur des choses odieuses pour des particuliers. Je me demande pourquoi, conceptuellement, vous pouvez accéder aux privés. J'ai supposé qu'il y avait une raison logique plutôt qu'une situation de meilleures pratiques.
RichK
13

La raison en est en effet le contrôle d'égalité, la comparaison, le clonage, la surcharge d'opérateurs ... Il serait très délicat d'implémenter operator + sur des nombres complexes, par exemple.

Lorenzo Dematté
la source
3
Mais tout ce que vous mentionnez nécessite un type OBTENIR la valeur d'un autre type. Je suis d'accord pour dire: vous voulez inspecter mon état privé? Très bien, allez-y. Vous souhaitez définir mon état privé? Pas question, mon pote, de changer ton propre état. :)
aquinas
2
@aquinas Cela ne fonctionne pas de cette façon. Les champs sont des champs. Ils ne sont en lecture seule que s'ils sont déclarés en tant que readonlyet cela s'applique également à l'instance déclarante. De toute évidence, vous affirmez qu'il devrait y avoir une règle de compilation spécifique pour l'interdire, mais chaque règle de ce type est une fonctionnalité que l'équipe C # doit spécifier, documenter, localiser, tester, maintenir et prendre en charge. S'il n'y a pas d' avantage convaincant, pourquoi le feraient-ils?
Aaronaught
Je suis d'accord avec @Aaronaught: il y a un coût associé à la mise en œuvre de chaque nouvelle spécification et des modifications apportées au compilateur. Pour un scénario défini, envisagez une méthode de clonage. Bien sûr, vous voudrez peut-être le réaliser avec un constructeur privé, mais peut-être que dans certains scénarios, ce n'est pas possible / conseillé.
Lorenzo Dematté
1
Équipe C #? Je suis juste en train de débattre d'éventuels changements de langage théorique dans TOUTE langue. "S'il n'y a pas d'avantage convaincant ..." Je pense qu'il peut y avoir un avantage. Tout comme avec n'importe quelle garantie du compilateur, quelqu'un peut trouver utile de garantir qu'une classe ne modifie que son propre état.
aquinas
@aquinas: les fonctionnalités sont implémentées parce qu'elles sont utiles à la plupart ou à la plupart des utilisateurs - pas parce qu'elles peuvent être utiles dans certains cas isolés.
Aaronaught
9

Tout d'abord, qu'arriverait-il aux membres statiques privés? Peuvent-ils être accessibles uniquement par des méthodes statiques? Vous ne voudriez certainement pas cela, car vous ne seriez alors pas en mesure d'accéder à votreconst s.

En ce qui concerne votre question explicite, considérons le cas de a StringBuilder, qui est implémenté comme une liste chaînée d'instances d'elle-même:

public class StringBuilder
{
    private string chunk;
    private StringBuilder nextChunk;
}

Si vous ne pouvez pas accéder aux membres privés d'autres instances de votre propre classe, vous devez implémenter ToStringcomme ceci:

public override string ToString()
{
    return chunk + nextChunk.ToString();
}

Cela fonctionnera, mais c'est O (n ^ 2) - pas très efficace. En fait, cela va probablement à l'encontre de l'objectif général d'avoir une StringBuilderclasse en premier lieu. Si vous pouvez accéder aux membres privés d'autres instances de votre propre classe, vous pouvez implémenter ToStringen créant une chaîne de la longueur appropriée, puis en faisant une copie non sécurisée de chaque morceau à son emplacement approprié dans la chaîne:

public override string ToString()
{
    string ret = string.FastAllocateString(Length);
    StringBuilder next = this;

    unsafe
    {
        fixed (char *dest = ret)
            while (next != null)
            {
                fixed (char *src = next.chunk)
                    string.wstrcpy(dest, src, next.chunk.Length);
                next = next.nextChunk;
            }
    }
    return ret;
}

Cette implémentation est O (n), ce qui la rend très rapide, et n'est possible que si vous avez accès aux membres privés d'autres instances de votre classe .

Gabe
la source
Ouais, mais n'y a-t-il pas d'autres moyens de contourner cela, comme exposer certains champs comme internes?
MikeKulls
2
@Mike: exposer un champ comme le internalrend moins privé! Vous finiriez par exposer les éléments internes de votre classe à tout ce qui se trouve dans son assemblage.
Gabe
3

Ceci est parfaitement légitime dans de nombreux langages (C ++ pour un). Les modificateurs d'accès proviennent du principe d'encapsulation en POO. L'idée est de restreindre l'accès à l' extérieur , dans ce cas à l'extérieur se trouvent d'autres classes. Toute classe imbriquée en C # par exemple peut également accéder aux membres privés de ses parents.

Bien qu'il s'agisse d'un choix de conception pour un concepteur de langage. La restriction de cet accès peut compliquer certains scénarios très courants sans trop contribuer à l'isolation des entités.

Il y a une discussion similaire ici

Mehran
la source
1

Je ne pense pas qu'il y ait une raison pour laquelle nous ne pourrions pas ajouter un autre niveau de confidentialité, où les données sont privées pour chaque instance. En fait, cela pourrait même donner une belle sensation d'exhaustivité à la langue.

Mais en pratique, je doute que ce soit vraiment utile. Comme vous l'avez souligné, notre caractère privé habituel est utile pour des choses telles que les vérifications d'égalité, ainsi que pour la plupart des autres opérations impliquant plusieurs instances d'un type. Bien que j'apprécie également votre point sur le maintien de l'abstraction des données, car c'est un point important dans la POO.

Je pense que tout autour, fournir la possibilité de restreindre l'accès de cette manière pourrait être une fonctionnalité intéressante à ajouter à la POO. Est-ce vraiment utile? Je dirais non, car une classe devrait pouvoir faire confiance à son propre code. Étant donné que cette classe est la seule chose qui peut accéder aux membres privés, il n'y a aucune raison réelle d'avoir besoin d'abstraction de données lorsqu'il s'agit d'une instance d'une autre classe.

Bien sûr, vous pouvez toujours écrire votre code comme s'il était privé appliqué aux instances. Utilisez les get/setméthodes habituelles pour accéder / modifier les données. Cela rendrait probablement le code plus gérable si la classe pouvait être soumise à des modifications internes.

Ken Wayne VanderLinde
la source
0

Excellentes réponses données ci-dessus. J'ajouterais qu'une partie de ce problème est le fait que l'instanciation d'une classe à l'intérieur d'elle-même est même autorisée en premier lieu. Cela permet depuis dans une logique récursive de boucle "for", par exemple, d'utiliser ce type d'astuce tant que vous avez une logique pour terminer la récursivité. Mais instancier ou passer la même classe à l'intérieur d'elle-même sans créer de telles boucles crée logiquement ses propres dangers, même si c'est un paradigme de programmation largement accepté. Par exemple, une classe C # peut instancier une copie d'elle-même dans son constructeur par défaut, mais cela n'enfreint aucune règle ni ne crée de boucles causales. Pourquoi?

BTW .... ce même problème s'applique également aux membres «protégés». :(

Je n'ai jamais accepté pleinement ce paradigme de programmation, car il comporte encore toute une série de problèmes et de risques que la plupart des programmeurs ne saisissent pas complètement tant que des problèmes comme ce problème ne surviennent pas et déroutent les gens et défient toute la raison d'avoir des membres privés.

Cet aspect "bizarre et farfelu" de C # est encore une raison de plus pour laquelle une bonne programmation n'a rien à voir avec l'expérience et les compétences, mais juste connaître les trucs et les pièges ..... comme travailler sur une voiture. C'est l'argument selon lequel les règles étaient censées être enfreintes, ce qui est un très mauvais modèle pour tout langage informatique.

Stokely
la source
-1

Il me semble que si les données étaient privées pour d'autres instances du même type, elles ne seraient plus nécessairement du même type. Il ne semble pas se comporter ou agir de la même manière que les autres instances. Le comportement pourrait facilement être modifié en fonction de ces données internes privées. Cela ne ferait que semer la confusion à mon avis.

En gros, je pense personnellement que l'écriture de classes dérivées d'une classe de base offre des fonctionnalités similaires que vous décrivez avec «avoir des données privées par instance». Au lieu de cela, vous avez juste une nouvelle définition de classe par type «unique».

C Johnson
la source