Qu'est-ce qui fait de l'itérateur un modèle de conception?

9

Je me demandais ce qui rend l'itérateur spécial par rapport à d'autres constructions similaires, et qui a fait de la liste Gang of Four un modèle de conception.

L'itérateur est basé sur le polymorphisme (une hiérarchie de collections avec une interface commune) et la séparation des préoccupations (l'itération sur les collections doit être indépendante de la façon dont les données sont structurées).

Mais que se passe-t-il si nous remplaçons la hiérarchie des collections par, par exemple, une hiérarchie d'objets mathématiques (entier, flottant, complexe, matrice, etc.) et l'itérateur par une classe représentant certaines opérations connexes sur ces objets, par exemple les fonctions de puissance. Le diagramme de classe serait le même.

Nous pourrions probablement trouver de nombreux autres exemples similaires comme Writer, Painter, Encoder, et probablement de meilleurs exemples, qui fonctionnent de la même manière. Cependant, je n'ai jamais entendu aucun d'entre eux s'appeler un motif de conception.

Alors, qu'est-ce qui rend l'itérateur spécial?

Est-ce le fait qu'il est plus compliqué car il nécessite un état mutable pour stocker la position actuelle dans la collection? Mais alors, l'état mutable n'est généralement pas considéré comme souhaitable.


Pour clarifier mon propos, permettez-moi de donner un exemple plus détaillé.

Voici notre problème de conception:

Disons que nous avons une hiérarchie de classes et une opération définies sur les objets de ces classes. L'interface de cette opération est la même pour chaque classe, mais les implémentations peuvent être complètement différentes. Il est également supposé qu'il est judicieux d'appliquer l'opération plusieurs fois sur le même objet, par exemple avec des paramètres différents.

Voici une solution raisonnable à notre problème de conception (pratiquement une généralisation du modèle d'itérateur):

Pour la séparation des préoccupations, les implémentations de l'opération ne doivent pas être ajoutées en tant que fonctions à la hiérarchie de classe d'origine (objets opérande). Puisque nous voulons appliquer l'opération plusieurs fois sur le même opérande, elle doit être représentée par un objet contenant une référence à l'opérande, et pas seulement par une fonction. Par conséquent, l'opérande doit fournir une fonction qui renvoie l'objet représentant l'opération. Cet objet fournit une fonction qui exécute l'opération réelle.

Un exemple:

Il y a une classe de base ou une interface MathObject(nom stupide, je sais, peut-être que quelqu'un a une meilleure idée.) Avec des classes dérivées MyIntegeret MyMatrix. Pour chacun, MathObjectune opération Powerdoit être définie qui permet le calcul du carré, du cube, etc. On pourrait donc écrire (en Java):

MathObject i = new MyInteger(5);
Power powerOfFive = i.getPower();
MyInteger square = powerOfFive.calculate(2); // should return 25
MyInteger cube = powerOfFive.calculate(3); // should return 125
Frank Puffer
la source
5
"Le diagramme de classe serait le même" - alors quoi? Un modèle de conception n'est pas un diagramme de classes. Il s'agit d'une abstraction de haut niveau pour une classe de solutions à un problème récurrent.
Doc Brown
@DocBrown: D'accord, mais les opérations mathématiques, l'écriture d'objets dans un fichier, la sortie graphique ou l'encodage de données ne sont-elles pas des problèmes récurrents, tout comme l'itération?
Frank Puffer
Le choix du Design Pattern est subjectif (c'est-à-dire aux yeux des "designers" ou des personnes qui jugent les designs). La dénomination des modèles de conception est destinée à être indépendante du domaine (afin que nous ne soyons pas distraits en pensant qu'elle est spécifique au domaine). Juste mon avis, je n'ai aucune référence à citer.
rwong
@FrankPuffer Si vous décrivez une solution courante pour écrire des objets dans un fichier, vous pouvez écrire votre solution et l'appeler le modèle d'écriture d'objet si cela est utile.
Brandin
3
Vous y pensez trop. Un modèle de conception est une solution bien connue à un problème informatique courant, et c'est tout. Vous utilisez le modèle lorsque vous pouvez reconnaître et appliquer les avantages qu'il offre.
Robert Harvey

Réponses:

9

La plupart des modèles du livre du GoF ont les points communs suivants:

  • ils résolvent les problèmes de conception de base , en utilisant des moyens orientés objet
  • les gens sont souvent confrontés à ce genre de problèmes dans des programmes arbitraires, indépendamment du domaine ou de l'entreprise
  • ce sont des recettes pour rendre le code plus réutilisable, souvent en le rendant plus SOLIDE
  • ils présentent des solutions canoniques à ces problèmes

Les problèmes résolus par ces modèles sont si basiques que de nombreux développeurs les comprennent principalement comme des solutions de contournement pour les fonctionnalités manquantes du langage de programmation , ce qui est à mon humble avis un point de vue valide (notez que le livre du GoF date de 1995, où Java et C ++ n'offraient pas autant de fonctionnalités). caractéristiques comme aujourd'hui).

Le modèle d'itérateur s'inscrit bien dans cette description: il résout un problème de base qui se produit très souvent, indépendamment de tout domaine spécifique, et comme vous l'avez écrit par vous-même, c'est un bon exemple de «séparation des préoccupations». Comme vous le savez sûrement, la prise en charge directe des itérateurs est quelque chose que vous trouvez aujourd'hui dans de nombreux langages de programmation contemporains.

Comparez maintenant ceci aux problèmes que vous avez choisis:

  • écrire dans un fichier - c'est à mon humble avis tout simplement pas assez "basique". C'est un problème très spécifique. Il n'y a pas non plus de bonne solution canonique - il existe de nombreuses approches différentes pour écrire dans un fichier, et aucune "meilleure pratique" claire.
  • Painter, Encoder: quoi que vous ayez à l'esprit, ces problèmes me semblent encore moins fondamentaux, et même indépendants du domaine.
  • avoir la fonction "power" disponible pour différents types d'objets: à première vue, cela pourrait valoir la peine d'être un motif, mais la solution que vous proposez ne me convainc pas - cela ressemble plus à une tentative de chausse-pied la fonction power en quelque chose de similaire à le modèle d'itérateur. J'ai implémenté beaucoup de code avec des calculs d'ingénierie, mais je ne me souviens pas d'une situation où une approche similaire à votre objet de fonction de puissance m'aurait aidé (cependant, les itérateurs sont quelque chose que je dois gérer quotidiennement).

De plus, je ne vois rien dans votre exemple de fonction de puissance qui ne puisse pas être interprété comme une application du modèle de stratégie ou du modèle de commande, ce qui signifie que ces parties de base sont déjà dans le livre du GoF. Une meilleure solution pourrait contenir une surcharge d'opérateur ou des méthodes d'extension, mais ce sont des choses qui sont soumises aux fonctionnalités du langage, et c'est exactement ce que le «OO signifie» utilisé par le «Gang» ne pouvait pas fournir.

Doc Brown
la source
The problems solved by these patterns are so basic that many developers think their main purpose is to be workarounds for missing programming language features- L'ironie est que les développeurs de logiciels utilisent régulièrement des modèles de conception de logiciels qui ont 20 ans tout en croyant qu'ils écrivent du code de pointe.
Robert Harvey
@RobertHarvey: Je ne pense pas que beaucoup de développeurs implémenteraient le modèle d'itérateur aujourd'hui de la "manière OO" suggérée par le GoF. Ils l'implémentent généralement par les moyens fournis par le langage ou par sa lib standard (par exemple, en C # en utilisant IEnumerableet yield). Mais pour d'autres modèles du GoF, ce que vous avez écrit pourrait probablement être vrai.
Doc Brown
1
Pertinent pour workarounds for missing programming language features: blog.plover.com/prog/johnson.html
jrw32982 prend en charge Monica
8

The Gang of Four cite la définition du motif de Christopher Alexander:

Chaque modèle décrit un problème qui se produit encore et encore dans notre environnement, puis décrit le cœur de la solution à ce problème […]

Quel est le problème résolu par les itérateurs?

Intention: fournir un moyen d'accéder séquentiellement aux éléments d'un objet agrégé sans exposer sa représentation sous-jacente.

Applicabilité: utilisez le modèle Iterator

  • accéder au contenu d'un objet agrégé sans exposer sa représentation interne
  • pour prendre en charge plusieurs traversées d'objets agrégés
  • pour fournir une interface uniforme pour traverser différentes structures d'agrégats (c'est-à-dire pour prendre en charge l'itération polymorphe).

On pourrait donc affirmer que le modèle d'itérateur est par définition propre au domaine des collections. Et c'est parfaitement OK. D'autres modèles comme le modèle d'interpréteur sont spécifiques au domaine pour les langages spécifiques au domaine, les modèles d'usine sont spécifiques au domaine pour la création d'objets,…. Bien sûr, c'est une compréhension plutôt stupide de «spécifique au domaine». Tant qu'il s'agit d'un couple problème-solution récurrent, nous pouvons l'appeler un modèle.

Et c'est bien que le modèle Iterator existe. De mauvaises choses se produisent si vous ne l'utilisez pas. Mon anti-exemple préféré est Perl. Ici, chaque collection (tableau ou hachage) inclut l'état de l'itérateur dans le cadre de la collection. Pourquoi est-ce mauvais? Nous pouvons facilement parcourir un hachage avec une boucle while – each:

while (my ($key, $value) = each %$hash) {
  say "$key => $value";
}

Mais que faire si nous appelons une fonction dans le corps de la boucle?

while (my ($key, $value) = each %$hash) {
  do_something_with($key, $value, $hash);
}

Cette fonction peut maintenant faire à peu près tout ce qu'elle veut, sauf:

  • ajouter ou supprimer des entrées de hachage, car celles-ci modifieraient de manière imprévisible l'ordre d'itération (en C ++ - parler, cela invaliderait l'itérateur).
  • itérer la même table de hachage sans faire de copie, car cela consommerait le même état d'itération. Oops.

Si la fonction appelée doit utiliser l'itérateur, le comportement de notre boucle devient indéfini. C'est un problème. Et le modèle d'itérateur a une solution: mettre tout l'état d'itération dans un objet distinct créé par itération.

Oui, bien sûr, le modèle d'itérateur est lié à d'autres modèles. Par exemple, comment l'itérateur est-il instancié? En Java, nous avons un générique Iterable<T>et une Iterator<T>interface. Un élément concret itérable ArrayList<T>crée un type d'itérateur particulier, tandis qu'un HashSet<T>élément peut fournir un type d'itérateur complètement différent. Cela me rappelle beaucoup le modèle de l'usine abstraite, où Iterable<T>est l'usine abstraite et Iteratorle produit.

Un itérateur polymorphe peut également être interprété comme un exemple du modèle de stratégie. Par exemple, un arbre peut proposer différents types d'itérateurs (pré-commande, dans l'ordre, post-commande,…). Extérieurement, ceux-ci partageraient tous une interface d'itérateur et produiraient des éléments dans une certaine séquence. Le code client doit uniquement dépendre de l'interface de l'itérateur, pas d'un algorithme de traversée d'arbre particulier.

Les modèles n'existent pas isolément, indépendamment les uns des autres. Certains modèles sont des solutions différentes au même problème, et certains modèles décrivent la même solution dans des contextes différents. Certains modèles en impliquent un autre. De plus, l'espace des motifs n'est pas fermé lorsque vous tournez la dernière page du livre sur les motifs de conception (voir également votre question précédente Le gang des quatre a-t-il exploré en profondeur «Espace des motifs»? ). Les modèles décrits dans le livre Design Patterns sont très flexibles et larges, ouverts à des variations infinies, et certainement pas les seuls modèles existants.

Les concepts que vous énumérez (écriture, peinture, encodage) ne sont pas des modèles car ils ne décrivent pas une combinaison problème-solution. Une tâche comme «J'ai besoin d'écrire des données» ou «J'ai besoin d'encoder des données» n'est pas vraiment un problème de conception et n'inclut pas de solution; une «solution» qui consiste simplement en «Je sais, je vais créer une classe Writer» n'a pas de sens. Mais si nous avons un problème comme "Je ne veux pas que des graphiques à demi-rendu soient peints à l'écran", alors un motif peut exister: "Je sais, je vais utiliser des graphiques à double tampon!"

amon
la source
Bonne réponse, merci. Je ne suis toujours pas complètement convaincu que ce que vous écrivez dans le dernier paragraphe s'applique ici. J'ai édité ma question pour expliquer ce que je veux dire.
Frank Puffer