Me he estado preguntando qué es lo que hace que el Iterator sea especial en comparación con otras construcciones similares, y que hizo que la Gang of Four lo enumerara como un patrón de diseño.
El iterador se basa en el polimorfismo (una jerarquía de colecciones con una interfaz común) y la separación de preocupaciones (la iteración sobre las colecciones debe ser independiente de la forma en que se estructuran los datos).
Pero, ¿qué sucede si reemplazamos la jerarquía de colecciones con, por ejemplo, una jerarquía de objetos matemáticos (entero, flotante, complejo, matriz, etc.) y el iterador por una clase que representa algunas operaciones relacionadas en estos objetos, por ejemplo, funciones de potencia. El diagrama de clases sería el mismo.
Probablemente podríamos encontrar muchos más ejemplos similares como Writer, Painter, Encoder y probablemente mejores, que funcionen de la misma manera. Sin embargo, nunca he oído que ninguno de estos se llame Patrón de diseño.
Entonces, ¿qué hace que el iterador sea especial?
¿Es el hecho de que es más complicado porque requiere un estado mutable para almacenar la posición actual dentro de la colección? Pero entonces, el estado mutable generalmente no se considera deseable.
Para aclarar mi punto, permítanme dar un ejemplo más detallado.
Aquí está nuestro problema de diseño:
Digamos que tenemos una jerarquía de clases y una operación definida en los objetos de estas clases. La interfaz de esta operación es la misma para cada clase, pero las implementaciones pueden ser completamente diferentes. También se supone que tiene sentido aplicar la operación varias veces en el mismo objeto, por ejemplo, con diferentes parámetros.
Aquí hay una solución sensata para nuestro problema de diseño (prácticamente una generalización del patrón iterador):
Para la separación de preocupaciones, las implementaciones de la operación no deben agregarse como funciones a la jerarquía de clases original (objetos de operando). Dado que queremos aplicar la operación varias veces en el mismo operando, debe estar representado por un objeto que tenga una referencia al operando, no solo por una función. Por lo tanto, el objeto operando debe proporcionar una función que devuelva el objeto que representa la operación. Este objeto proporciona una función que realiza la operación real.
Un ejemplo:
Hay una clase base o interfaz MathObject
(nombre estúpido, lo sé, tal vez alguien tiene una mejor idea) con clases derivadas MyInteger
y MyMatrix
. Para cada MathObject
una se Power
debe definir una operación que permita el cálculo de cuadrado, cubo, etc. Entonces podríamos escribir (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
fuente
Respuestas:
La mayoría de los patrones del libro GoF tienen las siguientes cosas en común:
Los problemas resueltos por estos patrones son tan básicos que muchos desarrolladores los entienden principalmente como soluciones para las características faltantes del lenguaje de programación , lo cual es un punto de vista válido (tenga en cuenta que el libro GoF es de 1995, donde Java y C ++ no ofrecían tantos características como hoy).
El patrón iterador encaja bien en esta descripción: resuelve un problema básico que ocurre con mucha frecuencia, independientemente de cualquier dominio específico, y como usted mismo escribió, es un buen ejemplo de "separación de preocupaciones". Como seguramente sabrá, el soporte de iterador directo es algo que se encuentra en muchos lenguajes de programación actuales.
Ahora compare esto con los problemas que eligió:
Además, no veo nada en su ejemplo de función de potencia que no pueda interpretarse como una aplicación del patrón de estrategia o el patrón de comando, lo que significa que esas partes básicas ya están en el libro GoF. Una mejor solución podría contener métodos de sobrecarga del operador o de extensión, pero esas son cosas que están sujetas a las características del lenguaje, y eso es exactamente lo que el "OO significa" utilizado por la "Pandilla" no podría proporcionar.
fuente
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
- La ironía es que los desarrolladores de software usan rutinariamente patrones de diseño de software que tienen 20 años de edad y siguen creyendo que están escribiendo código de última generación.IEnumerable
yyield
). Pero para otros patrones de GoF, lo que escribiste probablemente sea cierto.workarounds for missing programming language features:
blog.plover.com/prog/johnson.htmlThe Gang of Four cita la definición del patrón de Christopher Alexander:
¿Cuál es el problema resuelto por los iteradores?
Entonces, uno podría argumentar que el patrón iterador es, por definición, específico del dominio para las colecciones. Y eso está perfectamente bien. Otros patrones, como el patrón de intérprete, son específicos de dominio para lenguajes específicos de dominio, los patrones de fábrica son específicos de dominio para la creación de objetos, ... Por supuesto, esta es una comprensión bastante tonta de "específico de dominio". Mientras sea un par recurrente de problema-solución, podemos llamarlo un patrón.
Y es bueno que exista el patrón Iterator. Suceden cosas malas si no lo usas. Mi anti-ejemplo favorito es Perl. Aquí, cada colección (matriz o hash) incluye el estado del iterador como parte de la colección. ¿Por qué es esto malo? Podemos iterar fácilmente sobre un hash con un while, cada bucle:
¿Pero qué pasa si llamamos a una función en el cuerpo del bucle?
Esta función ahora puede hacer casi lo que quiera, excepto:
Si la función llamada debe usar el iterador, el comportamiento de nuestro bucle se vuelve indefinido. Eso es un problema. Y el patrón iterador tiene una solución: poner todo el estado de iteración en un objeto separado que se crea por iteración.
Sí, por supuesto, el patrón iterador está relacionado con otros patrones. Por ejemplo, ¿cómo se instancia el iterador? En Java, tenemos un genérico
Iterable<T>
y unaIterator<T>
interfaz. Un iterable concreto comoArrayList<T>
crea un tipo particular de iterador, mientras que unHashSet<T>
puede proporcionar un tipo de iterador completamente diferente. Eso me recuerda mucho el patrón abstracto de fábrica, dondeIterable<T>
es la fábrica abstracta yIterator
el producto.Un iterador polimórfico también puede interpretarse como un ejemplo del patrón de estrategia. Por ejemplo, un árbol puede ofrecer diferentes tipos de iteradores (pre-orden, en orden, post-orden, ...). Externamente, todos compartirían una interfaz de iterador y producirían elementos en alguna secuencia. El código del cliente solo debe depender de la interfaz del iterador, no de ningún algoritmo de recorrido de árbol en particular.
Los patrones no existen de forma aislada, independientes entre sí. Algunos patrones son soluciones diferentes para el mismo problema, y algunos patrones describen la misma solución en diferentes contextos. Algunos patrones implican otro. Además, el espacio del patrón no se cierra cuando pasa la última página del libro de Patrones de diseño (consulte también su pregunta anterior ¿La Banda de los Cuatro exploró a fondo el "Espacio del patrón"? ). Los patrones descritos en el libro de Patrones de diseño son muy flexibles y amplios, abiertos a variaciones infinitas, y definitivamente no son los únicos patrones existentes.
Los conceptos que enumera (escritura, pintura, codificación) no son patrones porque no describen una combinación de problema y solución. Una tarea como "Necesito escribir datos" o "Necesito codificar datos" no es realmente un problema de diseño y no incluye una solución; una "solución" que solo consiste en "lo sé, crearé una clase de escritor" no tiene sentido. Pero si tenemos un problema como "No quiero que los gráficos a medio renderizar se pinten en la pantalla", entonces puede existir un patrón: "¡Lo sé, usaré gráficos con doble búfer!"
fuente