
本文探讨如何通过组合模式重构过度依赖抽象基类继承的绘图系统,避免爆炸式增长的抽象类(如 AbstractMonthlyMultiPanelPlot),以提升代码可维护性、复用性与 Pythonic 风格。
本文探讨如何通过组合模式重构过度依赖抽象基类继承的绘图系统,避免爆炸式增长的抽象类(如 `abstractmonthlymultipanelplot`),以提升代码可维护性、复用性与 pythonic 风格。
在面向对象设计中,当抽象类数量随业务维度(如“时间粒度”“图表类型”“布局方式”)呈组合式增长时,深层或交叉继承极易导致类爆炸(class explosion)——例如 AbstractSeasonalContourMultiPanelPlot 这类名称已预示着设计失衡。原方案中 AbstractMonthlyPlot 同时承担“时间组织逻辑”(按月拆分子图)和“绘图行为契约”(_plot_for_month),违反了单一职责原则(SRP),也使新增图表类型(如 Bar、Heatmap)必须重复实现相同的时间调度逻辑。
更 Pythonic 的解法是分离关注点:将“如何绘制单个子图”封装为独立的策略类(Plotter),而将“如何组织多个子图”交由组合容器类(MonthlyPlot)管理。这种组合模式天然支持运行时行为切换,且无需修改已有类即可扩展新能力。
以下是重构后的核心实现:
from abc import ABC, abstractmethod
from numpy import ndarray
from typing import List, Tuple
import matplotlib.pyplot as plt
class Plotter(ABC):
"""定义绘图策略的抽象接口:专注‘怎么画’,不关心‘画多少次’或‘在哪画’"""
@abstractmethod
def plot(self, ax, data) -> None:
pass
class ContourPlotter(Plotter):
def plot(self, ax, data) -> None:
ax.contourf(*data)
class LinearPlotter(Plotter):
def plot(self, ax, data) -> None:
ax.plot(*data)
class MonthlyPlot:
"""专注‘画多少次’和‘如何排布’:通过组合获得绘图能力,而非继承"""
def __init__(self, plotter: Plotter):
self.plotter = plotter # 组合:持有策略实例
@property
def n_months(self) -> int:
return 12
def plot(self, month_to_data: List[Tuple[ndarray]]) -> None:
fig, axs = plt.subplots(self.n_months, 1, figsize=(8, 12))
for month in range(self.n_months):
if month < len(month_to_data): # 防御性检查
self.plotter.plot(ax=axs[month], data=month_to_data[month])
plt.tight_layout()
plt.show()该设计带来三大优势:
✅ 高内聚低耦合:Plotter 子类只处理图形语义;MonthlyPlot 只处理布局与流程控制;二者可独立测试与演进。
✅ 开闭原则友好:新增图表类型(如 ScatterPlotter)只需实现 Plotter 接口,无需改动 MonthlyPlot;新增时间维度(如 SeasonalPlot)也只需新建组合类,复用现有 Plotter。
✅ 运行时灵活性:同一 MonthlyPlot 实例可动态替换 plotter,例如 monthly_plot.plotter = ScatterPlotter(),便于 A/B 测试或用户配置。
⚠️ 注意事项:
- 避免过度抽象:仅当存在明确复用需求(如多处需“按月绘图”)时才提取
MonthlyPlot;若仅一处使用,直接函数化(如def plot_monthly(plot_func, data))可能更简洁。 - 类型提示增强可读性:显式标注
plotter: Plotter既约束输入,也向调用者传达设计意图。 - 考虑进一步泛化:若需支持任意子图数(不限于 12),可将
n_months改为n_subplots: int并注入构造器,提升通用性。
总结而言,当继承树开始“横向蔓延”(产生大量组合命名的抽象类)而非“纵向深化”(清晰的 IS-A 层次),便是转向组合的明确信号。Python 崇尚“显式优于隐式”,而组合正是以清晰的依赖关系和可控的复杂度,践行这一哲学。

















