TypeScript设计模式:模板方法模式

模板方法模式(Template Method Pattern)是一种行为设计模式,用于定义一个操作中的算法骨架,将某些步骤延迟到子类中实现。这样可以让子类在不改变算法结构的情况下,重定义算法的特定步骤。

什么是模板方法模式?

模板方法模式的核心思想是通过抽象类定义一个方法的框架(模板方法),其中包含固定的算法步骤序列。有些步骤是固定的(由抽象类实现),而可变的部分则定义为抽象方法,由具体子类实现。这样可以实现:

  • 代码复用 :公共算法逻辑集中在基类中,避免重复。
  • 扩展性 :子类只需实现特定步骤,即可自定义行为。
  • 控制反转 :基类控制整体流程,子类提供细节(类似于好莱坞原则:"不要调用我们,我们会调用你")。

在 TypeScript 中,模板方法模式可以通过抽象类和继承来实现,利用类型系统确保子类正确实现抽象方法。

模板方法模式的结构

模板方法模式通常包含以下几个角色:

  1. AbstractClass(抽象类) :定义模板方法(算法骨架)和抽象的钩子方法或步骤。
  2. ConcreteClass(具体类) :继承抽象类,实现抽象步骤,提供具体逻辑。

优点

  • 复用性 :算法的公共部分被封装在基类中,便于维护。
  • 灵活性 :子类可以自定义特定步骤,而不影响整体结构。
  • 类型安全 :TypeScript 的抽象类确保子类必须实现所需方法。

适用场景

  • 当多个类有相似的算法结构,但部分步骤不同时(如不同格式的文件解析)。
  • 需要控制子类扩展行为,但保持核心流程不变时(如框架设计)。
  • 算法有固定顺序,但允许钩子函数自定义时(如游戏循环或构建过程)。

简单的模拟代码介绍

以下是一个简单的模拟代码示例,使用 TypeScript 实现模板方法模式。我们创建一个游戏角色准备类(CharacterPreparation),定义准备角色的模板方法,不同角色(如战士和法师)通过子类实现具体步骤,如装备武器和学习技能。

typescript 复制代码
// 抽象类:定义模板方法
abstract class CharacterPreparation {
  // 模板方法:定义算法骨架
  prepareCharacter(): void {
    this.createCharacter();
    this.equipWeapon();
    this.learnSkills();
    this.finalize();
  }

  // 固定步骤
  createCharacter(): void {
    console.log("Creating base character");
  }

  finalize(): void {
    console.log("Character preparation complete");
  }

  // 抽象步骤,由子类实现
  abstract equipWeapon(): void;
  abstract learnSkills(): void;
}

// 具体类:战士
class Warrior extends CharacterPreparation {
  equipWeapon(): void {
    console.log("Equipping sword and shield");
  }

  learnSkills(): void {
    console.log("Learning combat tactics");
  }
}

// 具体类:法师
class Mage extends CharacterPreparation {
  equipWeapon(): void {
    console.log("Equipping staff and wand");
  }

  learnSkills(): void {
    console.log("Learning magic spells");
  }
}

// 客户端代码
function main() {
  const warrior = new Warrior();
  console.log("Preparing Warrior:");
  warrior.prepareCharacter();

  console.log("\nPreparing Mage:");
  const mage = new Mage();
  mage.prepareCharacter();
}

main();

运行结果

运行以上代码,将输出:

sql 复制代码
Preparing Warrior:
Creating base character
Equipping sword and shield
Learning combat tactics
Character preparation complete

Preparing Mage:
Creating base character
Equipping staff and wand
Learning magic spells
Character preparation complete

这个模拟示例展示了模板方法的基本应用:固定步骤(如创建角色和最终化)在基类中实现,而可变步骤(如装备和技能)由子类自定义,适用于游戏开发中的角色初始化等场景。

AI知识库文件解析的实际应用示例

在 AI 知识库管理中,模板方法模式常用于解析不同格式的文件(如 Markdown、PDF 和 Word),以提取结构化内容(如标题、段落或元数据)。核心流程(如读取文件、提取内容、验证和输出)固定,但解析细节因格式而异。以下是一个 Node.js 示例,使用 TypeScript 实现一个文件解析器,支持解析 Markdown (.md)、PDF (.pdf) 和 Word (.docx) 文件,提取文本内容并生成 JSON 输出。我们假设使用外部库(如 marked for Markdown、pdf-parse for PDF 和 mammoth for Word),这些库需要在项目中安装(npm install marked pdf-parse mammoth)。

实现示例

定义统一的内容模型为一个包含标题和段落的数组。

typescript 复制代码
import * as fs from 'fs/promises';
import { marked } from 'marked'; // 用于 Markdown 解析
import pdfParse from 'pdf-parse'; // 用于 PDF 解析
import mammoth from 'mammoth'; // 用于 Word 解析

// 定义统一的内容模型
interface ContentSection {
  title: string;
  paragraph: string;
}

// 抽象类:AI 知识库文件解析器
abstract class KnowledgeFileParser {
  // 模板方法:定义文件解析的算法骨架
  async parseFile(filePath: string, outputPath: string): Promise<void> {
    const rawContent = await this.readFile(filePath);
    const sections = this.extractSections(rawContent);
    if (this.validateSections(sections)) {
      await this.outputJson(outputPath, sections);
    } else {
      throw new Error('Invalid content structure');
    }
  }

  // 固定步骤:读取文件(返回 Buffer 或 string,根据格式)
  async readFile(filePath: string): Promise<Buffer | string> {
    console.log(`Reading file: ${filePath}`);
    return await fs.readFile(filePath);
  }

  // 固定步骤:验证提取的内容
  validateSections(sections: ContentSection[]): boolean {
    console.log('Validating sections');
    return sections.length > 0 && sections.every(section => 
      typeof section.title === 'string' && typeof section.paragraph === 'string'
    );
  }

  // 固定步骤:输出为 JSON
  async outputJson(outputPath: string, sections: ContentSection[]): Promise<void> {
    console.log(`Writing JSON output to ${outputPath}`);
    const json = JSON.stringify(sections, null, 2);
    await fs.writeFile(outputPath, json);
  }

  // 抽象步骤:提取 sections(由子类实现,处理特定格式)
  abstract extractSections(rawContent: Buffer | string): ContentSection[];
}

// 具体类:Markdown 解析器
class MarkdownParser extends KnowledgeFileParser {
  async readFile(filePath: string): Promise<string> {
    return (await super.readFile(filePath)).toString('utf-8');
  }

  extractSections(rawContent: string): ContentSection[] {
    console.log('Extracting sections from Markdown');
    const tokens = marked.lexer(rawContent);
    const sections: ContentSection[] = [];
    let currentTitle = '';
    tokens.forEach(token => {
      if (token.type === 'heading') {
        currentTitle = token.text;
      } else if (token.type === 'paragraph' && currentTitle) {
        sections.push({ title: currentTitle, paragraph: token.text });
        currentTitle = ''; // 重置以避免重复
      }
    });
    return sections;
  }
}

// 具体类:PDF 解析器
class PDFParser extends KnowledgeFileParser {
  async readFile(filePath: string): Promise<Buffer> {
    return await super.readFile(filePath) as Buffer;
  }

  async extractSections(rawContent: Buffer): Promise<ContentSection[]> {
    console.log('Extracting sections from PDF');
    const data = await pdfParse(rawContent);
    const text = data.text;
    // 简单模拟提取:按行分割,假设奇数行为标题,偶数行为段落(实际可使用更复杂逻辑)
    const lines = text.split('\n').filter(line => line.trim());
    const sections: ContentSection[] = [];
    for (let i = 0; i < lines.length; i += 2) {
      sections.push({
        title: lines[i] || 'Untitled',
        paragraph: lines[i + 1] || ''
      });
    }
    return sections;
  }
}

// 具体类:Word 解析器
class WordParser extends KnowledgeFileParser {
  async readFile(filePath: string): Promise<Buffer> {
    return await super.readFile(filePath) as Buffer;
  }

  async extractSections(rawContent: Buffer): Promise<ContentSection[]> {
    console.log('Extracting sections from Word');
    const result = await mammoth.extractRawText({ buffer: rawContent });
    const text = result.value;
    // 类似 PDF,简单分割(实际可解析 HTML 输出以获取结构)
    const paragraphs = text.split('\n\n').filter(p => p.trim());
    const sections: ContentSection[] = [];
    paragraphs.forEach((p, index) => {
      sections.push({
        title: `Section ${index + 1}`,
        paragraph: p
      });
    });
    return sections;
  }
}

// 客户端代码
async function main() {
  // 假设输入文件存在
  // Markdown 示例内容: # Title\nParagraph text
  const mdParser = new MarkdownParser();
  await mdParser.parseFile('knowledge.md', 'output_md.json');

  // PDF 示例(假设一个简单 PDF)
  const pdfParser = new PDFParser();
  await pdfParser.parseFile('knowledge.pdf', 'output_pdf.json');

  // Word 示例(假设一个 .docx)
  const wordParser = new WordParser();
  await wordParser.parseFile('knowledge.docx', 'output_docx.json');
}

main().catch(console.error);

运行与测试

  1. 准备 :安装所需库(npm install marked pdf-parse mammoth @types/node),并准备测试文件。

    • knowledge.md:Markdown 文件示例。
    • knowledge.pdf:PDF 文件示例。
    • knowledge.docx:Word 文件示例。
  2. 运行 :使用 npx ts-node parser.ts

  3. 输出 :生成 JSON 文件,如 output_md.json 包含提取的 sections。

  4. 控制台输出示例

    javascript 复制代码
    Reading file: knowledge.md
    Extracting sections from Markdown
    Validating sections
    Writing JSON output to output_md.json
    // 类似 PDF 和 Word

代码说明

  • 模板方法parseFile 控制流程:读取、提取、验证、输出。
  • 固定步骤:读取、验证和输出在基类中统一处理。
  • 抽象步骤extractSections 由子类实现,针对格式自定义提取逻辑。
  • 现实场景:在 AI 知识库中,此模式可用于 ingestion 管道,从各种文档提取知识,存储为向量数据库输入,支持 RAG(Retrieval-Augmented Generation)系统。

总结

模板方法模式通过算法骨架提供复用性。从简单模拟到 AI 知识库文件解析示例,展示了该设计模式在数据提取中的实际价值,便于扩展新格式如 XML 或 HTML。

相关推荐
9523610 分钟前
SpringBoot统一功能处理
java·spring boot·后端
light blue bird16 分钟前
主子端台二分法任务汇总组件
前端·数据库·.net·桌面端winform
rleS IONS31 分钟前
SpringBoot中自定义Starter
java·spring boot·后端
DevilSeagull1 小时前
MySQL(2) 客户端工具和建库
开发语言·数据库·后端·mysql·服务
jeffwang1 小时前
我做了个让 AI 看屏幕跑测试的工具,因为 Playwright 测不了我的 Flutter Web
前端
吴声子夜歌2 小时前
Node.js——JSON-Server轻量级RESTful API
node.js·json·restful·json-server
HSunR2 小时前
dify 搭建ai作业批改流
开发语言·前端·javascript
代码不加糖2 小时前
2026 跨境电商独立站实战:从 0 到 1 搭建高转化 SaaS 商城(附源码)
开发语言·前端·javascript
TeDi TIVE2 小时前
springboot和springframework版本依赖关系
java·spring boot·后端