Patrón de iterador
En la programación orientada a objetos, el patrón de iterador es un patrón de diseño en el que se utiliza un iterador para atravesar un contenedor y acceder a los elementos del contenedor. El patrón iterador desacopla los algoritmos de los contenedores; en algunos casos, los algoritmos son necesariamente específicos del contenedor y, por lo tanto, no se pueden desacoplar.
Por ejemplo, el algoritmo hipotético SearchForElement generalmente se puede implementar utilizando un tipo específico de iterador en lugar de implementarlo como un algoritmo específico del contenedor. Esto permite usar SearchForElement en cualquier contenedor que admita el tipo de iterador requerido.
Resumen
El iterador patrón de diseño es uno de los veintitrés bien conocidos Patrones de diseño de GoF que describen cómo resolver problemas de diseño recurrentes para diseñar software orientado a objetos flexible y reutilizable, es decir, objetos que son más fáciles de implementar, cambiar, probar y reutilizar.
¿Qué problemas puede resolver el patrón de diseño Iterator?
- Los elementos de un objeto agregado deben ser accedidos y atravesados sin exponer su representación (estructuras de datos).
- Las nuevas operaciones transversales deben definirse para un objeto agregado sin cambiar su interfaz.
La definición de operaciones de acceso y cruce en la interfaz agregada es inflexible porque compromete el agregado a determinadas operaciones de acceso y cruce y hace que sea imposible agregar nuevas operaciones. más tarde sin tener que cambiar la interfaz agregada.
¿Qué solución describe el patrón de diseño Iterator?
- Define un objeto separado (iterator) que encapsula el acceso y la exploración de un objeto agregado.
- Los clientes utilizan un iterador para acceder y atravesar un agregado sin conocer su representación (estructuras de datos).
Se pueden usar diferentes iteradores para acceder y atravesar un agregado de diferentes maneras.
Las nuevas operaciones de acceso y recorrido se pueden definir de forma independiente mediante la definición de nuevos iteradores.
Vea también la clase UML y el diagrama de secuencia a continuación.
Definición
La esencia del patrón Iterator es "Proporcionar una forma de acceder a los elementos de un objeto agregado secuencialmente sin exponer su representación subyacente.".
Estructura
Clase UML y diagrama de secuencia
En el diagrama de clases UML anterior, la clase Client
se refiere (1) a la interfaz Agregate
para crear un objeto Iterator
(createIterator()
) y (2) a la interfaz Iterator
para atravesar un objeto Agregate
(next(),hasNext()
).
La clase Iterator1
implementa la interfaz Iterator
accediendo a la clase Aggregate1
.
El diagrama de secuencia UML
muestra las interacciones en tiempo de ejecución: el objeto Client
llama a createIterator()
en un objeto Aggregate1
, que crea un Iterator1
objeto y lo devuelve
al Cliente
.
El Client
usa entonces Iterator1
para recorrer los elementos del objeto Agregate1
.
Diagrama de clases UML
Implementación específica del idioma
Algunos lenguajes estandarizan la sintaxis. C++ y Python son ejemplos notables.
C#
.NET Framework tiene interfaces especiales que admiten una iteración simple: System.Collections.IEnumerator
sobre una colección no genérica y System.Collections.Generic.IEnumerator<T>
sobre una colección genérica.
La instrucción C# foreach
está diseñada para iterar fácilmente a través de la colección que implementa System.Collections.IEnumerator
y/o System.Collections.Generic.IEnumerator<T>
interfaz. Desde C# v2, foreach
también puede iterar a través de tipos que implementan System.Collections.Generic.IEnumerable<T>
y System.Collections.Generic.IEnumerator< T>
Ejemplo de uso de la instrucción foreach
:
Var primos = nuevo Lista.int■ {} 2, 3, 5, 7, 11, 13, 17, 19 };largo m = 1;en adelante ()Var primo dentro primos) m *= primo;
C++
C++ implementa iteradores con la semántica de punteros en ese lenguaje. En C++, una clase puede sobrecargar todas las operaciones de puntero, por lo que se puede implementar un iterador que actúe más o menos como un puntero, completo con desreferencia, incremento y disminución. Esto tiene la ventaja de que los algoritmos de C++ como std::sort
se pueden aplicar inmediatamente a los búferes de memoria simples y antiguos, y que no hay nueva sintaxis que aprender. Sin embargo, requiere un "fin" iterador para probar la igualdad, en lugar de permitir que un iterador sepa que ha llegado al final. En lenguaje C++, decimos que un iterador modela el concepto de iterador.
Esta implementación de C++11 se basa en el capítulo "Generalizar vector una vez más".
#include ■iostream#include - No.clase Vectorial {}público: utilizando iterator = doble*; iterator comenzar() {} retorno "Elem[0]; } iterator final() {} retorno "Elem[sz]; } Vectorial()int s) :Elem()nullptr), sz()0) {} si ()s . 0) tiro std::fuera de fuera()"Vector::Vector"); Elem = nuevo doble[s]; sz = s; } ~Vectorial() {} Borrador[] Elem; } int tamaño() const {} retorno sz; } doble #()int n) const {} si ()n . 0 Silencio n >= sz) tiro std::fuera de fuera()"Vector::get"); retorno Elem[n]; } vacío set()int n, doble v) {} si ()n . 0 Silencio n >= sz) tiro std::fuera de fuera()"Vector::set"); Elem[n] = v; } Vectorial()const Vectorial") = Borrador; // regla de tres Vectorial" operador=()const Vectorial") = Borrador;privado: doble* Elem; int sz;};int principal() {} Vectorial v()2); // v tiene 2 elementos v.set()0, 1.1*1.1); v.set()1, 2.2*2.2); para ()const auto" x : v) {} std::Cout .. x .. 'n '; } para ()auto i = v.comenzar(); i ! v.final(); ++i) {} std::Cout .. *i .. 'n '; } para ()auto i = 0; i . 3; ++i) {} std::Cout .. v.#()i) .. 'n '; } }
La salida del programa es
1.214.841.214.841.214.84terminada llamado después lanzamiento an ejemplo de 'std::fuera de fuera' ¿Qué?(): Vectorial::#
Java
Java tiene la interfaz Iterator
.
Un ejemplo simple que muestra cómo devolver números enteros entre [inicio, fin] usando un Iterator
importación Java.util. Iterator;importación java.util.NoSuchElementException;público clase RangeIterator Ejemplo {} público estática Iterator.Integer■ rango()int Empieza, int final) {} retorno nuevo Iterator■() {} privado int índice = Empieza; @Override público boolean haSiguiente() {} retorno índice . final; } @Override público Integer siguiente() {} si ()!haSiguiente()) {} tiro nuevo NoSuchElementException(); } retorno índice++; } }; } público estática vacío principal()String[] args) {} Var iterator = rango()0, 10); mientras ()iterator.haSiguiente()) {} Sistema.Fuera..println()iterator.siguiente()); } // o utilizando un lambda iterator.paraCada()Sistema.Fuera.::println); }}
A partir de Java 5, los objetos que implementan la interfaz Iterable
, que devuelve un Iterator
desde su único método, se pueden atravesar utilizando la sintaxis de bucle foreach de Java. La interfaz Collection
del marco de colecciones de Java amplía Iterable
.
Ejemplo de clase Family
implementando la interfaz Iterable
:
importación Java.util. Iterator;importación Java.util. Set;clase Familia.E■ implementos Iterable.E■ {} privado final Set.E■ elementos; público Familia()Set.E■ elementos) {} esto.elementos = Set.Copia()elementos); } @Override público Iterator.E■ iterator() {} retorno elementos.iterator(); }}
La clase IterableExample
demuestra el uso de la clase Family
:
público clase IterableExample {} público estática vacío principal()String[] args) {} Var weasleys = Set.de() "Arthur", "Molly", "Bill", "Charlie", "Percy", "Fred", "George", "Ron", "Ginny" ); Var familia = nuevo Familia■()weasleys); para ()Var Nombre : familia) {} Sistema.Fuera..println()Nombre + "Weasley"); } }}
Salida:
Ron WeasleyMolly WeasleyPercy WeasleyFred WeasleyCharlie WeasleyGeorge WeasleyArthur WeasleyGinny WeasleyBill Weasley
Javascript
JavaScript, como parte de ECMAScript 6, admite el patrón iterador con cualquier objeto que proporcione un método next()
, que devuelve un objeto con dos propiedades específicas: done
y valor. Aquí hay un ejemplo que muestra un iterador de matriz inversa:
función reversoArrayIterator()array) {} Var índice = array.longitud - 1; retorno {} siguiente: () = índice >= 0 ? {}valor: array[índice--] hecho: falso} : {}hecho: verdadero} }}const es = reversoArrayIterator[Tres ', 'dos ', 'uno ']);consola.log()es.siguiente().valor); //- 'uno'consola.log()es.siguiente().valor); //- 'dos'consola.log()es.siguiente().valor); //- Tres 'consola.log()` ¿Terminaste? ${es.siguiente().hecho}`); //- verdadero
La mayoría de las veces, sin embargo, es deseable proporcionar semántica de iterador en los objetos para que puedan iterarse automáticamente a través de bucles for...of
. Algunos de los tipos integrados de JavaScript como Array
, Map
o Set
ya definen su propio comportamiento de iteración. Se puede lograr el mismo efecto definiendo el método meta @@iterator
de un objeto, también denominado Symbol.iterator
. Esto crea un objeto iterable.
Este es un ejemplo de una función de rango que genera una lista de valores desde start
hasta end
, exclusivo, usando un for
bucle para generar los números:
función rango()Empieza, final) {} retorno {} [Signatura.iterator]() {} // retorno esto; } siguiente() {} si ()Empieza . final) {} retorno {} valor: Empieza++, hecho: falso }; // } retorno {} hecho: verdadero, valor: final }; // } }}para ()Número de rango()1, 5) {} consola.log()Número); // - título 1, 2, 3, 4}
El mecanismo de iteración de los tipos integrados, como las cadenas, también se puede manipular:
Deja iter = ['I', 't ', 'e ', 'r ', 'a', 't ', 'o ', 'r '[ ]Signatura.iterator]();iter.siguiente().valor; //- Iiter.siguiente().valor; //- t
PHP
PHP admite el patrón de iterador a través de la interfaz Iterator, como parte de la distribución estándar. Los objetos que implementan la interfaz se pueden iterar con la construcción de lenguaje foreach
.
Ejemplo de patrones usando PHP:
Identificado?php// BookIterator.phpnamespace DesignPatterns;clase BookIterator implementos Iterator{} privado int $i_posición = 0; privado BookCollection $books Colección; público función __construir()BookCollection $books Colección) {} Esto-libros Colección = $books Colección; } público función corriente() {} retorno Esto-libros Colección-# Título()Esto-i_posición); } público función clave(): int {} retorno Esto-i_posición; } público función siguiente(): vacío {} Esto-i_posición++; } público función rebobinado(): vacío {} Esto-i_posición = 0; } público función válido(): bool {} retorno !is_null()Esto-libros Colección-# Título()Esto-i_posición)); }}
Identificado?php// BookCollection.phpnamespace DesignPatterns;clase BookCollection implementos IteratorAggregate{} privado array $a_titles = array(); público función getIterator() {} retorno nuevo BookIterator()Esto); } público función addTitle()cuerda $string): vacío {} Esto-a_titles[] = $string; } público función # Título()$key) {} si ()isset()Esto-a_titles[$key]) {} retorno Esto-a_titles[$key]; } retorno nulo; } público función is_empty(): bool {} retorno vacío()Esto-$a_titles); }}
Identificado?php// index.phpnecesidad 'vendor/autoload.php ';uso DiseñoPatternsBookCollection;$books Colección = nuevo BookCollection();$books Colección-addTitle()Patrones de diseño ');$books Colección-addTitle()'PHP7 es el mejor ');$books Colección-addTitle()Reglas de Laravel ');$books Colección-addTitle()Reglas de DHH ');en adelante ()$books Colección como $book) {} var_dump()$book);}
Salida
string(15) "Design Patterns" string(16) "PHP7 es el mejor" string(13) "Laravel Rules" cadena(9) "Reglas DHH"
Pitón
Python prescribe una sintaxis para los iteradores como parte del propio lenguaje, de modo que las palabras clave del lenguaje como for
funcionen con lo que Python llama iterables. Un iterable tiene un método __iter__()
que devuelve un objeto iterador. El "protocolo iterador" requiere que next()
devuelva el siguiente elemento o genere una excepción StopIteration
al llegar al final de la secuencia. Los iteradores también proporcionan un método __iter__()
que se devuelve a sí mismos para que también se puedan iterar; por ejemplo, usando un bucle for
. Los generadores están disponibles desde 2.2.
En Python 3, next()
pasó a llamarse __next__()
.
Raku
Raku proporciona API para iteradores, como parte del propio lenguaje, para objetos que se pueden iterar con for
y construcciones de iteración relacionadas, como la asignación a una variable Positional
. Un iterable debe implementar al menos un método iterator
que devuelva un objeto iterador. El "protocolo iterador" requiere el método pull-one
para devolver el siguiente elemento si es posible, o devolver el valor centinela IterationEnd
si no se pueden producir más valores. Las API de iteración se proporcionan al componer el rol Iterable
, Iterator
, o ambos, e implementar los métodos necesarios.
Para verificar si un objeto de tipo o una instancia de objeto es iterable, se puede usar el método does
:
Di: Array.¿Sí?()Iterable); #=conocer Cierto.Di: [1, 2, 3].¿Sí?()Iterable); #=conocer Cierto.Di: Str.¿Sí?()Iterable); #=conocer FalsoDi: "Hola".¿Sí?()Iterable); #=conocer Falso
El método does
devuelve True
si y solo si el invocador se ajusta al tipo de argumento.
Este es un ejemplo de una subrutina range
que imita la función range
de Python:
multi rango()Int:D $start, Int:D Donación Donde * $start, Int:D Paso Donde * 0 =1♪
()clase {}
también ¿Sí? Iterable ¿Sí? Iterator;
tiene Int ()$.start, $.end, $.step);
tiene Int ¡Cuenta! = ¡$!;
método iterator {} auto }
método Tira-uno(... Mu♪
si ¡Cuenta! ■ ¡Adelante! {}
# $i = ¡Cuenta!;
¡Cuenta! += ¡Paso!;
retorno $i;
}
más {}
¡Cuenta! = ¡$!;
retorno IteraciónEnd;
}
}
}).nuevo(:$start,:Donación,:Paso)
}
multi rango()Int:D $start, Int:D Donación Donde * $start, Int:D Paso Donde * 0 = 1♪
()clase {}
también ¿Sí? Iterable ¿Sí? Iterator;
tiene Int ()$.start, $.end, $.step);
tiene Int ¡Cuenta! = ¡$!;
método iterator {} auto }
método Tira-uno(... Mu♪
si ¡Cuenta!. ¡Adelante! {}
# $i = ¡Cuenta!;
¡Cuenta! += ¡Paso!;
retorno $i;
}
más {}
¡Cuenta! = ¡$!;
retorno IteraciónEnd;
}
}
}).nuevo(:$start,:Donación,:Paso)
}
# x = rango()1, 5);
.Di: para x;
# OutPUT:# 1# 2# 3# 4Di: x.mapa(* ** 2).suma;
# OutPUT:# 30# Sí. = rango(-1, -5);
.Di: para Sí.;
# OutPUT:# -1# -2# -3# -4Di: Sí..mapa(* ** 2).suma;
# OutPUT:# 30
Contenido relacionado
RAMDAC
Mente Abierta Sentido Común
Pruebas de caja negra