工厂模式 | 文档解析器工厂
import osfrom abc import ABC, abstractmethod"""用来定义 “规矩 / 模板” 的工具ABC = Abstract Base Class翻译:抽象基类人话:一个只定规矩、不干活的类@abstractmethod = 抽象方法人话:这是一个必须实现的方法,谁继承我,谁就必须写这个方法!不写 → 直接报错,不让创建对象!强制统一结构"""class DocumentParser(ABC): #ABC:让类变成抽象类(规矩类)"""文档解析器基类"""@abstractmethoddef parse(self, filepath):passclass PDFParser(DocumentParser):def parse(self, filepath):return {'content':f'这是从{filepath}提取的PDF内容。包含10页文本。','metadata': {"type": "pdf","pages": 10,"file_size": "2.5MB"}}class DOCXParser(DocumentParser):def parse(self, filepath):return {"content": f"这是从{filepath}提取的Word文档内容。","metadata": {"type": "docx","paragraphs": 50,"file_size": "1.2MB"}}class TXTParser(DocumentParser):def parse(self, filepath):try:with open(filepath, 'r', encoding='utf-8')as f:content = f.read()return {"content": content,"metadata": {"type": "txt","lines": len(content.split('\n')),"file_size": f"{len(content)} bytes"}}except FileNotFoundError:return{"content": f"模拟TXT内容来自{filepath}","metadata": {"type": "txt"}}class MarkdownParser(DocumentParser):def parse(self, filepath: str) -> dict:return {"content": f"这是从{filepath}解析的Markdown内容。","metadata": {"type": "markdown","headings": 5}}class ParserFactory:"""解析器工厂"""# 注册表模式(可扩展)_parsers = {'.pdf': PDFParser,'.docx': DOCXParser,'.doc': DOCXParser,'.txt': TXTParser,'.md': MarkdownParser,'.markdown': MarkdownParser,}# @staticmethod@classmethoddef create_parser(cls, filepath: str) -> DocumentParser:"""根据文件扩展名返回对应的解析器"""ext = os.path.splitext(filepath)[1].lower()"""splitext = split extension → 按 “后缀名” 拆分路径比如:filepath = "test.pdf"os.path.splitext(filepath)结果是一个元组:("test", ".pdf") 第 0 位:文件名前面部分 "test",第 1 位:后缀名(带点) .pdf也可以这样:_, ext = os.path.splitext(filepath)ext = ext.lower()"""parser_class = cls._parsers.get(ext)if parser_class is None:raise ValueError(f"不支持的文件格式: {ext}")return parser_class()@classmethoddef register_parser(cls, extension, parser_class):"""注册新的解析器(扩展点)"""# if not extension.startwith('.'):# extension = '.' + extensioncls._parsers[extension.lower()] = parser_class@classmethoddef get_supported_formats(cls) -> list:return list(cls._parsers.keys())# 测试# 测试PDFparser = ParserFactory.create_parser("document.pdf")assert isinstance(parser, PDFParser)result = parser.parse("test.pdf")print(f"PDF解析: {result['metadata']}")# 测试DOCXparser = ParserFactory.create_parser("document.docx")assert isinstance(parser, DOCXParser)result = parser.parse("test.docx")print(f"DOCX解析: {result['metadata']}")# 测试TXTparser = ParserFactory.create_parser("document.txt")assert isinstance(parser, TXTParser)result = parser.parse("test.txt")print(f"TXT解析: {result['metadata']}")# 测试注册新解析器class CSVParser(DocumentParser):def parse(self, filepath: str) -> dict:return {"content": f"CSV数据来自{filepath}","metadata": {"type": "csv", "rows": 100}}ParserFactory.register_parser('.csv', CSVParser)parser = ParserFactory.create_parser("data.csv")assert isinstance(parser, CSVParser)# 测试不支持的格式try:ParserFactory.create_parser("file.xyz")assert False, "应该抛出异常"except ValueError as e:print(f"✅ 正确处理不支持格式: {e}")print(f"支持的格式: {ParserFactory.get_supported_formats()}")print("✅ 工厂模式测试通过")
一、场景
根据不同文件后缀,自动创建对应的解析器,不用写一堆 if-elif。
1. 第一段:抽象类(定规矩)
class DocumentParser(ABC):@abstractmethoddef parse(self, filepath):pass
作用:强制 PDFParser、DOCXParser 都必须写 parse(),不写就报错,保证统一接口。
2. 第二段:各种解析器(遵守规矩)
它们都继承了 DocumentParser;
它们都老老实实实现了 parse ();
它们接口完全统一。
3. 第三段:工厂(自动造对象)—— 最重要
classParserFactory:_parsers = {'.pdf': PDFParser,'.docx': DOCXParser,'.txt': TXTParser,}@staticmethoddef create_parser(filepath):ext = os.path.splitext(filepath)[1].lower() # 拿后缀parser_class = ParserFactory._parsers.get(ext)return parser_class()
你给我一个文件名,我自动判断后缀,返回对应解析器。就不用写下面这些了:
if 后缀 == pdf: ...elif 后缀 == docx: ...elif ...
二、个人理解/最初卡在哪里
1、在抽象方法里加代码
@abstractmethoddef parse(self, filepath):self.filepath = filepath # 👈 这么写错误
抽象方法只是定规矩,不应该包含任何实际功能代码,上面加了一句实际代码,就破坏了抽象类的意义。
@abstractmethod 的意义:只定义接口,不实现功能,写 self.filepath = filepath 是实现代码,不对。
有的子类可能需要 filepath,有的子类根本不需要,在父类强制存 filepath,会让所有子类被迫接受,非常不灵活。
正确写法:
@abstractmethoddef parse(self, filepath):pass # 只有 pass,什么都不写
所以 self.filepath = filepath应该写在子类里。
2、在用@classmethod 地方使用了 @staticmethod
@classmethod(类方法) = 跟【类】绑定,能访问类自己的东西,第一个参数固定是 cls,代表当前类,能访问类变量:cls._parsers,继承后是否自动适配子类
@staticmethod(静态方法) = 跟谁都不绑定,就是个普通工具函数,没有固定参数,不能自动访问类,纯工具,继承不适配,永远用原类,想访问类变量必须写死:ParserFactory._parsers
代码对比:
classParserFactory:_parsers = {} # 类变量# ✅ 类方法:自带 cls,能访问 _parsers@classmethoddef create_parser(cls, filepath):return cls._parsers # 正常访问# ✅ 静态方法:没有 cls,想访问必须写死类名@staticmethoddef create_parser2(filepath):return ParserFactory._parsers # 写死类名
在本程序中的好处:
不用写死类名 ParserFactory
以后子类继承,自动用子类的 _parsers
代码规范、干净、可扩展
三、关键洞察
抽象方法里写 pass,不能写任何实际代码,实际代码必须写在子类里。
夜雨聆风