Je lis les éléments essentiels du modèle de conception C # . Je lis actuellement sur le modèle d'itérateur.
Je comprends parfaitement comment mettre en œuvre, mais je ne comprends pas l'importance ou voir un cas d'utilisation. Dans le livre, un exemple est donné où quelqu'un a besoin d'obtenir une liste d'objets. Ils auraient pu le faire en exposant une propriété publique, comme IList<T>
ou un Array
.
Le livre écrit
Le problème est que la représentation interne dans ces deux classes a été exposée à des projets extérieurs.
Quelle est la représentation interne? Le fait que ce soit un array
ou IList<T>
? Je ne comprends vraiment pas pourquoi c'est une mauvaise chose pour le consommateur (le programmeur qui l'appelle) de savoir ...
Le livre dit ensuite que ce modèle fonctionne en exposant sa GetEnumerator
fonction, afin que nous puissions appeler GetEnumerator()
et exposer la «liste» de cette façon.
Je suppose que ces modèles ont une place (comme tous) dans certaines situations, mais je ne vois pas où et quand.
la source
F
qui me donne des connaissances (méthodes) dea
,b
etc
. Tout va bien et bien, il peut y avoir beaucoup de choses différentes mais justeF
pour moi. Considérez la méthode comme des «contraintes» ou des clauses d'un contrat quiF
engage les classes à faire. Supposons que nous ajoutions und
bien, car nous en avons besoin. Cela ajoute une contrainte supplémentaire, chaque fois que nous faisons cela, nous imposons de plus en plus sur leF
s. Finalement (le pire des cas), une seule chose peut être unF
donc nous pouvons aussi bien ne pas l'avoir. EtF
contraint tellement il n'y a qu'une seule façon de le faire.Réponses:
Le logiciel est un jeu de promesses et de privilèges. Ce n'est jamais une bonne idée de promettre plus que ce que vous pouvez offrir ou plus que ce dont votre collaborateur a besoin.
Cela s'applique particulièrement aux types. Le point d'écrire une collection itérable est que son utilisateur peut itérer dessus - ni plus, ni moins. L'exposition du type concret
Array
crée généralement de nombreuses promesses supplémentaires, par exemple que vous pouvez trier la collection par une fonction de votre choix, sans parler du fait qu'une normaleArray
permettra probablement au collaborateur de modifier les données qui y sont stockées.Même si vous pensez que c'est une bonne chose ("Si le moteur de rendu remarque que la nouvelle option d'exportation est manquante, il peut simplement la corriger directement! Neat!"), Dans l'ensemble, cela diminue la cohérence de la base de code, ce qui la rend plus difficile à raisonner - et rendre le code facile à raisonner est le principal objectif du génie logiciel.
Maintenant, si votre collaborateur a besoin d'accéder à un certain nombre de trucs pour qu'ils soient assurés de n'en manquer aucun, vous implémentez une
Iterable
interface et exposez uniquement les méthodes déclarées par cette interface. De cette façon, l'année prochaine, lorsqu'une structure de données massivement meilleure et plus efficace apparaîtra dans votre bibliothèque standard, vous serez en mesure de changer le code sous-jacent et d'en bénéficier sans réparer votre code client partout . Il y a d'autres avantages à ne pas promettre plus que ce qui est nécessaire, mais celui-ci seul est si grand qu'en pratique, aucun autre n'est nécessaire.la source
Exposing the concrete type Array usually creates many additional promises, e.g. that you can sort the collection by a function of your own choosing...
- Brillant. Je n'y ai tout simplement pas pensé. Oui, cela ramène une «itération» et seulement, une itération!Masquer l'implémentation est un principe de base de la POO et une bonne idée dans tous les paradigmes, mais il est particulièrement important pour les itérateurs (ou quel que soit leur nom dans ce langage spécifique) dans les langages qui prennent en charge l'itération paresseuse.
Le problème avec l'exposition du type concret d'itérables - ou même des interfaces comme
IList<T>
- n'est pas dans les objets qui les exposent, mais dans les méthodes qui les utilisent. Par exemple, supposons que vous ayez une fonction pour imprimer une liste deFoo
s:Vous ne pouvez utiliser cette fonction que pour imprimer des listes de foo - mais seulement si elles implémentent
IList<Foo>
Mais que se passe-t-il si vous souhaitez imprimer chaque élément indexé de la liste? Vous devrez créer une nouvelle liste:
C'est assez long, mais avec LINQ, nous pouvons le faire sur une seule ligne, qui est en fait plus lisible:
Maintenant, avez-vous remarqué
.ToList()
la fin? Cela convertit la requête paresseuse en liste, afin que nous puissions la transmettre àPrintFoos
. Cela nécessite une allocation d'une deuxième liste, et deux passes sur les éléments (un sur la première liste pour créer la deuxième liste, et un autre sur la deuxième liste pour l'imprimer). Et si nous avons ceci:Et si
foos
a des milliers d'entrées? Nous devrons les parcourir tous et allouer une énorme liste juste pour en imprimer 6!Entrez Enumerators - la version C # du modèle d'itérateur. Au lieu que notre fonction accepte une liste, nous la faisons accepter
Enumerable
:Print6Foos
Peut maintenant paresseusement parcourir les 6 premiers éléments de la liste, et nous n'avons pas besoin de toucher le reste.Ne pas exposer la représentation interne est la clé ici. Lorsque
Print6Foos
nous avons accepté une liste, nous avons dû lui donner une liste - quelque chose qui prend en charge l'accès aléatoire - et nous avons donc dû allouer une liste, car la signature ne garantit pas qu'elle ne fera que l'itérer. En masquant l'implémentation, nous pouvons facilement créer unEnumerable
objet plus efficace qui ne prend en charge que ce dont la fonction a réellement besoin.la source
Exposer la représentation interne n'est pratiquement jamais une bonne idée. Cela rend non seulement le code plus difficile à raisonner, mais aussi plus difficile à maintenir. Imaginez que vous avez choisi n'importe quelle représentation interne - disons un
IList<T>
- et exposé cette interne. Toute personne utilisant votre code peut accéder à la liste et le code peut s'appuyer sur la représentation interne étant une liste.Pour une raison quelconque, vous décidez de modifier la représentation interne
IAmWhatever<T>
à un moment ultérieur. Au lieu de simplement changer les internes de votre classe, vous devrez changer chaque ligne de code et méthode en fonction de la représentation interne de typeIList<T>
. Ceci est fastidieux et sujet à des erreurs au mieux, lorsque vous êtes le seul à utiliser la classe, mais cela peut casser le code d'autres personnes utilisant votre classe. Si vous venez d'exposer un ensemble de méthodes publiques sans exposer aucun élément interne, vous auriez pu modifier la représentation interne sans aucune ligne de code en dehors de votre classe, en travaillant comme si rien n'avait changé.C'est pourquoi l'encapsulation est l'un des aspects les plus importants de toute conception de logiciel non triviale.
la source
Moins vous en dites, moins vous devez répéter.
Moins vous en demandez, moins vous devez être informé.
Si votre code expose uniquement
IEnumerable<T>
, qui ne prend en charge queGetEnumrator()
celui-ci, il peut être remplacé par tout autre code pouvant le prendre en chargeIEnumerable<T>
. Cela ajoute de la flexibilité.Si votre code l'utilise uniquement,
IEnumerable<T>
il peut être pris en charge par tout code qui implémenteIEnumerable<T>
. Encore une fois, il y a une flexibilité supplémentaire.Tous les linq-to-objets, par exemple, ne dépendent que de
IEnumerable<T>
. Bien qu'il accélère certaines implémentations particulières,IEnumerable<T>
il le fait d'une manière test et utilisation qui peut toujours se résumer à une simple utilisationGetEnumerator()
et à l'IEnumerator<T>
implémentation qui revient. Cela lui donne beaucoup plus de flexibilité que s'il était construit sur des tableaux ou des listes.la source
Un libellé que j'aime utiliser dans les miroirs Commentaire de @CaptainMan: "C'est bien pour un consommateur de connaître les détails internes. Il est mauvais pour un client d' avoir besoin de connaître les détails internes."
Si un client doit taper
array
ouIList<T>
dans son code, lorsque ces types étaient des détails internes, le consommateur doit les obtenir correctement. S'ils se trompent, le consommateur peut ne pas compiler ou même obtenir des résultats erronés. Cela donne les mêmes comportements que les autres réponses de «toujours masquer les détails de l'implémentation car vous ne devriez pas les exposer», mais commence à creuser les avantages de ne pas exposer. Si vous n'exposez jamais un détail d'implémentation, votre client ne pourra jamais se mettre en difficulté en l'utilisant!J'aime y penser sous cet angle car cela ouvre le concept de masquer l'implémentation à des nuances de sens, plutôt qu'à un draconien "cachez toujours vos détails d'implémentation". Il existe de nombreuses situations où l'exposition de l'implémentation est totalement valide. Prenons le cas des pilotes de périphériques en temps réel, où la possibilité de sauter des couches d'abstraction et de pousser des octets aux bons endroits peut faire la différence entre faire un timing ou non. Ou considérez un environnement de développement où vous n'avez pas le temps de créer de «bonnes API» et devez utiliser les choses immédiatement. Souvent, exposer des API sous-jacentes dans de tels environnements peut faire la différence entre fixer ou non un délai.
Cependant, à mesure que vous laissez ces cas spéciaux derrière vous et que vous regardez les cas plus généraux, il devient de plus en plus important de masquer l'implémentation. Exposer les détails de l'implémentation est comme une corde. Utilisé correctement, vous pouvez faire toutes sortes de choses avec, comme escalader de hautes montagnes. Utilisé incorrectement, il peut vous attacher à un nœud coulant. Moins vous en savez sur la façon dont votre produit sera utilisé, plus vous devez être prudent.
L'étude de cas que je vois maintes et maintes fois est celle où un développeur crée une API qui ne fait pas très attention à masquer les détails de la mise en œuvre. Un deuxième développeur, utilisant cette bibliothèque, constate qu'il manque à l'API certaines fonctionnalités clés qu'il souhaiterait. Plutôt que d'écrire une demande de fonctionnalité (qui pourrait avoir un délai d'exécution de plusieurs mois), ils décident plutôt d'abuser de leur accès aux détails d'implémentation cachés (ce qui prend quelques secondes). Ce n'est pas mauvais en soi, mais souvent le développeur d'API n'a jamais prévu que cela fasse partie de l'API. Plus tard, lorsqu'ils améliorent l'API et modifient ces détails sous-jacents, ils constatent qu'ils sont liés à l'ancienne implémentation, car quelqu'un l'a utilisée dans le cadre de l'API. La pire version est celle où quelqu'un a utilisé votre API pour fournir une fonctionnalité centrée sur le client, et maintenant votre entreprise » chèque de paie est lié à cette API défectueuse! Simplement en exposant les détails de l'implémentation alors que vous ne saviez pas assez sur vos clients s'est avéré être assez de corde pour attacher un nœud coulant autour de votre API!
la source
Vous ne savez pas nécessairement quelle est la représentation interne de vos données - ou pourrait devenir à l'avenir.
Supposons que vous ayez une source de données que vous représentez sous forme de tableau, vous pouvez itérer dessus avec une boucle for régulière.
Maintenant, dites que vous voulez refactoriser. Votre patron a demandé que la source de données soit un objet de base de données. En outre, il aimerait avoir la possibilité d'extraire des données d'une API, ou d'une table de hachage, ou d'une liste liée, ou d'un tambour magnétique en rotation, ou d'un microphone, ou d'un décompte en temps réel des copeaux de yak trouvés en Mongolie extérieure.
Eh bien, si vous n'en avez qu'une pour la boucle, vous pouvez facilement modifier votre code pour accéder à l'objet de données de la manière à laquelle il s'attend à y accéder.
Si 1000 classes essaient d'accéder à l'objet de données, vous avez maintenant un gros problème de refactoring.
Un itérateur est un moyen standard d'accéder à une source de données basée sur une liste. C'est une interface commune. Son utilisation rendra votre source de données de code agnostique.
la source