¿Qué hace que el iterador sea un patrón de diseño?

9

Me he estado preguntando qué es lo que hace que el Iterador sea especial en comparación con otras construcciones similares, y que hizo que la Gang of Four enumerarlo 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é pasa 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, power funciones El diagrama de clase sería el mismo.

Probablemente podríamos encontrar muchos más ejemplos similares como Writer, Painter, Encoder y probablemente mejores, que funcionan de la misma manera. Sin embargo, nunca he oído que ninguno de estos se llame un patrón de diseño.

Entonces, ¿qué hace especial al iterador?

¿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, déjame 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 de iteradores):

Para la separación de inquietudes, las implementaciones de la operación no deben agregarse como funciones a la jerarquía de clases original (objetos del operando). Como queremos aplicar la operación varias veces en el mismo operando, debe representarse mediante un objeto que contenga una referencia al operando, no solo mediante 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é, quizás alguien tenga una mejor idea) con las clases derivadas MyInteger y MyMatrix . Para cada MathObject se debe definir una operación Power que permita el cálculo de cuadrados, cubos, etc. Así 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
    
pregunta Frank Puffer 26.11.2016 - 20:52

2 respuestas

9

La mayoría de los patrones del libro de GoF tienen las siguientes cosas en común:

  • resuelven problemas básicos de diseño , usando medios orientados a objetos
  • las personas a menudo enfrentan este tipo de problemas en programas arbitrarios, independientemente del dominio o negocio
  • son recetas para hacer que el código sea más reutilizable, a menudo haciéndolo más SÓLIDO
  • presentan soluciones canónicas a estos problemas

Los problemas resueltos por estos patrones son tan básicos que muchos desarrolladores los entienden principalmente como soluciones alternativas para las características faltantes del lenguaje de programación , que, en mi opinión, son un punto de vista válido (tenga en cuenta que el libro GoF es de 1995, donde Java y C ++ no ofrecían tantas funciones como hoy).

El patrón del iterador encaja bien en esta descripción: resuelve un problema básico que ocurre muy a menudo, independientemente de cualquier dominio específico, y como usted escribió, es un buen ejemplo para la "separación de preocupaciones". Como seguramente sabrá, el soporte de iterador directo es algo que se encuentra en muchos lenguajes de programación contemporáneos.

Ahora compare esto con los problemas que seleccionó:

  • escribir en un archivo, en mi humilde opinión simplemente no es lo suficientemente "básico". Es un problema muy específico. Tampoco hay una buena solución canónica: hay muchos enfoques diferentes sobre cómo escribir en un archivo y no hay una "buena práctica" clara.
  • Pintor, Codificador: sea lo que sea que tenga en mente con eso, esos problemas me parecen incluso menos básicos, y ni siquiera son independientes del dominio.
  • tener la función de "poder" disponible para diferentes tipos de objetos: a primera vista, podría valer la pena ser un patrón, pero su solución propuesta no me convence, se parece más a un intento de calentar la función de poder en Algo similar al patrón iterador. Implementé una gran cantidad de código con cálculos de ingeniería, pero no puedo recordar una situación en la que un enfoque similar a su función de poder me hubiera ayudado (sin embargo, los iteradores son algo con lo que tengo que lidiar a diario).

Además, no veo nada en su ejemplo de función de poder que no pueda interpretarse como una aplicación del patrón de estrategia o del patrón de comando, lo que significa que esas partes básicas ya están en el libro de GoF. Una mejor solución podría contener la sobrecarga del operador o los métodos de extensión, pero esas cosas están sujetas a las características del idioma, y eso es exactamente lo que "OO significa" utilizado por "Gang" no pudo proporcionar.

    
respondido por el Doc Brown 27.11.2016 - 20:37
8

La banda de los cuatro cita la definición del patrón de Christopher Alexander:

  

Cada patrón describe un problema que ocurre una y otra vez en nuestro entorno, y luego describe el núcleo de la solución a ese problema [...]

¿Cuál es el problema resuelto por los iteradores?

  

Intención: Proporciona una forma de acceder a los elementos de un objeto agregado de forma secuencial sin exponer su representación subyacente.

     

...

     

Aplicabilidad: Usa el patrón de iterador

     
  • para acceder a los contenidos de un objeto agregado sin exponer su representación interna
  •   
  • para admitir múltiples recorridos de objetos agregados
  •   
  • para proporcionar una interfaz uniforme para atravesar diferentes estructuras agregadas (es decir, para soportar la iteración polimórfica).
  •   

Por lo tanto, se podría argumentar que el patrón del 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 del dominio para lenguajes específicos del dominio, los patrones de fábrica son específicos del dominio para la creación de objetos, ... Por supuesto, esta es una comprensión bastante tonta de "dominio específico". Siempre que sea un par de problema-solución recurrente, podemos llamarlo un patrón.

Y es bueno que el patrón Iterator exista. Las cosas malas pasan 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 tiempo: cada bucle:

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

Pero, ¿y si llamamos a una función en el cuerpo del bucle?

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

Esta función ahora puede hacer prácticamente lo que quiera, excepto:

  • agregue o elimine entradas de hash, ya que esto alteraría el orden de iteración de forma impredecible (en C ++, hablar, esto invalidaría el iterador).
  • iterar la misma tabla hash sin hacer una copia, ya que consumiría el mismo estado de iteración. Uy.

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 de 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 del iterador está relacionado con otros patrones. Por ejemplo, ¿cómo se instancia el iterador? En Java, tenemos una interfaz genérica Iterable<T> y Iterator<T> . Un iterable concreto como ArrayList<T> crea un tipo particular de iterador, mientras que un HashSet<T> puede suministrar un tipo de iterador completamente diferente. Eso me recuerda mucho al patrón abstracto de fábrica, donde Iterable<T> es la fábrica abstracta y el Iterator es el producto.

Un iterador polimórfico también se puede interpretar como un ejemplo del patrón de estrategia. P.ej. un árbol puede ofrecer diferentes tipos de iteradores (pedido anticipado, pedido, pedido posterior, ...). Externamente, todos compartirían una interfaz de iterador y producirían elementos en alguna secuencia. El código del cliente solo necesita depender de la interfaz del iterador, no de ningún algoritmo de recorrido de árbol en particular.

Los patrones no existen aislados, independientes unos de otros. 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 de patrones no se cierra cuando pasas a la última página del libro de Patrones de diseño (consulta también tu pregunta anterior Did the Gang de cuatro explora a fondo el “Espacio de patrones”? ). Los patrones descritos en el libro Design Patterns son muy flexibles y amplios, abiertos a infinitas variaciones, y definitivamente no son los únicos patrones que existen.

Los conceptos que enumera (escritura, pintura, codificación) no son patrones porque no describen una combinación de problema-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 se pinten en la pantalla los gráficos a medio hacer", entonces puede existir un patrón: "¡Lo sé, usaré gráficos con doble búfer!"

    
respondido por el amon 26.11.2016 - 23:27

Lea otras preguntas en las etiquetas