模板方法模式(Template Method Pattern)是一种行为设计模式,用于定义一个操作中的算法骨架,将某些步骤延迟到子类中实现。这样可以让子类在不改变算法结构的情况下,重定义算法的特定步骤。
什么是模板方法模式?
模板方法模式的核心思想是通过抽象类定义一个方法的框架(模板方法),其中包含固定的算法步骤序列。有些步骤是固定的(由抽象类实现),而可变的部分则定义为抽象方法,由具体子类实现。这样可以实现:
- 代码复用 :公共算法逻辑集中在基类中,避免重复。
- 扩展性 :子类只需实现特定步骤,即可自定义行为。
- 控制反转 :基类控制整体流程,子类提供细节(类似于好莱坞原则:"不要调用我们,我们会调用你")。
在 TypeScript 中,模板方法模式可以通过抽象类和继承来实现,利用类型系统确保子类正确实现抽象方法。
模板方法模式的结构
模板方法模式通常包含以下几个角色:
- AbstractClass(抽象类) :定义模板方法(算法骨架)和抽象的钩子方法或步骤。
- 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);
运行与测试
-
准备 :安装所需库(
npm install marked pdf-parse mammoth @types/node
),并准备测试文件。knowledge.md
:Markdown 文件示例。knowledge.pdf
:PDF 文件示例。knowledge.docx
:Word 文件示例。
-
运行 :使用
npx ts-node parser.ts
。 -
输出 :生成 JSON 文件,如
output_md.json
包含提取的 sections。 -
控制台输出示例 :
javascriptReading 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。