应使用类继承和方法重写替代硬编码if/else,通过抽象基类定义统一接口、子类实现具体逻辑、注册表映射类型标识,实现开闭原则、类型安全与可测试性。

用类继承和方法重写替代硬编码的 if else
当你的代码里出现一长串 if/elif/elif/else 判断同一变量的不同取值并执行不同逻辑时,多态不是“更优雅的写法”,而是避免漏分支、难测试、不敢改的刚需。核心思路是:把每个分支逻辑封装成一个类,让调用方只管调用统一接口,不关心具体是谁在干活。
常见错误是直接把所有分支塞进一个类里加一堆 if,或者用字典映射函数但没做类型约束——这没解决可扩展性问题,只是换了个地方藏 if。
- 每个业务分支对应一个子类,都继承自同一个抽象基类或协议类(推荐
typing.Protocol或abc.ABC) - 抽象类定义统一方法签名,比如
execute(self, data: dict) -> dict - 实例化时根据输入动态选择子类,而不是在运行时判断字段再分支
如何安全地从输入值映射到具体子类
不能靠字符串拼接类名或 eval,也不该在工厂函数里写新的 if——那只是把分支上移了一层。正确做法是用注册表 + 类属性声明,把映射关系显式、静态地绑定。
示例场景:处理不同消息类型("email"、"sms"、"push")的发送逻辑。
立即学习“Python免费学习笔记(深入)”;
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
from abc import ABC, abstractmethod
<p>class MessageHandler(ABC):
type_code: str # 子类必须声明这个类属性</p><pre class="brush:php;toolbar:false;">@abstractmethod
def send(self, payload: dict) -> bool:
...注册表
_HANDLERS = {}
def register_handler(cls): _HANDLERS[cls.type_code] = cls return cls
@register_handler class EmailHandler(MessageHandler): type_code = "email" def send(self, payload): ...
@register_handler class SmsHandler(MessageHandler): type_code = "sms" def send(self, payload): ...
def get_handler(msg_type: str) -> MessageHandler: handler_cls = _HANDLERS.get(msg_type) if not handler_cls: raise ValueError(f"Unknown message type: {msg_type}") return handler_cls()
这样新增类型只需加个类、加个 @register_handler,不用动任何已有逻辑。
为什么不用字典存函数而要用类
单个函数能搞定的场景,确实不需要多态。但一旦分支逻辑开始共享状态(如连接池、配置对象)、需要初始化(如认证 token)、或要支持单元测试隔离,函数就撑不住了。
- 类可以持有实例状态:
self.session、self.retry_policy,函数每次都要传参或闭包捕获 - 类天然支持依赖注入:测试时可轻松替换
MockHandler,函数得靠patch或全局替换 - IDE 和类型检查器(如 mypy)能识别类方法签名,对字典里的函数值基本无感知
- 错误堆栈里显示的是
EmailHandler.send,而不是匿名函数或模块级函数名,排查更快
容易被忽略的边界:类型分发失效的三种情况
多态替换了 if,但没解决“怎么拿到正确的类型标识”这个前置问题。这里最容易掉坑:
- 输入字段名不一致:API 传
"msg_type",数据库查出来是"channel",映射前没标准化 - 空值或非法值没兜底:
_HANDLERS.get(None)返回None,后面调.send()直接AttributeError,必须提前校验 - 热更新时注册冲突:两个模块都定义了
type_code = "webhook",后注册的会覆盖前一个,且无提示
真正麻烦的从来不是写多态,而是确保那个决定走哪条路的字符串,在整个数据流里始终唯一、可信、可追溯。

















