Code comme:
public Thing[] getThings(){
return things;
}
Cela n'a pas beaucoup de sens, car votre méthode d'accès ne fait que renvoyer directement la structure de données interne. Vous pourriez tout aussi bien déclarer Thing[] things
être public
. L'idée derrière une méthode d'accès est de créer une interface qui isole les clients des changements internes et les empêche de manipuler la structure de données réelle, sauf de manière discrète comme le permet l'interface. Comme vous l'avez découvert lorsque tout votre code client s'est cassé, votre méthode d'accès ne l'a pas fait - c'est juste du code gaspillé. Je pense que beaucoup de programmeurs ont tendance à écrire du code comme ça parce qu'ils ont appris quelque part que tout doit être encapsulé avec des méthodes d'accès - mais c'est pour les raisons que j'ai expliquées. Le faire simplement pour «suivre le formulaire» lorsque la méthode d'accès ne sert à rien n'est que du bruit.
Je recommanderais certainement votre solution proposée, qui atteint certains des objectifs les plus importants de l'encapsulation: Donner aux clients une interface robuste et discrète qui les isole des détails d'implémentation interne de votre classe et ne leur permet pas de toucher la structure de données interne attendez de la manière que vous jugez appropriée - "la loi du moindre privilège". Si vous regardez les grands frameworks OOP populaires, tels que le CLR, le STL, le VCL, le modèle que vous avez proposé est répandu, pour exactement cette raison.
Faut-il toujours faire ça? Pas nécessairement. Par exemple, si vous avez des classes d'assistance ou d'amis qui sont essentiellement un composant de votre classe de travail principale et qui ne sont pas "orientées vers l'avant", ce n'est pas nécessaire - c'est une surpuissance qui va ajouter beaucoup de code inutile. Et dans ce cas, je n'utiliserais pas du tout de méthode d'accès - c'est insensé, comme expliqué. Déclarez simplement la structure de données d'une manière qui ne s'applique qu'à la classe principale qui l'utilise - la plupart des langues prennent en charge des façons de le faire - friend
, ou la déclarez dans le même fichier que la classe de travail principale, etc.
Le seul inconvénient que je peux voir dans votre proposition, c'est que c'est plus de travail à coder (et maintenant vous allez devoir recoder vos classes de consommateurs - mais vous devez / devez le faire de toute façon.) Mais ce n'est pas vraiment un inconvénient - vous devez le faire correctement, et cela prend parfois plus de travail.
L'une des choses qui rendent un bon programmeur bon, c'est qu'il sait quand le travail supplémentaire en vaut la peine et quand il ne l'est pas. À long terme, le fait de verser un supplément maintenant rapportera de gros dividendes à l'avenir - sinon sur ce projet, puis sur d'autres. Apprenez à coder la bonne façon et à utiliser votre tête à ce sujet, pas seulement à suivre de manière robotique les formulaires prescrits.
Notez que les collections plus complexes que les tableaux peuvent nécessiter que la classe englobante implémente encore plus de trois méthodes juste pour accéder à la structure de données interne.
Si vous exposez une structure de données entière à travers une classe contenante, OMI, vous devez réfléchir à la raison pour laquelle cette classe est encapsulée, si ce n'est pas simplement pour fournir une interface plus sûre - une "classe wrapper". Vous dites que la classe conteneur n'existe pas à cette fin - alors peut-être qu'il y a quelque chose qui ne va pas dans votre conception. Pensez à diviser vos classes en modules plus discrets et à les superposer.
Une classe doit avoir un objectif clair et discret, et fournir une interface pour prendre en charge cette fonctionnalité - pas plus. Vous essayez peut-être de regrouper des choses qui ne vont pas ensemble. Lorsque vous faites cela, les choses se brisent à chaque fois que vous devez mettre en œuvre un changement. Plus vos classes sont petites et discrètes, plus il est facile de changer les choses: pensez à LEGO.
get(index)
,add()
,size()
,remove(index)
etremove(Object)
. En utilisant la technique proposée, la classe qui contient cette ArrayList doit avoir cinq méthodes publiques juste pour déléguer à la collection interne. Et le but de cette classe dans le programme est très probablement de ne pas encapsuler cette ArrayList, mais plutôt de faire autre chose. L'ArrayList n'est qu'un détail. [...]remove
n'est pas en lecture seule. Je crois comprendre que le PO veut tout rendre public - comme dans le code d'origine avant le changement proposé.public Thing[] getThings(){return things;}
C'est ce que je n'aime pas.Vous avez demandé: Dois-je toujours encapsuler entièrement une structure de données interne?
Réponse brève: Oui, la plupart du temps mais pas toujours .
Réponse longue: Je pense que les classes suivent les catégories suivantes:
Classes qui encapsulent des données simples. Exemple: point 2D. Il est facile de créer des fonctions publiques qui permettent d'obtenir / de définir les coordonnées X et Y, mais vous pouvez masquer facilement les données internes sans trop de problèmes. Pour de telles classes, il n'est pas nécessaire d'exposer les détails de la structure de données interne.
Classes de conteneur qui encapsulent des collections. STL a les classes de conteneurs classiques. Je considère
std::string
etstd::wstring
parmi eux aussi. Ils fournissent une interface riche pour faire face aux abstractions , maisstd::vector
,std::string
etstd::wstring
fournira également la possibilité d'obtenir l' accès aux données brutes. Je ne serais pas pressé de les appeler des classes mal conçues. Je ne connais pas la justification de ces classes exposant leurs données brutes. Cependant, dans mon travail, j'ai trouvé nécessaire d'exposer les données brutes lors du traitement de millions de nœuds de maillage et de données sur ces nœuds de maillage pour des raisons de performances.La chose importante à propos de l'exposition de la structure interne d'une classe est que vous devez réfléchir longuement et sérieusement avant de lui donner un signal vert. Si l'interface est interne à un projet, il sera coûteux de la changer à l'avenir mais pas impossible. Si l'interface est externe au projet (par exemple lorsque vous développez une bibliothèque qui sera utilisée par d'autres développeurs d'applications), il peut être impossible de modifier l'interface sans perdre vos clients.
Les classes qui sont principalement de nature fonctionnelle. Exemples:
std::istream
,std::ostream
, itérateurs des conteneurs STL. C'est carrément stupide d'exposer les détails internes de ces classes.Classes hybrides. Ce sont des classes qui encapsulent une certaine structure de données mais fournissent également des fonctionnalités algorithmiques. Personnellement, je pense que c'est le résultat d'une conception mal pensée. Cependant, si vous les trouvez, vous devez décider s'il est judicieux d'exposer leurs données internes au cas par cas.
En conclusion: La seule fois où j'ai trouvé nécessaire d'exposer la structure de données interne d'une classe, c'est lorsqu'elle est devenue un goulot d'étranglement des performances.
la source
Au lieu de renvoyer directement les données brutes, essayez quelque chose comme ceci
Donc, vous fournissez essentiellement une collection personnalisée qui présente tout ce que le visage est souhaité. Dans votre nouvelle implémentation alors,
Vous disposez maintenant de l'encapsulation appropriée, masquez les détails de l'implémentation et fournissez une compatibilité descendante (moyennant un coût).
la source
List
. Les méthodes d'accès qui renvoient simplement des membres de données, même avec un cast pour rendre les choses plus robustes, ne sont pas vraiment une bonne IMO d'encapsulation. La classe des travailleurs devrait gérer tout cela, pas le client. Plus le client doit être «bête», plus il sera robuste. En passant, je ne suis pas sûr que vous ayez répondu à la question ...Vous devez utiliser des interfaces pour ces choses. N'aiderait pas dans votre cas, car le tableau de Java n'implémente pas ces interfaces, mais vous devriez le faire à partir de maintenant:
De cette façon, vous pouvez passer
ArrayList
àLinkedList
ou autre chose, et vous ne casserez aucun code car toutes les collections Java (à l'exception des tableaux) qui ont un accès (pseudo?) Aléatoire seront probablement implémentéesList
.Vous pouvez également utiliser
Collection
, qui offrent moins de méthodes queList
mais peuvent prendre en charge les collections sans accès aléatoire, ouIterable
qui peuvent même prendre en charge les flux mais n'offrent pas beaucoup en termes de méthodes d'accès ...la source
List
tant que classes dérivées, cela aurait plus de sens, mais simplement "espérer le meilleur" n'est pas une bonne idée. "Un bon programmeur regarde dans les deux sens dans une rue à sens unique".List
(ouCollection
, ou au moinsIterable
). C'est tout l'intérêt de ces interfaces, et c'est dommage que les tableaux Java ne les implémentent pas, mais ce sont des interfaces officielles pour les collections en Java, il n'est donc pas si exagéré de supposer qu'une collection Java les implémentera - à moins que cette collection ne soit plus ancienne queList
, et dans ce cas, il est très facile de l'envelopper avec AbstractList .List
dansArrayList
, mais ce n'est pas comme si l'implémentation pouvait être protégée à 100% - vous pouvez toujours utiliser la réflexion pour accéder aux champs privés. Cela est mal vu, mais le casting est également mal vu (pas autant que cela). Le point d'encapsulation n'est pas d'empêcher le piratage malveillant - c'est plutôt d'empêcher les utilisateurs de dépendre des détails d'implémentation que vous voudrez peut-être modifier. L'utilisation de l'List
interface fait exactement cela - les utilisateurs de la classe peuvent dépendre de l'List
interface au lieu de laArrayList
classe concrète qui pourrait changer.C'est assez courant pour cacher votre structure de données interne du monde extérieur. Parfois, il est exagéré spécialement dans DTO. Je le recommande pour le modèle de domaine. S'il est nécessaire de l'exposer, renvoyez la copie immuable. Parallèlement à cela, je suggère de créer une interface ayant ces méthodes comme obtenir, définir, supprimer, etc.
la source