Iterator Pattern

Posted by Dustin Boston in .

The Iterator Pattern is a behavioral design pattern that allows sequential access to elements of a collection without exposing its internal structure.


The Iterator Pattern is a behavioral design pattern that provides a way to
access elements of a collection sequentially without exposing its underlying
representation. This pattern is particularly useful for traversing complex data
structures in a consistent and flexible manner.

Source Code Listing

code.ts

export type Iterator<T> = {
  first(): void;
  next(): void;
  isDone(): boolean;
  currentItem(): T | undefined; // Return undefined if done
};

export class ConcreteIterator<T> implements Iterator<T> {
  private current = 0; // Initialize current

  constructor(private readonly list: List<T>) {}

  first(): void {
    this.current = 0;
  }

  next(): void {
    this.current++;
  }

  isDone(): boolean {
    return this.current >= this.list.count();
  }

  currentItem(): T | undefined {
    if (this.isDone()) {
      return undefined; // Or throw an error if you prefer
    }

    return this.list.get(this.current);
  }
}

export type Aggregate<T> = {
  createIterator(items: Record<string, T>): Iterator<T>;
};

export class List<T> {
  private readonly items: T[] = [];

  append(item: T): void {
    this.items.push(item);
  }

  get(index: number): T {
    return this.items[index];
  }

  count(): number {
    return this.items.length;
  }
}

export class ConcreteAggregate<T> implements Aggregate<T> {
  createIterator(items: Record<string, T>): Iterator<T> {
    const list = new List<T>();
    for (const key in items) {
      if (Object.hasOwn(items, key)) {
        // Important: Check for own properties
        const value = items[key];
        list.append(value);
      }
    }

    return new ConcreteIterator<T>(list);
  }
}

// Example usage:
//
// const client = {
//   run<T>(items: Record<string, T>): T[] {
//     const aggregate = new ConcreteAggregate<T>();
//     const iterator = aggregate.createIterator(items);

//     const result: T[] = [];
//     while (!iterator.isDone()) {
//       const current = iterator.currentItem();
//       if (current !== undefined) {
//         // Check for undefined before using current
//         // Do something with the item here...
//         result.push(current);
//       }

//       iterator.next(); // Move to the next item *after* processing the current one
//     }

//     return result;
//   },
// };

// // Example usage:
// const items = {a: 1, b: 2, c: 3}; // Example items
// const result = client.run(items);
// console.log(result); // Output:

// const items2 = {x: "hello", y: "world"};
// const result2 = client.run(items2);
// console.log(result2); // Output: ["hello", "world"]