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