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
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Iterator Pattern para Traversal de Colecciones Custom en TypeScript
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
FAQ
P: En que se diferencia de un simple loop for?
R: Iterator separa traversal de la coleccion, permitiendo multiples algoritmos y ocultando estructura interna. Un loop for expone indices y detalles de array.
P: Puedo usar esto con iterators nativos de JavaScript?
R: Si. Implementa [Symbol.iterator] y usa generators para integrar con for...of, spread syntax y destructuring.
P: Cuando deberia usar async iterators? R: Para queries paginadas de base de datos, lecturas de archivos en streaming, o cualquier coleccion donde elementos llegan asincronicamente.
¿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.
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. Recursos Relacionados
Composite Pattern for UI Component Trees in React
Use the Composite pattern to compose objects into tree structures, letting clients treat individual objects and compositions uniformly in UI component hierarchies
PatternAbstract Factory for Cross-Platform UI Component Families
Create families of related objects without specifying concrete classes, enabling platform-specific implementations that share a common interface
PatternInterpreter Pattern for Domain-Specific Expression Languages
Build a language interpreter that evaluates expressions and rules by representing grammar as composable objects, useful for formulas, queries, and business rules
PatternVisitor Pattern for Extensible Operations on Object
Separate algorithms from the objects they operate on, allowing new operations to be added without modifying existing element classes