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。

相关推荐
Cache技术分享2 小时前
191. Java 异常 - 捕获与处理异常
前端·后端
努力的小郑2 小时前
从一次分表实践谈起:我们真的需要复杂的分布式ID吗?
分布式·后端·面试
GeekAGI2 小时前
如何重复执行 curl 请求:8种实用方法详解
后端
LH_R2 小时前
OneTerm开源堡垒机实战(三):功能扩展与效率提升
运维·后端·安全
小桥风满袖2 小时前
极简三分钟ES6 - ES9中对象扩展
前端·javascript
云舟吖2 小时前
基于 electron-vite 实现一个 RPA 网页自动化工具
前端·架构
用户9481817675443 小时前
超越NAT:如何构建高效、安全的内网穿透隧道
前端
明天的明3 小时前
vue双向数据绑定失效
前端
bug_kada3 小时前
前端路由:深入理解History模式
前端·面试