design-pattern
design-pattern/strategy/exporter-strategy.md
medium
strategy-exporter
导出器策略 — DocxExporter / PdfExporter 可替换。Use when 写 Python 后端代码 / 评审涉及 `exporter-strategy` 的 PR。
exporterstrategydocxpdf导出器策略可替换
paths
**/services/exporters/**/*.py**/exporters/**/*.py
Strategy · 导出器策略
何时用 Strategy
多种"做同一件事"的方式,运行时选一种执行。
典型用例
| 场景 | 策略组 | 实现 |
|---|---|---|
| 报表导出 | OrderExporter | DocxExporter / PdfSummaryExporter / PdfDetailExporter |
| 文档渲染 | DocumentRenderer | HtmlRenderer / MarkdownRenderer |
订单导出器示例
# exporters/order_exporter.py
from abc import ABC, abstractmethod
from io import BytesIO
class OrderExporter(ABC):
@abstractmethod
async def export(self, order: Order) -> bytes:
"""返回文件字节流"""
class DocxExporter(OrderExporter):
async def export(self, order: Order) -> bytes:
from docx import Document
doc = Document()
doc.add_heading(order.title, 0)
for item in order.items:
doc.add_paragraph(item.name)
for attr in item.attrs:
doc.add_paragraph(f" {attr}")
out = BytesIO()
doc.save(out)
return out.getvalue()
class PdfSummaryExporter(OrderExporter):
"""概要视图"""
async def export(self, order: Order) -> bytes:
return await _render_pdf(order, with_detail=False)
class PdfDetailExporter(OrderExporter):
"""完整明细"""
async def export(self, order: Order) -> bytes:
return await _render_pdf(order, with_detail=True)
EXPORTERS: dict[str, type[OrderExporter]] = {
"docx": DocxExporter,
"pdf_summary": PdfSummaryExporter,
"pdf_detail": PdfDetailExporter,
}
def get_exporter(format: str) -> OrderExporter:
if format not in EXPORTERS:
raise ApiException(msg=f"不支持的格式: {format}", code=400)
return EXPORTERS[format]()
Service 编排
class OrderService:
async def export(self, order_id: str, format: str) -> bytes:
order = await self.repo.find_by_id_or_raise(order_id)
exporter = get_exporter(format)
return await exporter.export(order)
反例
# ❌ Service 内 if/else 选格式
async def export(order_id, format):
order = await ...
if format == "docx":
# 50 行 docx 生成代码
elif format == "pdf_summary":
# 50 行 pdf 生成代码
elif format == "pdf_detail":
# 50 行
# 加新格式要改这里 + 风险所有 if/else 分支
# ✅ 用策略
exporter = get_exporter(format)
return await exporter.export(order)
Strategy vs Factory 区别
| Strategy | Factory |
|---|---|
| 选哪个算法做事 | 选哪个对象创建 |
| 行为多态 | 创建多态 |
exporter.export(paper) |
get_provider().__init__(...) |
实际中两者经常一起用:Factory 创建 Strategy 实例。
自检
- [ ] 多个"做同一件事"的方式都继承公共抽象?
- [ ] 选择逻辑集中在工厂函数?
- [ ] 调用方只用接口不知具体类?
- [ ] 加新策略不改老代码(OCP)?