python-design-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePython Design Patterns
Python设计模式
Write maintainable Python code using fundamental design principles. These patterns help you build systems that are easy to understand, test, and modify.
使用基础设计原则编写可维护的Python代码。这些模式能帮助你构建易于理解、测试和修改的系统。
When to Use This Skill
何时使用这些设计原则
- Designing new components or services
- Refactoring complex or tangled code
- Deciding whether to create an abstraction
- Choosing between inheritance and composition
- Evaluating code complexity and coupling
- Planning modular architectures
- 设计新组件或服务
- 重构复杂或混乱的代码
- 决定是否创建抽象
- 在继承和组合之间做选择
- 评估代码复杂度与耦合度
- 规划模块化架构
Core Concepts
核心概念
1. KISS (Keep It Simple)
1. KISS(保持简单)
Choose the simplest solution that works. Complexity must be justified by concrete requirements.
选择可行的最简单解决方案。复杂度必须有具体需求作为依据。
2. Single Responsibility (SRP)
2. 单一职责原则(SRP)
Each unit should have one reason to change. Separate concerns into focused components.
每个单元应该只有一个修改的理由。将关注点拆分到专注的组件中。
3. Composition Over Inheritance
3. 组合优于继承
Build behavior by combining objects, not extending classes.
通过组合对象来构建行为,而非扩展类。
4. Rule of Three
4. 三次原则
Wait until you have three instances before abstracting. Duplication is often better than premature abstraction.
等到出现三个相同场景时再进行抽象。重复代码通常比过早抽象更好。
Quick Start
快速入门
python
undefinedpython
undefinedSimple beats clever
Simple beats clever
Instead of a factory/registry pattern:
Instead of a factory/registry pattern:
FORMATTERS = {"json": JsonFormatter, "csv": CsvFormatter}
def get_formatter(name: str) -> Formatter:
return FORMATTERSname
undefinedFORMATTERS = {"json": JsonFormatter, "csv": CsvFormatter}
def get_formatter(name: str) -> Formatter:
return FORMATTERSname
undefinedFundamental Patterns
基础模式
Pattern 1: KISS - Keep It Simple
模式1:KISS - 保持简单
Before adding complexity, ask: does a simpler solution work?
python
undefined在增加复杂度之前,先问自己:有没有更简单的解决方案?
python
undefinedOver-engineered: Factory with registration
Over-engineered: Factory with registration
class OutputFormatterFactory:
_formatters: dict[str, type[Formatter]] = {}
@classmethod
def register(cls, name: str):
def decorator(formatter_cls):
cls._formatters[name] = formatter_cls
return formatter_cls
return decorator
@classmethod
def create(cls, name: str) -> Formatter:
return cls._formatters[name]()@OutputFormatterFactory.register("json")
class JsonFormatter(Formatter):
...
class OutputFormatterFactory:
_formatters: dict[str, type[Formatter]] = {}
@classmethod
def register(cls, name: str):
def decorator(formatter_cls):
cls._formatters[name] = formatter_cls
return formatter_cls
return decorator
@classmethod
def create(cls, name: str) -> Formatter:
return cls._formatters[name]()@OutputFormatterFactory.register("json")
class JsonFormatter(Formatter):
...
Simple: Just use a dictionary
Simple: Just use a dictionary
FORMATTERS = {
"json": JsonFormatter,
"csv": CsvFormatter,
"xml": XmlFormatter,
}
def get_formatter(name: str) -> Formatter:
"""Get formatter by name."""
if name not in FORMATTERS:
raise ValueError(f"Unknown format: {name}")
return FORMATTERSname
The factory pattern adds code without adding value here. Save patterns for when they solve real problems.FORMATTERS = {
"json": JsonFormatter,
"csv": CsvFormatter,
"xml": XmlFormatter,
}
def get_formatter(name: str) -> Formatter:
"""Get formatter by name."""
if name not in FORMATTERS:
raise ValueError(f"Unknown format: {name}")
return FORMATTERSname
此处工厂模式增加了代码量却没有带来实际价值。只有当模式能解决实际问题时再使用它们。Pattern 2: Single Responsibility Principle
模式2:单一职责原则
Each class or function should have one reason to change.
python
undefined每个类或函数应该只有一个修改的理由。
python
undefinedBAD: Handler does everything
BAD: Handler does everything
class UserHandler:
async def create_user(self, request: Request) -> Response:
# HTTP parsing
data = await request.json()
# Validation
if not data.get("email"):
return Response({"error": "email required"}, status=400)
# Database access
user = await db.execute(
"INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *",
data["email"], data["name"]
)
# Response formatting
return Response({"id": user.id, "email": user.email}, status=201)class UserHandler:
async def create_user(self, request: Request) -> Response:
# HTTP parsing
data = await request.json()
# Validation
if not data.get("email"):
return Response({"error": "email required"}, status=400)
# Database access
user = await db.execute(
"INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *",
data["email"], data["name"]
)
# Response formatting
return Response({"id": user.id, "email": user.email}, status=201)GOOD: Separated concerns
GOOD: Separated concerns
class UserService:
"""Business logic only."""
def __init__(self, repo: UserRepository) -> None:
self._repo = repo
async def create_user(self, data: CreateUserInput) -> User:
# Only business rules here
user = User(email=data.email, name=data.name)
return await self._repo.save(user)class UserHandler:
"""HTTP concerns only."""
def __init__(self, service: UserService) -> None:
self._service = service
async def create_user(self, request: Request) -> Response:
data = CreateUserInput(**(await request.json()))
user = await self._service.create_user(data)
return Response(user.to_dict(), status=201)
Now HTTP changes don't affect business logic, and vice versa.class UserService:
"""Business logic only."""
def __init__(self, repo: UserRepository) -> None:
self._repo = repo
async def create_user(self, data: CreateUserInput) -> User:
# Only business rules here
user = User(email=data.email, name=data.name)
return await self._repo.save(user)class UserHandler:
"""HTTP concerns only."""
def __init__(self, service: UserService) -> None:
self._service = service
async def create_user(self, request: Request) -> Response:
data = CreateUserInput(**(await request.json()))
user = await self._service.create_user(data)
return Response(user.to_dict(), status=201)
现在HTTP相关的修改不会影响业务逻辑,反之亦然。Pattern 3: Separation of Concerns
模式3:关注点分离
Organize code into distinct layers with clear responsibilities.
┌─────────────────────────────────────────────────────┐
│ API Layer (handlers) │
│ - Parse requests │
│ - Call services │
│ - Format responses │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Service Layer (business logic) │
│ - Domain rules and validation │
│ - Orchestrate operations │
│ - Pure functions where possible │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Repository Layer (data access) │
│ - SQL queries │
│ - External API calls │
│ - Cache operations │
└─────────────────────────────────────────────────────┘Each layer depends only on layers below it:
python
undefined将代码组织成职责明确的不同层级。
┌─────────────────────────────────────────────────────┐
│ API 层(处理器) │
│ - 解析请求 │
│ - 调用服务 │
│ - 格式化响应 │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 服务层(业务逻辑) │
│ - 领域规则与验证 │
│ - 编排操作 │
│ - 尽可能使用纯函数 │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 仓储层(数据访问) │
│ - SQL 查询 │
│ - 外部 API 调用 │
│ - 缓存操作 │
└─────────────────────────────────────────────────────┘每个层级仅依赖其下方的层级:
python
undefinedRepository: Data access
Repository: Data access
class UserRepository:
async def get_by_id(self, user_id: str) -> User | None:
row = await self._db.fetchrow(
"SELECT * FROM users WHERE id = $1", user_id
)
return User(**row) if row else None
class UserRepository:
async def get_by_id(self, user_id: str) -> User | None:
row = await self._db.fetchrow(
"SELECT * FROM users WHERE id = $1", user_id
)
return User(**row) if row else None
Service: Business logic
Service: Business logic
class UserService:
def init(self, repo: UserRepository) -> None:
self._repo = repo
async def get_user(self, user_id: str) -> User:
user = await self._repo.get_by_id(user_id)
if user is None:
raise UserNotFoundError(user_id)
return userclass UserService:
def init(self, repo: UserRepository) -> None:
self._repo = repo
async def get_user(self, user_id: str) -> User:
user = await self._repo.get_by_id(user_id)
if user is None:
raise UserNotFoundError(user_id)
return userHandler: HTTP concerns
Handler: HTTP concerns
@app.get("/users/{user_id}")
async def get_user(user_id: str) -> UserResponse:
user = await user_service.get_user(user_id)
return UserResponse.from_user(user)
undefined@app.get("/users/{user_id}")
async def get_user(user_id: str) -> UserResponse:
user = await user_service.get_user(user_id)
return UserResponse.from_user(user)
undefinedPattern 4: Composition Over Inheritance
模式4:组合优于继承
Build behavior by combining objects rather than inheriting.
python
undefined通过组合对象而非继承来构建行为。
python
undefinedInheritance: Rigid and hard to test
Inheritance: Rigid and hard to test
class EmailNotificationService(NotificationService):
def init(self):
super().init()
self._smtp = SmtpClient() # Hard to mock
def notify(self, user: User, message: str) -> None:
self._smtp.send(user.email, message)class EmailNotificationService(NotificationService):
def init(self):
super().init()
self._smtp = SmtpClient() # Hard to mock
def notify(self, user: User, message: str) -> None:
self._smtp.send(user.email, message)Composition: Flexible and testable
Composition: Flexible and testable
class NotificationService:
"""Send notifications via multiple channels."""
def __init__(
self,
email_sender: EmailSender,
sms_sender: SmsSender | None = None,
push_sender: PushSender | None = None,
) -> None:
self._email = email_sender
self._sms = sms_sender
self._push = push_sender
async def notify(
self,
user: User,
message: str,
channels: set[str] | None = None,
) -> None:
channels = channels or {"email"}
if "email" in channels:
await self._email.send(user.email, message)
if "sms" in channels and self._sms and user.phone:
await self._sms.send(user.phone, message)
if "push" in channels and self._push and user.device_token:
await self._push.send(user.device_token, message)class NotificationService:
"""Send notifications via multiple channels."""
def __init__(
self,
email_sender: EmailSender,
sms_sender: SmsSender | None = None,
push_sender: PushSender | None = None,
) -> None:
self._email = email_sender
self._sms = sms_sender
self._push = push_sender
async def notify(
self,
user: User,
message: str,
channels: set[str] | None = None,
) -> None:
channels = channels or {"email"}
if "email" in channels:
await self._email.send(user.email, message)
if "sms" in channels and self._sms and user.phone:
await self._sms.send(user.phone, message)
if "push" in channels and self._push and user.device_token:
await self._push.send(user.device_token, message)Easy to test with fakes
Easy to test with fakes
service = NotificationService(
email_sender=FakeEmailSender(),
sms_sender=FakeSmsSender(),
)
undefinedservice = NotificationService(
email_sender=FakeEmailSender(),
sms_sender=FakeSmsSender(),
)
undefinedAdvanced Patterns
进阶模式
Pattern 5: Rule of Three
模式5:三次原则
Wait until you have three instances before abstracting.
python
undefined等到出现三个相同场景时再进行抽象。
python
undefinedTwo similar functions? Don't abstract yet
Two similar functions? Don't abstract yet
def process_orders(orders: list[Order]) -> list[Result]:
results = []
for order in orders:
validated = validate_order(order)
result = process_validated_order(validated)
results.append(result)
return results
def process_returns(returns: list[Return]) -> list[Result]:
results = []
for ret in returns:
validated = validate_return(ret)
result = process_validated_return(validated)
results.append(result)
return results
def process_orders(orders: list[Order]) -> list[Result]:
results = []
for order in orders:
validated = validate_order(order)
result = process_validated_order(validated)
results.append(result)
return results
def process_returns(returns: list[Return]) -> list[Result]:
results = []
for ret in returns:
validated = validate_return(ret)
result = process_validated_return(validated)
results.append(result)
return results
These look similar, but wait! Are they actually the same?
These look similar, but wait! Are they actually the same?
Different validation, different processing, different errors...
Different validation, different processing, different errors...
Duplication is often better than the wrong abstraction
Duplication is often better than the wrong abstraction
Only after a third case, consider if there's a real pattern
Only after a third case, consider if there's a real pattern
But even then, sometimes explicit is better than abstract
But even then, sometimes explicit is better than abstract
undefinedundefinedPattern 6: Function Size Guidelines
模式6:函数大小指南
Keep functions focused. Extract when a function:
- Exceeds 20-50 lines (varies by complexity)
- Serves multiple distinct purposes
- Has deeply nested logic (3+ levels)
python
undefined保持函数专注。当函数出现以下情况时进行拆分:
- 超过20-50行(根据复杂度有所不同)
- 承担多个不同职责
- 存在深度嵌套逻辑(3层及以上)
python
undefinedToo long, multiple concerns mixed
Too long, multiple concerns mixed
def process_order(order: Order) -> Result:
# 50 lines of validation...
# 30 lines of inventory check...
# 40 lines of payment processing...
# 20 lines of notification...
pass
def process_order(order: Order) -> Result:
# 50 lines of validation...
# 30 lines of inventory check...
# 40 lines of payment processing...
# 20 lines of notification...
pass
Better: Composed from focused functions
Better: Composed from focused functions
def process_order(order: Order) -> Result:
"""Process a customer order through the complete workflow."""
validate_order(order)
reserve_inventory(order)
payment_result = charge_payment(order)
send_confirmation(order, payment_result)
return Result(success=True, order_id=order.id)
undefineddef process_order(order: Order) -> Result:
"""Process a customer order through the complete workflow."""
validate_order(order)
reserve_inventory(order)
payment_result = charge_payment(order)
send_confirmation(order, payment_result)
return Result(success=True, order_id=order.id)
undefinedPattern 7: Dependency Injection
模式7:依赖注入
Pass dependencies through constructors for testability.
python
from typing import Protocol
class Logger(Protocol):
def info(self, msg: str, **kwargs) -> None: ...
def error(self, msg: str, **kwargs) -> None: ...
class Cache(Protocol):
async def get(self, key: str) -> str | None: ...
async def set(self, key: str, value: str, ttl: int) -> None: ...
class UserService:
"""Service with injected dependencies."""
def __init__(
self,
repository: UserRepository,
cache: Cache,
logger: Logger,
) -> None:
self._repo = repository
self._cache = cache
self._logger = logger
async def get_user(self, user_id: str) -> User:
# Check cache first
cached = await self._cache.get(f"user:{user_id}")
if cached:
self._logger.info("Cache hit", user_id=user_id)
return User.from_json(cached)
# Fetch from database
user = await self._repo.get_by_id(user_id)
if user:
await self._cache.set(f"user:{user_id}", user.to_json(), ttl=300)
return user通过构造函数传递依赖以提升可测试性。
python
from typing import Protocol
class Logger(Protocol):
def info(self, msg: str, **kwargs) -> None: ...
def error(self, msg: str, **kwargs) -> None: ...
class Cache(Protocol):
async def get(self, key: str) -> str | None: ...
async def set(self, key: str, value: str, ttl: int) -> None: ...
class UserService:
"""Service with injected dependencies."""
def __init__(
self,
repository: UserRepository,
cache: Cache,
logger: Logger,
) -> None:
self._repo = repository
self._cache = cache
self._logger = logger
async def get_user(self, user_id: str) -> User:
# Check cache first
cached = await self._cache.get(f"user:{user_id}")
if cached:
self._logger.info("Cache hit", user_id=user_id)
return User.from_json(cached)
# Fetch from database
user = await self._repo.get_by_id(user_id)
if user:
await self._cache.set(f"user:{user_id}", user.to_json(), ttl=300)
return userProduction
Production
service = UserService(
repository=PostgresUserRepository(db),
cache=RedisCache(redis),
logger=StructlogLogger(),
)
service = UserService(
repository=PostgresUserRepository(db),
cache=RedisCache(redis),
logger=StructlogLogger(),
)
Testing
Testing
service = UserService(
repository=InMemoryUserRepository(),
cache=FakeCache(),
logger=NullLogger(),
)
undefinedservice = UserService(
repository=InMemoryUserRepository(),
cache=FakeCache(),
logger=NullLogger(),
)
undefinedPattern 8: Avoiding Common Anti-Patterns
模式8:避免常见反模式
Don't expose internal types:
python
undefined不要暴露内部类型:
python
undefinedBAD: Leaking ORM model to API
BAD: Leaking ORM model to API
@app.get("/users/{id}")
def get_user(id: str) -> UserModel: # SQLAlchemy model
return db.query(UserModel).get(id)
@app.get("/users/{id}")
def get_user(id: str) -> UserModel: # SQLAlchemy model
return db.query(UserModel).get(id)
GOOD: Use response schemas
GOOD: Use response schemas
@app.get("/users/{id}")
def get_user(id: str) -> UserResponse:
user = db.query(UserModel).get(id)
return UserResponse.from_orm(user)
**Don't mix I/O with business logic:**
```python@app.get("/users/{id}")
def get_user(id: str) -> UserResponse:
user = db.query(UserModel).get(id)
return UserResponse.from_orm(user)
**不要将I/O与业务逻辑混合:**
```pythonBAD: SQL embedded in business logic
BAD: SQL embedded in business logic
def calculate_discount(user_id: str) -> float:
user = db.query("SELECT * FROM users WHERE id = ?", user_id)
orders = db.query("SELECT * FROM orders WHERE user_id = ?", user_id)
# Business logic mixed with data access
def calculate_discount(user_id: str) -> float:
user = db.query("SELECT * FROM users WHERE id = ?", user_id)
orders = db.query("SELECT * FROM orders WHERE user_id = ?", user_id)
# Business logic mixed with data access
GOOD: Repository pattern
GOOD: Repository pattern
def calculate_discount(user: User, order_history: list[Order]) -> float:
# Pure business logic, easily testable
if len(order_history) > 10:
return 0.15
return 0.0
undefineddef calculate_discount(user: User, order_history: list[Order]) -> float:
# Pure business logic, easily testable
if len(order_history) > 10:
return 0.15
return 0.0
undefinedBest Practices Summary
最佳实践总结
- Keep it simple - Choose the simplest solution that works
- Single responsibility - Each unit has one reason to change
- Separate concerns - Distinct layers with clear purposes
- Compose, don't inherit - Combine objects for flexibility
- Rule of three - Wait before abstracting
- Keep functions small - 20-50 lines (varies by complexity), one purpose
- Inject dependencies - Constructor injection for testability
- Delete before abstracting - Remove dead code, then consider patterns
- Test each layer - Isolated tests for each concern
- Explicit over clever - Readable code beats elegant code
- 保持简单 - 选择可行的最简单解决方案
- 单一职责 - 每个单元只有一个修改的理由
- 分离关注点 - 职责明确的不同层级
- 组合而非继承 - 通过组合对象实现灵活性
- 三次原则 - 等待合适的时机再进行抽象
- 保持函数短小 - 20-50行(根据复杂度调整),单一职责
- 依赖注入 - 构造函数注入提升可测试性
- 先删除再抽象 - 移除无用代码后再考虑使用模式
- 分层测试 - 对每个关注点进行独立测试
- 显式优于巧妙 - 可读代码优于优雅代码