如何在 Python 中用 Strategy Pattern 重构多重 if-elif?
可以用“字典注册表 + 独立解析器类”实现 Strategy Pattern:字典将格式名称映射到解析器类,DataProcessor 调用传入的解析器。新增格式时,添加解析器并更新注册表即可,无须修改 DataProcessor。
ABC 定义解析器必须实现的接口,字典负责选择具体实现,两者可以一起使用。
完整实现
示例沿用原代码返回提示字符串的行为,用来展示代码结构,不会真正解析 JSON、XML 或 CSV。原代码缺少必要的缩进,这里的缩进已调整为合法的 Python 缩进。
from abc import ABC, abstractmethod
class Parser(ABC):
@abstractmethod
def parse(self, data: str) -> str:
pass
class JSONParser(Parser):
def parse(self, data: str) -> str:
return f"Parsing JSON: {data}"
class XMLParser(Parser):
def parse(self, data: str) -> str:
return f"Parsing XML: {data}"
class CSVParser(Parser):
def parse(self, data: str) -> str:
return f"Parsing CSV: {data}"
PARSER_CLASSES = {
"json": JSONParser,
"xml": XMLParser,
"csv": CSVParser,
}
def create_parser(format_type: str) -> Parser:
try:
parser_class = PARSER_CLASSES[format_type]
except KeyError:
raise ValueError(
f"Unsupported format: {format_type}"
) from None
return parser_class()
class DataProcessor:
def __init__(self, parser: Parser):
self._parser = parser
def process(self, data: str) -> str:
return self._parser.parse(data)
processor = DataProcessor(create_parser("json"))
print(processor.process("{'key': 'value'}"))
输出为:
Parsing JSON: {'key': 'value'}
create_parser() 中的异常处理只包围字典查找,因此解析器构造函数内部抛出的 KeyError 不会被误报为“不支持的格式”。
为什么工厂中不再需要 if-elif?
原来的分支同时承担格式选择和解析工作。重构后,各个类负责具体解析逻辑,注册表负责格式选择:
"json" -> JSONParser
"xml" -> XMLParser
"csv" -> CSVParser
工厂查表并创建对象。DataProcessor 接收已经选好的策略,无须知道格式名称、注册表或具体解析器类。
调用方式也从原来的 process(format_type, data) 改为创建时选择格式、处理时传入数据。同一个 processor 可以继续处理多份使用相同解析策略的数据。
新增格式时修改哪里?
例如,需要增加一种自定义的逐行文本格式,可以新增解析器并注册:
class LinesParser(Parser):
def parse(self, data: str) -> str:
return f"Parsing lines: {data}"
PARSER_CLASSES["lines"] = LinesParser
processor = DataProcessor(create_parser("lines"))
print(processor.process("first\nsecond"))
DataProcessor 和 create_parser() 都不需要修改。注册代码应在使用该格式之前执行,通常可以集中放在应用初始化阶段。
支持新格式仍需要声明名称与解析器的对应关系。把配置集中在注册表中,可以避免每增加一种格式就修改处理类。
字典和 ABC 是否符合 Python 的惯用写法?
字典保存类或函数都是常见的 Python 写法,适合按名称选择行为的场景。Strategy Pattern 并不要求使用特定的工厂结构。
ABC 也不是必需的。只要传入的对象提供兼容的 parse() 方法,DataProcessor 就可以调用它,这属于鸭子类型。ABC 的好处是明确表达接口,并阻止没有实现抽象方法的子类被实例化。
不过,ABC 不会完整检查方法签名,也不会保证返回值类型正确;示例中的类型注解本身也不会执行运行时类型校验。如果项目规模较小,策略只是几个简单函数,也可以直接建立函数注册表,无须为了使用设计模式而增加类。
实际解析时需要注意什么?
- 示例假设所有解析器都能无参数实例化。如果不同解析器需要不同配置,可以在注册表中保存工厂函数,由函数完成创建。
- 注册表保存的是类,每次调用
create_parser()都会创建新实例。若改为保存实例,需要考虑解析器是否携带可变状态,以及是否适合共享。 - 当前示例只返回字符串。换成真实解析逻辑后,需要重新约定返回类型;JSON、XML 和 CSV 的解析结果未必具有相同结构。
- 原示例中的
"{'key': 'value'}"是一个 Python 字符串,其内容不是合法 JSON。若接入真正的 JSON 解析,应使用如' {"key": "value"} '这样的内容,或去掉两端空格写成'{"key": "value"}'。
备注:内容仅供参考。
