web-dev-qa-db-fra.com

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

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 la spécification C # (sections 3.5.1, 3.5.2) indique que l'accès aux champs privés est sur un type, pas 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?

152
RichK

Je pense que l'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 other est en fait this? Pas dans tous les cas. On pourrait faire valoir que cela ne devrait pas être compilé alors, mais cela signifie que nous avons un chemin de code où un membre d'instance privée de l'instance correcte n'est pas accessible, ce qui je pense est encore pire.

Exiger uniquement une visibilité au niveau du type plutôt qu'une visibilité au niveau de l'objet garantit que le problème est traitable, ainsi que la création d'une situation qui semble devrait fonctionner fonctionne réellement .

MODIFIER : l'argument de Danilel Hilgarth selon lequel ce raisonnement est à l'envers a du mérite. Les concepteurs de langage peuvent créer le langage qu'ils souhaitent et les rédacteurs du compilateur doivent s'y conformer. Cela étant dit, les concepteurs de langage sont incités à faciliter le travail des rédacteurs de compilateurs. (Bien que dans ce cas, il soit assez facile d'affirmer que les membres privés ne pourraient alors que être accessibles via this (implicitement ou explicitement) ).

Cependant, je pense que cela rend la question plus confuse qu'elle ne devrait l'être. La plupart des utilisateurs (moi y compris) trouveraient cela 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 passer par this?

En bref, je pense que j'ai peut-être surestimé le fait qu'il 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.

73
dlev

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

Par exemple, si vous écrivez du code dans une classe qui utilise certains champs dans une autre classe, alors ces classes sont très étroitement couplées. Cependant, si vous traitez 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 obtient/définit des paires en Java) et expose tous les champs directement, ce qui rend les classes couplées comme si elles accédaient de toute façon aux champs.

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

61
Goran Jovic

Beaucoup de réponses ont déjà été ajoutées à ce fil intéressant, cependant, je n'ai pas tout à fait trouvé la vraie raison pour pourquoi ce comportement est la façon dont il est . Permettez-moi de l'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 d'objet a mûri. La dissimulation d'informations, qui n'était pas considérée à l'origine comme un concept uniquement disponible pour OO (mentionné pour la première fois en 1978), a été introduite 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 de OO dans les années 90, Bertrand Meyer a tenté de formaliser une grande partie des concepts de [OO dans son livre de référence Object Oriented Software Construction (OOSC) = qui a depuis lors été considéré comme une référence (presque) définitive sur les concepts de 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 les classes A et B et tous leurs descendants:

feature {classA, classB}
   methodName

Dans le cas de private, il 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é. C'est à dire. si x est une variable, x.doSomething() n'est pas autorisée. L'accès sans réserve est bien sûr 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 explicitement l'accès à la méthode par cette classe. Ceci est parfois appelé instance-private contre class-private.

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 par exemple par opposition au masquage d'informations privées par classe. L'un est Eiffel, un langage conçu par Meyer, qui pousse OO à ses extrêmes extrêmes. L'autre étant le rubis, une langue beaucoup plus courante de nos jours. En Ruby, private signifie: "privé pour cette instance" .

Choix pour la conception de la langue

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

D'un point de vue technique, il n'y a aucune raison de choisir l'une ou l'autre façon (surtout si l'on considère qu'Eiffel.NET peut le faire avec IL, même avec un 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 # autorise uniquement l'encapsulation de classe et non l'encapsulation d'instance

Si vous regardez les threads Internet sur l'encapsulation d'instance (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 que cela peut être et est utilisé dans le monde de la programmation moderne.

Cependant, C # a certes examiné le plus durement C++ et Java pour sa conception de langage. Alors qu'Eiffel et Modula-3 étaient également sur la photo, compte tenu des nombreuses fonctionnalités d'Eiffel manquantes (héritage multiple), je pense qu'ils ont choisi le même itinéraire que Java et C++ en ce qui concerne le modificateur d'accès privé.

Si vous voulez vraiment savoir pourquoi vous devriez essayer de joindre Eric Lippert, Krzysztof Cwalina, Anders Hejlsberg ou toute autre personne qui a travaillé sur la norme de C #. Malheureusement, je n'ai pas pu trouver de note définitive dans l'annoté The C # Programming Language .

49
Abel

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 en accédant aux membres privés de l'instance de classe. Pourquoi lier un programmeur à droite alors qu'à sa gauche vous lui avez déjà donné les clés du royaume?

18
FishBasketGordo

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

13
Lorenzo Dematté

Tout d'abord, qu'arriverait-il aux membres statiques privés? Peut-on y accéder uniquement par des méthodes statiques? Vous ne voudriez certainement pas cela, car vous ne pourriez alors pas accéder à vos consts.

En ce qui concerne votre question explicite, considérons le cas d'un StringBuilder, qui est implémenté comme une liste liée d'instances de lui-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 ToString comme 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 global d'avoir une classe StringBuilder en premier lieu. Si vous pouvez accéder aux membres privés d'autres instances de votre propre classe, vous pouvez implémenter ToString en créant une chaîne de la bonne longueur, puis en effectuant une copie non sécurisée de chaque bloc dans son place appropriée 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 est niquement possible si vous avez accès à des membres privés d'autres instances de votre classe.

9
Gabe

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

Bien que ce soit un choix de conception pour un concepteur de langage. La restriction de cet accès peut compliquer extrêmement certains scénarios très courants sans beaucoup contribuer à l'isolement des entités.

Il y a une discussion similaire ici

3
Mehran

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 fournir une agréable sensation de complétude de la langue.

Mais dans la pratique, je doute que ce soit vraiment utile. Comme vous l'avez souligné, notre confidentialité habituelle 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'aime aussi 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'une abstraction de données lors du traitement d'une instance d'une autre classe.

Bien sûr, vous pouvez toujours écrire votre code comme si privé appliqué aux instances. Utilisez l'habituel get/set méthodes pour accéder/modifier les données. Cela rendrait probablement le code plus gérable si la classe pouvait être sujette à des changements internes.

1

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 en elle-même est même autorisée en premier lieu. Il permet depuis dans une boucle récursive "for", par exemple, d'utiliser ce type de truc tant que vous avez la logique de mettre fin à la récursivité. Mais instancier ou passer la même classe à l'intérieur de lui-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 ne casse 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 pleinement accepté ce paradigme de programmation car il comporte toujours toute une série de problèmes et de risques que la plupart des programmeurs ne comprennent pas complètement jusqu'à ce que des problèmes comme ce problème se posent 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 autre raison pour laquelle une bonne programmation n'a rien à voir avec l'expérience et les compétences, mais simplement connaître les astuces 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.

0
Stokely