
本文介绍如何通过工厂方法模式,让调用方仅面向抽象类 PdfRenderer 编程,由内部逻辑根据运行时参数(如配置、环境或数据类型)自动选择并创建具体实现类(如 ReportlabRenderer),从而彻底解耦客户端代码与具体实现。
本文介绍如何通过工厂方法模式,让调用方仅面向抽象类 `pdfrenderer` 编程,由内部逻辑根据运行时参数(如配置、环境或数据类型)自动选择并创建具体实现类(如 `reportlabrenderer`),从而彻底解耦客户端代码与具体实现。
在面向对象设计中,直接调用具体子类(如 ReportlabRenderer(...).render())会破坏抽象层的封装性,导致客户端代码对实现细节产生强依赖。理想的方案是:客户端只与抽象类交互,而实例化逻辑由抽象类自身或其配套工厂统一管理。这正是工厂方法模式(Factory Method Pattern)的核心价值。
✅ 推荐实现:静态工厂方法(推荐初学者与多数场景)
在抽象基类中定义一个 @staticmethod 工厂方法,根据传入参数返回合适的子类实例:
from abc import ABC, abstractmethod
from typing import Union, IO, Type
class PdfRenderer(ABC):
@abstractmethod
def render(self, filename: Union[str, IO[bytes]]) -> None:
...
@staticmethod
def create(
renderer_type: str,
data: "CertificateData",
image_path: str,
hash_value: str,
**kwargs
) -> "PdfRenderer":
"""
工厂方法:根据 renderer_type 创建具体渲染器实例
支持扩展(如新增 WeasyPrintRenderer 时只需在此添加分支)
"""
if renderer_type == "reportlab":
return ReportlabRenderer(data, image_path, hash_value)
elif renderer_type == "weasyprint":
return WeasyPrintRenderer(data, image_path, hash_value, **kwargs)
else:
raise ValueError(f"Unsupported renderer type: {renderer_type}")
# 具体实现(保持原有结构)
class ReportlabRenderer(PdfRenderer):
def __init__(self, data: "CertificateData", image_path: str, hash_value: str):
self.data = data
self.image_path = image_path
self.hash = hash_value
def render(self, filename: Union[str, IO[bytes]]) -> None:
# 实际渲染逻辑
print(f"Rendering PDF via ReportLab to {filename}")
# 可选:新增实现不影响现有调用
class WeasyPrintRenderer(PdfRenderer):
def __init__(self, data: "CertificateData", image_path: str, hash_value: str, css_path: str = None):
self.data = data
self.image_path = image_path
self.hash = hash_value
self.css_path = css_path
def render(self, filename: Union[str, IO[bytes]]) -> None:
print(f"Rendering PDF via WeasyPrint to {filename}")
✅ 调用方式:简洁、抽象、可维护
现在,你的业务方法完全无需感知具体实现类:
def render_and_send_pdf(self) -> None:
# 从配置、请求头或上下文动态获取 renderer_type
renderer_type = self.get_renderer_preference() # e.g., "reportlab"
# 一行代码完成抽象类实例化与使用
PdfRenderer.create(
renderer_type=renderer_type,
data=self.certificate_data,
image_path=self.logo_path,
hash_value=self.request.id
).render(temp_file)
⚠️ 注意事项与最佳实践
参数设计要正交:工厂方法的参数应聚焦于“决策依据”(如
renderer_type,output_quality,target_platform),而非所有子类构造函数的全部参数。可考虑用**kwargs或专用配置对象(如RendererConfig)提升可扩展性。-
避免硬编码分支:当渲染器类型较多时,建议改用注册表模式(
_registry: Dict[str, Type[PdfRenderer]]),支持插件式扩展:_registry: Dict[str, Type[PdfRenderer]] = {} @classmethod def register(cls, name: str): def decorator(subclass: Type[PdfRenderer]): cls._registry[name] = subclass return subclass return decorator @classmethod def create(cls, name: str, *args, **kwargs) -> "PdfRenderer": subclass = cls._registry.get(name) if not subclass: raise ValueError(f"No renderer registered for '{name}'") return subclass(*args, **kwargs) 不推荐
__new__或__init__中做工厂逻辑:抽象类本身不可实例化,强行在__new__中重定向会破坏类型安全和 IDE 支持,且违反单一职责原则。测试友好性:工厂方法可独立单元测试;客户端代码可轻松 Mock
PdfRenderer.create,无需修改业务逻辑即可切换实现。
通过该方案,你不仅实现了“PdfRenderer(...).render()”这一理想调用形式,更构建了高内聚、低耦合、易于演进的渲染器体系——这才是抽象类真正应有的力量。










