Iterator Pattern para Traversal de Colecciones Custom en
Provee una forma de acceder a elementos de un objeto agregado secuencialmente sin exponer su representacion subyacente usando el Iterator pattern
El Iterator pattern provee una forma de acceder a elementos de un objeto agregado secuencialmente sin exponer su representacion subyacente. Separa el algoritmo de traversal de la estructura de coleccion, permitiendo iterar sobre arrays, arboles, grafos o streams con la misma interfaz.
Cuando Usar Esto
- Necesitas recorrer una coleccion sin exponer su estructura interna
- Se requieren multiples algoritmos de traversal (pre-order, post-order, level-order) para la misma coleccion
- Quieres iteracion uniforme a traves de diferentes tipos de coleccion
Problema
Una estructura de arbol requiere diferentes ordenes de traversal para diferentes casos de uso, pero cada traversal esta fuertemente acoplado a la implementacion del nodo del arbol.
Solucion
// iterator/Iterator.ts
interface Iterator<T> {
next(): T | null;
hasNext(): boolean;
reset(): void;
}
interface IterableCollection<T> {
createIterator(): Iterator<T>;
}
// Tree Node
class TreeNode<T> {
children: TreeNode<T>[] = [];
constructor(public value: T) {}
addChild(child: TreeNode<T>): void {
this.children.push(child);
}
}
// Depth-First Iterator (pre-order)
class PreOrderIterator<T> implements Iterator<T> {
private stack: TreeNode<T>[] = [];
constructor(root: TreeNode<T>) {
this.stack.push(root);
}
next(): T | null {
if (!this.hasNext()) return null;
const node = this.stack.pop()!;
// Push hijos en orden inverso para traversal de izquierda a derecha
for (let i = node.children.length - 1; i >= 0; i--) {
this.stack.push(node.children[i]);
}
return node.value;
}
hasNext(): boolean {
return this.stack.length > 0;
}
reset(): void {
this.stack = [];
}
}
// Breadth-First Iterator
class LevelOrderIterator<T> implements Iterator<T> {
private queue: TreeNode<T>[] = [];
constructor(root: TreeNode<T>) {
this.queue.push(root);
}
next(): T | null {
if (!this.hasNext()) return null;
const node = this.queue.shift()!;
this.queue.push(...node.children);
return node.value;
}
hasNext(): boolean {
return this.queue.length > 0;
}
reset(): void {
this.queue = [];
}
}
// Sistema de archivos con iterator
class FileSystem implements IterableCollection<string> {
private root = new TreeNode<string>('root');
addNode(parentPath: string, name: string): void {
const parent = this.findNode(parentPath);
if (parent) {
parent.addChild(new TreeNode<string>(name));
}
}
private findNode(path: string): TreeNode<string> | null {
// Busqueda por path simplificada
return this.root;
}
createIterator(type: 'pre-order' | 'level-order' = 'pre-order'): Iterator<string> {
if (type === 'level-order') {
return new LevelOrderIterator(this.root);
}
return new PreOrderIterator(this.root);
}
}
// Uso
const fs = new FileSystem();
fs.addNode('root', 'src');
fs.addNode('root', 'dist');
const preOrder = fs.createIterator('pre-order');
console.log('Pre-order:');
while (preOrder.hasNext()) {
console.log(preOrder.next());
}
const levelOrder = fs.createIterator('level-order');
console.log('Level-order:');
while (levelOrder.hasNext()) {
console.log(levelOrder.next());
}
Variacion: Async Iterator para Streams
// iterator/AsyncStreamIterator.ts
interface AsyncIterator<T> {
next(): Promise<T | null>;
hasNext(): boolean;
}
class DatabaseQueryIterator implements AsyncIterator<Record<string, unknown>> {
private currentPage: Record<string, unknown>[] = [];
private pageIndex = 0;
private offset = 0;
private hasMore = true;
constructor(
private query: string,
private pageSize: number = 100,
private db: { query: (sql: string, params: unknown[]) => Promise<Record<string, unknown>[]> }
) {}
async next(): Promise<Record<string, unknown> | null> {
if (this.pageIndex >= this.currentPage.length) {
if (!this.hasMore) return null;
await this.loadNextPage();
}
if (this.pageIndex >= this.currentPage.length) return null;
return this.currentPage[this.pageIndex++];
}
hasNext(): boolean {
return this.hasMore || this.pageIndex < this.currentPage.length;
}
private async loadNextPage(): Promise<void> {
this.currentPage = await this.db.query(
`${this.query} LIMIT ${this.pageSize} OFFSET ${this.offset}`,
[]
);
this.offset += this.pageSize;
this.pageIndex = 0;
this.hasMore = this.currentPage.length === this.pageSize;
}
}
Como Funciona
- Iterator declara la interfaz para traversal con
next(),hasNext()yreset() - Concrete Iterator implementa logica de traversal para una estructura de coleccion especifica
- Aggregate declara el factory method para crear iterators
- Concrete Aggregate retorna una nueva instancia de iterator configurada para su estructura
Consideraciones de Produccion
- Implementa
Symbol.iteratorpara soporte nativo de loopsfor...ofen TypeScript - Usa generators (
function*) para implementacion concisa de iterators - Considera async iterators para APIs paginadas y datos de streaming
Errores Comunes
- Exponer el indice de coleccion interna, permitiendo a clientes modificarlo
- No manejar modificacion concurrente durante iteracion
- Implementar solo un traversal cuando multiples son necesarios
Troubleshooting
- Pattern does not fit the problem: re-evaluate the forces (performance, scalability, team size, coupling). A pattern is only appropriate when its trade-offs match your constraints.
- Too many abstractions: if adding a pattern increases complexity without a clear benefit, simplify. Not every module needs a factory, decorator, or strategy.
- Tight coupling after refactoring: check that interfaces are stable and dependencies point inward.
- Tests break when the design changes: favor stable contracts over internal structure.
- Performance regression from indirection: measure before and after. Layers, decorators, and adapters can add latency; cache or inline hot paths if needed.
Temas Avanzados
Escenario: Iterator para Colecciones Personalizadas
// Iterator para LinkedList personalizada
class LinkedListNode<T> {
constructor(public value: T, public next: LinkedListNode<T> | null = null) {}
}
class LinkedList<T> implements Iterable<T> {
private head: LinkedListNode<T> | null = null;
private tail: LinkedListNode<T> | null = null;
private size = 0;
append(value: T) {
const node = new LinkedListNode(value);
if (!this.head) { this.head = node; this.tail = node; }
else { this.tail!.next = node; this.tail = node; }
this.size++;
}
// Implementar Symbol.iterator para soportar for...of
[Symbol.iterator](): Iterator<T> {
let current = this.head;
return {
next(): IteratorResult<T> {
if (!current) return { done: true, value: undefined };
const value = current.value;
current = current.next;
return { done: false, value };
}
};
}
// Iterator reverso usando generator
*reverseIterator(): Generator<T> {
const values: T[] = [];
let current = this.head;
while (current) { values.unshift(current.value); current = current.next; }
for (const v of values) yield v;
}
// Iterator con filtro
*filterIterator(pred: (v: T) => boolean): Generator<T> {
let current = this.head;
while (current) {
if (pred(current.value)) yield current.value;
current = current.next;
}
}
}
// Uso
const list = new LinkedList<number>();
list.append(1); list.append(2); list.append(3); list.append(4); list.append(5);
// for...of funciona gracias a Symbol.iterator
for (const v of list) console.log(v); // 1, 2, 3, 4, 5
// Iterator reverso
for (const v of list.reverseIterator()) console.log(v); // 5, 4, 3, 2, 1
// Iterator con filtro
for (const v of list.filterIterator(x => x % 2 === 0)) console.log(v); // 2, 4
// Spread operator tambien funciona
const arr = [...list]; // [1, 2, 3, 4, 5]
Lecciones:
- Symbol.iterator hace cualquier iterable compatible con for…of, spread, destructuring
- Generators (function*) son la forma mas concisa de implementar iterators
- Iterators custom: filter, reverse, map, slice sin crear arrays intermedios
- Lazy evaluation: los generators solo computan cuando se consume
- En JS, Map, Set, String, Array, NodeList son iterables por defecto
### Como hago mi clase iterable con for...of?
Implementa [Symbol.iterator]() que retorna un objeto con next(). next() devuelve { done: boolean, value: T }. Cuando done es true, el iteracion termina. Alternativamente, usa un generator: *[Symbol.iterator]() { let current = this.head; while (current) { yield current.value; current = current.next; } }. El generator maneja el estado automaticamente: mas conciso y menos error-prone. Preguntas frecuentes
¿Es este patrón adecuado para proyectos pequeños?
Para proyectos pequeños con pocos componentes, este patrón puede añadir complejidad innecesaria. Empieza simple e introduce el patrón cuando sientas el problema que resuelve.
¿Cómo se compara este patrón con alternativas?
Cada patrón hace diferentes trade-offs. Revisa la tabla de variantes arriba y considera tus restricciones específicas: tamaño del equipo, requisitos de rendimiento y planes de escalado.
¿Puedo aplicar este patrón parcialmente?
Sí. Muchos equipos adoptan patrones incrementalmente. Empieza con la idea central y añade sofisticación según sea necesario. El patrón es una guía, no un blueprint estricto.
Recursos Relacionados
Composite Pattern para Arboles de Componentes UI en React
Usa el Composite pattern para componer objetos en estructuras de arbol, permitiendo que clientes traten objetos individuales y composiciones uniformemente en jerarquias de componentes
PatternAbstract Factory para Familias de Componentes UI
Crea familias de objetos relacionados sin especificar clases concretas, habilitando implementaciones especificas de plataforma que comparten una interfaz comun
PatternInterpreter Pattern para Lenguajes de Expresion
Construye un interprete de lenguaje que evalua expresiones y reglas representando la gramatica como objetos componibles, util para formulas, queries y reglas de negocio
PatternVisitor Pattern para Operaciones Extensibles sobre
Separa algoritmos de los objetos sobre los que operan, permitiendo agregar nuevas operaciones sin modificar clases de elementos existentes
PatternMemento Pattern para Snapshot y Restauracion de Estado
Captura y externaliza el estado interno de un objeto sin violar encapsulacion, habilitando undo, serializacion y rollback de estado en aplicaciones