TypeScript设计模式:迭代器模式

迭代器模式(Iterator Pattern)是一种行为设计模式,用于提供一种方式来顺序访问聚合对象(如数组、列表或集合)的元素,而无需暴露其底层实现。

设计模式原理

什么是迭代器模式?

迭代器模式的核心思想是将集合的遍历逻辑从集合本身分离出来,交给一个独立的迭代器对象。这样可以实现:

  • 单一职责:集合负责存储数据,迭代器负责遍历逻辑。
  • 灵活性:支持多种遍历方式(如正序、倒序、过滤等)。
  • 封装性:隐藏集合的内部实现,客户端只需关注如何使用迭代器。

在 TypeScript 中,迭代器模式可以通过内置的 IterableIterator 接口实现,也可以通过自定义方式实现更复杂的逻辑。

迭代器模式的结构

迭代器模式通常包含以下几个角色:

  1. Iterator(迭代器接口) :定义遍历元素所需的方法,如 next()hasNext()
  2. ConcreteIterator(具体迭代器) :实现迭代器接口,负责具体的遍历逻辑。
  3. Aggregate(聚合接口) :定义创建迭代器的方法,如 createIterator()
  4. ConcreteAggregate(具体聚合) :实现聚合接口,存储数据并提供迭代器。

优点

  • 解耦合:将遍历逻辑与集合分离,降低耦合度。
  • 多态性:支持多种遍历方式,只需实现不同的迭代器。
  • 类型安全:TypeScript 的类型系统确保迭代器和集合的类型一致。

适用场景

  • 需要遍历复杂数据结构(如树、图)时。
  • 需要支持多种遍历方式(正序、倒序、过滤等)。
  • 希望隐藏集合的内部实现细节。

TypeScript 实现示例

以下是一个使用 TypeScript 实现迭代器模式的示例。我们将创建一个书架(Bookshelf)类,用于存储书籍,并通过迭代器遍历书籍列表。

typescript 复制代码
// 迭代器接口
interface Iterator<T> {
  hasNext(): boolean;
  next(): T | undefined;
}

// 聚合接口
interface Aggregate {
  createIterator(): Iterator<Book>;
}

// 书籍类
class Book {
  constructor(private title: string) {}

  getTitle(): string {
    return this.title;
  }
}

// 具体聚合类:书架
class Bookshelf implements Aggregate {
  private books: Book[] = [];

  addBook(book: Book): void {
    this.books.push(book);
  }

  getBooks(): Book[] {
    return this.books;
  }

  createIterator(): Iterator<Book> {
    return new BookshelfIterator(this);
  }
}

// 具体迭代器类
class BookshelfIterator implements Iterator<Book> {
  private bookshelf: Bookshelf;
  private index: number = 0;

  constructor(bookshelf: Bookshelf) {
    this.bookshelf = bookshelf;
  }

  hasNext(): boolean {
    return this.index < this.bookshelf.getBooks().length;
  }

  next(): Book | undefined {
    if (this.hasNext()) {
      return this.bookshelf.getBooks()[this.index++];
    }
    return undefined;
  }
}

// 客户端代码
function main() {
  // 创建书架
  const bookshelf = new Bookshelf();
  bookshelf.addBook(new Book("设计模式"));
  bookshelf.addBook(new Book("TypeScript 高级编程"));
  bookshelf.addBook(new Book("深入理解 ES6"));

  // 获取迭代器
  const iterator = bookshelf.createIterator();

  // 遍历书籍
  while (iterator.hasNext()) {
    const book = iterator.next();
    if (book) {
      console.log(`书籍标题: ${book.getTitle()}`);
    }
  }
}

main();

运行结果

运行以上代码,将输出:

makefile 复制代码
书籍标题: 设计模式
书籍标题: TypeScript 高级编程
书籍标题: 深入理解 ES6

使用 TypeScript 的内置迭代器

TypeScript 提供了内置的 IterableIterator 接口,可以简化迭代器模式的实现。例如,Bookshelf 类可以直接实现 Iterable<Book> 接口:

typescript 复制代码
class Bookshelf implements Iterable<Book> {
  private books: Book[] = [];

  addBook(book: Book): void {
    this.books.push(book);
  }

  [Symbol.iterator](): Iterator<Book> {
    let index = 0;
    const books = this.books;

    return {
      next(): IteratorResult<Book> {
        if (index < books.length) {
          return { value: books[index++], done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
}

// 客户端代码
function main() {
  const bookshelf = new Bookshelf();
  bookshelf.addBook(new Book("设计模式"));
  bookshelf.addBook(new Book("TypeScript 高级编程"));

  // 使用 for...of 遍历
  for (const book of bookshelf) {
    console.log(`书籍标题: ${book.getTitle()}`);
  }
}

main();

运行结果

makefile 复制代码
书籍标题: 设计模式
书籍标题: TypeScript 高级编程

JavaScript 内部迭代器原理

JavaScript 的迭代器协议(Iteration Protocol)是 ECMAScript 2015 (ES6) 引入的核心机制,用于定义对象如何被迭代遍历。 它包括同步迭代(Iterable 和 Iterator)和异步迭代(AsyncIterable 和 AsyncIterator),允许开发者自定义遍历行为。

同步迭代协议

Iterable 接口

任何对象如果实现了 Symbol.iterator 方法,则被视为可迭代对象。该方法返回一个符合 Iterator 协议的对象。当引擎遇到需要迭代的对象时(如 for...of),它会调用 [Symbol.iterator]() 获取迭代器。

Iterator 接口

迭代器对象必须实现 next() 方法,返回 { value: any, done: boolean }done 为 true 表示迭代结束。迭代器维护内部状态,每次 next() 调用推进状态。

语言集成

  • for...of :调用 [Symbol.iterator](),循环 next(),如果提前退出,调用 return() 清理资源。
  • 其他:Spread 操作符、Array.from()、解构赋值。
  • 内置对象:Array、Map、Set、String 等实现该协议.

生成器(function*yield)是迭代器的语法糖,返回 Generator 对象,既是 iterable 又是 iterator。

异步迭代协议

AsyncIterable 和 AsyncIterator

  • Symbol.asyncIterator:返回 AsyncIterator。
  • next():返回 Promise<IteratorResult>
  • for-await-of :异步版本的 for...of.

这适用于处理 Promise 或异步序列,如网络响应。

使用 Node.js 实现一个类似 Deno.serve 的方法

在 Web 服务器中,异步迭代器特别适合处理异步网络请求,例如逐个处理 incoming requests 或以流式方式发送响应数据,避免一次性加载整个数据到内存。基于对 Deno.serve 的分析(其利用 Deno.ListenerDeno.HttpConn 的 AsyncIterable 特性迭代连接和请求),我们实现一个 Node.js 版本的 serve 函数,类似于 Deno.serve,支持异步处理函数,并通过异步迭代器处理请求流和响应流。

实现示例

以下是一个精简的 Node.js HTTP 服务器实现,使用 TypeScript 和 async * 语法糖,隐藏显式 AsyncIterator 概念,清晰展示请求流到响应流的转换。

typescript 复制代码
import { createServer, IncomingMessage, ServerResponse } from 'http';
import { Readable } from 'stream';

// 自定义响应类型(类似于 Deno 的 Response)
type Handler = (req: IncomingMessage) => Promise<ResponseLike>;

interface ResponseLike {
  status?: number;
  headers?: Record<string, string>;
  body: string | Buffer | Readable | AsyncIterable<Buffer | string>;
}

// 将 request 事件转为 AsyncIterable
async function* listen(server: ReturnType<typeof createServer>) {
  const q: [IncomingMessage, ServerResponse][] = [];
  server.on('request', (req, res) => q.push([req, res]));

  while (true) {
    while (q.length) yield q.shift()!; // 先消化积压
    await new Promise(r => server.once('request', r)); // 再等下一个
  }
}

// 自定义 serve 函数,使用异步迭代处理请求
export async function serve(handler: Handler, port = 3000) {
  const server = createServer(); // 不挂任何回调,完全由我们控制
  server.listen(port, () => console.log(`http://localhost:${port}`));

  for await (const [req, res] of listen(server)) {
    try {
      const { status = 200, headers = {}, body } = await handler(req);

      res.writeHead(status, headers);
      if (Symbol.asyncIterator in body) {
        Readable.from(body as AsyncIterable<any>).pipe(res); // 流式输出
      } else {
        res.end(body); // 一次性输出
      }
    } catch {
      res.writeHead(500).end('Internal Server Error');
    }
  }
}

// 示例使用:异步 handler 处理请求
if (require.main === module) {
  serve(async req => {
    const { pathname } = new URL(req.url!, `http://${req.headers.host}`);

    if (pathname === '/stream') {
      async function* counter() {
        for (let i = 0; i < 5; i++) {
          await new Promise(r => setTimeout(r, 1000));
          yield `chunk ${i}\n`;
        }
      }
      return { body: counter(), headers: { 'content-type': 'text/plain' } };
    }

    return { body: 'Hello World' };
  });
}

运行与测试

  • 运行:npx tsx server.ts(或使用 ts-node)。

  • 访问:http://localhost:3000/stream,浏览器或 curl 将逐秒接收 "chunk" 数据。

  • 输出示例:

    bash 复制代码
    http://localhost:3000
    [访问 /stream,每秒输出一行:]
    chunk 0
    chunk 1
    chunk 2
    chunk 3
    chunk 4

总结

迭代器模式通过分离遍历逻辑,提供灵活的集合访问方式。JavaScript 的迭代器协议为其提供了底层支持,而异步迭代器进一步扩展到异步场景,如 Node.js 的自定义 serve 示例。通过 async *Readable.from,请求流和响应流清晰转换,实现了类似 Deno.serve 的简洁 API。这展示了迭代器模式在现代 Web 开发中的强大应用。

相关推荐
2601_962055973 小时前
跟据spring boot版本,查看对应的tomcat,并查看可支持的tomcat的版本范围
spring boot·后端·tomcat
海兰3 小时前
【应用】基于 Next.js 16 + Python mplfinance的金融K线图与技术指标可视化平台(三)
javascript·python·金融
Lost of 程序猿4 小时前
ASP.NET Core API 幂等性设计深度实战:从一次重复申领事故说起
后端·asp.net
小磊哥er4 小时前
深入解构Claude Code - 第 7 篇 · 连接外面的世界
javascript·ai编程
小磊哥er5 小时前
深入解构Claude Code - 第 6 篇 · 终端里的图形界面
javascript·ai编程
海兰5 小时前
【应用】基于 Next.js 16 + Python mplfinance的金融K线图与技术指标可视化平台(一)
javascript·python·金融
QQ_21696290965 小时前
【源码编号:project79475】SpringBoot校内二手交易平台:商品发布、分类检索、留言交流、订单管理全流程实战
java·spring boot·后端
可乐鸡翅yeah_6 小时前
hls.js 内存泄漏实战排查,直播 M3U8 长时间播放异常定位方案
开发语言·javascript·python·django·ecmascript·m3u8·m3u8在线
可乐鸡翅yeah_6 小时前
第三方接口对接 M3U8 流媒体排坑实战,解决外部服务商流兼容难题
前端·javascript·python·django·html·m3u8·m3u8在线
郑州光合科技余经理6 小时前
本地生活服务系统:模块边界与结算字段怎么拆
java·开发语言·前端·后端·系统架构·uni-app·php