
本文介绍如何通过策略模式与组合(Composition)重构过度依赖抽象类继承的绘图系统,避免爆炸式增长的抽象基类(如 AbstractMonthlyMultiPanelPlot),提升代码可维护性、复用性与可测试性。
本文介绍如何通过策略模式与组合(composition)重构过度依赖抽象类继承的绘图系统,避免爆炸式增长的抽象基类(如 `abstractmonthlymultipanelplot`),提升代码可维护性、复用性与可测试性。
在面向对象设计中,当发现抽象类数量随业务维度(如“月度/季节/多面板”“等高线/折线/散点”)呈笛卡尔积式增长时,往往意味着继承体系已偏离初衷——它不再表达“is-a”关系,而是在强行模拟配置组合。你示例中的 AbstractMonthlyPlot、后续可能衍生的 AbstractSeasonalPlot 或 AbstractMultiPanelPlot,本质上是将数据组织方式(时间维度结构)与可视化行为(绘图逻辑)耦合在同一个继承链中,这既违背单一职责原则,也严重削弱扩展能力:每新增一种图表类型或一种时间切片方式,都需创建新抽象类甚至多重继承子类。
更 Pythonic 的解法是采用组合优于继承(Composition over Inheritance)原则,将系统拆分为两个正交职责层:
-
行为层(Strategy):封装绘图逻辑,如
ContourPlotter、LinearPlotter,统一实现Plotter接口; -
结构层(Context):负责布局、循环、资源管理,如
MonthlyPlot,通过构造函数注入具体Plotter实例。
这种分离使变化点解耦:添加新图表类型只需新增一个 Plotter 子类;调整时间维度(如改为季度)只需新增 QuarterlyPlot 类,复用全部现有 Plotter;甚至可动态切换策略:
# 支持运行时策略切换
class MonthlyPlot:
def __init__(self, plotter: Plotter):
self.plotter = plotter # 可随时 reassign
def set_plotter(self, new_plotter: Plotter):
self.plotter = new_plotter
def plot(self, month_to_data: List[Tuple[ndarray]]):
fig, axs = plt.subplots(12, 1, figsize=(8, 24))
for i, data in enumerate(month_to_data):
self.plotter.plot(ax=axs[i], data=data)
return fig
此外,该设计天然支持依赖注入与单元测试:MonthlyPlot 的行为可被完全隔离测试(mock plotter.plot),无需真实 matplotlib 环境;Plotter 子类亦可独立验证绘图逻辑。相较深层继承,代码体积更小、继承深度恒为 1(仅 Plotter → 具体实现),且符合 Python 的显式、扁平哲学——MonthlyPlot(contour_plotter) 比 Contour(AbstractMonthlyPlot) 更清晰地表达了“用等高线方式绘制月度图”的语义。
注意事项:
- 避免为“微小差异”创建新
Plotter类(如仅颜色不同),应通过参数化(如ColorPlotter(color='red'))或装饰器处理; - 若
Plotter接口方法增多(如需支持save()、annotate()),可引入协议(typing.Protocol)替代 ABC,进一步降低耦合; - 对于复杂布局(如嵌套多面板),可将
MonthlyPlot升级为可组合的Layout抽象(如VerticalLayout,GridLayout),形成策略+布局的双层组合体系。
最终,这不是放弃 OOP,而是更精准地运用它:用继承定义稳定契约(Plotter 接口),用组合实现灵活装配(MonthlyPlot + ContourPlotter)。这才是真正践行 DRY 与开闭原则的 Python 方式。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











