05-设计模式实战¶
学习时间: 约3-4小时 难度级别: ⭐⭐⭐⭐ 中高级 前置知识: 设计原则、创建型/结构型/行为型模式 学习目标: 在真实项目场景中综合运用设计模式,识别反模式,掌握面试策略
🎯 学习目标¶
- 掌握实际项目中最常用的8种模式及其组合方式
- 通过电商系统、Web框架、游戏开发三个完整案例理解模式应用
- 识别常见反模式和过度设计的陷阱
- 掌握设计模式面试回答框架和20道高频题
目录¶
1. 最常用的8种模式¶
按实际项目使用频率排序:
| 排名 | 模式 | 类型 | 典型场景 |
|---|---|---|---|
| 1 | 策略 | 行为型 | 多种算法/规则切换 |
| 2 | 观察者 | 行为型 | 事件系统、数据同步 |
| 3 | 工厂方法 | 创建型 | 对象创建解耦 |
| 4 | 单例 | 创建型 | 全局资源管理 |
| 5 | 装饰器 | 结构型 | 动态添加功能 |
| 6 | 代理 | 结构型 | 缓存、权限、延迟加载 |
| 7 | 模板方法 | 行为型 | 框架扩展点 |
| 8 | 命令 | 行为型 | 撤销/重做、任务队列 |
原则:不要为了用模式而用模式。先写最简单的实现,当代码出现"坏味道"时再考虑引入设计模式。
2. 实战案例一:电商系统重构¶
场景描述¶
一个电商系统核心模块存在以下问题: - 促销规则用大量 if/elif 堆叠,难以新增规则 - 下单后需要触发多个操作(扣库存、发通知、记日志),耦合严重 - 支付渠道硬编码,新增支付方式需要改核心代码
重构方案:策略 + 工厂 + 观察者¶
from abc import ABC, abstractmethod
from typing import Dict, List, Callable
from dataclasses import dataclass
from enum import Enum
# ================================
# 1. 策略模式:促销规则
# ================================
class PromotionStrategy(ABC):
@abstractmethod
def calculate(self, original_price: float) -> float:
pass
@abstractmethod
def description(self) -> str:
pass
class NoPromotion(PromotionStrategy):
def calculate(self, price): return price
def description(self): return "无优惠"
class PercentDiscount(PromotionStrategy):
def __init__(self, percent):
self.percent = percent
def calculate(self, price):
return price * (1 - self.percent / 100)
def description(self):
return f"{self.percent}%折扣"
class FullReduction(PromotionStrategy):
"""满减"""
def __init__(self, threshold, reduction):
self.threshold = threshold
self.reduction = reduction
def calculate(self, price):
if price >= self.threshold:
return price - self.reduction
return price
def description(self):
return f"满{self.threshold}减{self.reduction}"
class StackedPromotion(PromotionStrategy):
"""组合优惠:可叠加多种策略"""
def __init__(self, strategies: List[PromotionStrategy]):
self.strategies = strategies
def calculate(self, price):
result = price
for s in self.strategies:
result = s.calculate(result)
return result
def description(self):
return " + ".join(s.description() for s in self.strategies)
# ================================
# 2. 工厂模式:支付渠道
# ================================
class PaymentProcessor(ABC):
@abstractmethod
def process(self, amount: float, order_id: str) -> bool:
pass
class AlipayProcessor(PaymentProcessor):
def process(self, amount, order_id):
print(f" 💳 Alipay: Processing ¥{amount:.2f} for {order_id}")
return True
class WeChatPayProcessor(PaymentProcessor):
def process(self, amount, order_id):
print(f" 💳 WeChatPay: Processing ¥{amount:.2f} for {order_id}")
return True
class CreditCardProcessor(PaymentProcessor):
def process(self, amount, order_id):
print(f" 💳 CreditCard: Processing ¥{amount:.2f} for {order_id}")
return True
class PaymentFactory:
"""支付处理器工厂"""
_processors = {
"alipay": AlipayProcessor,
"wechat": WeChatPayProcessor,
"credit_card": CreditCardProcessor,
}
@classmethod
def create(cls, method: str) -> PaymentProcessor:
processor_cls = cls._processors.get(method)
if not processor_cls:
raise ValueError(f"Unsupported payment: {method}")
return processor_cls()
@classmethod
def register(cls, name: str, processor_cls):
"""注册新支付方式,无需修改工厂代码"""
cls._processors[name] = processor_cls
# ================================
# 3. 观察者模式:订单事件
# ================================
class EventBus:
"""全局事件总线"""
_instance = None
def __new__(cls):
if not cls._instance:
cls._instance = super().__new__(cls) # super()调用父类方法
cls._instance._listeners = {}
return cls._instance
def on(self, event: str, listener: Callable):
self._listeners.setdefault(event, []).append(listener)
def emit(self, event: str, data=None):
for listener in self._listeners.get(event, []):
listener(data)
# ================================
# 整合:OrderService
# ================================
@dataclass
class OrderItem:
name: str
price: float
quantity: int
class OrderService:
def __init__(self):
self.event_bus = EventBus()
def create_order(self, items: List[OrderItem], promotion: PromotionStrategy,
payment_method: str) -> dict:
# 1. 计算价格
subtotal = sum(item.price * item.quantity for item in items)
total = promotion.calculate(subtotal)
order = {
"id": f"ORD-{id(items) % 10000:04d}",
"items": items,
"subtotal": subtotal,
"promotion": promotion.description(),
"total": total,
}
print(f"\n📦 Order {order['id']}:")
print(f" Subtotal: ¥{subtotal:.2f}")
print(f" Promotion: {promotion.description()}")
print(f" Total: ¥{total:.2f}")
# 2. 支付(工厂模式创建处理器)
processor = PaymentFactory.create(payment_method)
if not processor.process(total, order["id"]):
raise Exception("Payment failed")
# 3. 发布事件(观察者模式解耦后续操作)
self.event_bus.emit("order_created", order)
self.event_bus.emit("payment_completed", {"order_id": order["id"], "amount": total})
return order
# ====== 注册事件处理器(各服务独立) ======
event_bus = EventBus()
# 库存服务
event_bus.on("order_created", lambda order:
print(f" 📦 Inventory: Deducting stock for {order['id']}"))
# 通知服务
event_bus.on("order_created", lambda order:
print(f" 📧 Notification: Sending confirmation for {order['id']}"))
# 积分服务
event_bus.on("payment_completed", lambda data:
print(f" ⭐ Points: Adding {int(data['amount'])} points"))
# 日志服务
event_bus.on("order_created", lambda order:
print(f" 📝 Logger: Order {order['id']} logged"))
# ====== 使用 ======
service = OrderService()
items = [
OrderItem("Python Book", 89.0, 1),
OrderItem("Keyboard", 299.0, 1),
]
# 满300减50 + 会员9折
promo = StackedPromotion([
FullReduction(300, 50),
PercentDiscount(10),
])
order = service.create_order(items, promo, "alipay")
重构效果¶
| 问题 | 模式 | 效果 |
|---|---|---|
| 促销规则硬编码 | 策略模式 | 新增促销只需加一个类 |
| 下单后操作耦合 | 观察者模式 | 各服务独立订阅,互不影响 |
| 支付渠道硬编码 | 工厂模式 | 注册新支付方式不改核心代码 |
3. 实战案例二:Web框架中的模式¶
Django中的设计模式¶
| 模式 | Django中的体现 |
|---|---|
| 模板方法 | View 类的 dispatch() 调用 get()/post() — 骨架固定,具体操作子类实现 |
| 策略 | Authentication Backend — 可配置多种认证策略 |
| 观察者 | Signals(pre_save, post_save)— 模型操作后自动触发 |
| 代理 | QuerySet 延迟查询 — 直到真正需要数据才执行SQL |
| 装饰器 | @login_required, @cache_page |
| 责任链 | Middleware 链式处理请求 |
| 外观 | django.shortcuts 简化复杂操作 |
Flask中的模式¶
from flask import Flask, request, jsonify
from functools import wraps
app = Flask(__name__)
# ====== 装饰器模式:认证 ======
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get("Authorization")
if not token or token != "Bearer valid_token":
return jsonify({"error": "Unauthorized"}), 401
return f(*args, **kwargs)
return decorated
# ====== 策略模式:序列化 ======
class ResponseSerializer:
"""不同格式的响应序列化"""
serializers = {
"json": lambda data: jsonify(data),
"xml": lambda data: f"<response>{data}</response>",
}
@classmethod
def serialize(cls, data, fmt="json"):
serializer = cls.serializers.get(fmt, cls.serializers["json"])
return serializer(data)
# ====== 责任链模式:中间件 ======
@app.before_request
def log_request():
print(f"[LOG] {request.method} {request.path}")
@app.before_request
def check_rate_limit():
# 简化的限流检查
pass
# ====== 使用 ======
@app.route("/api/users")
@require_auth
def get_users():
users = [{"id": 1, "name": "Alice"}]
fmt = request.args.get("format", "json")
return ResponseSerializer.serialize(users, fmt)
Spring框架中的模式¶
| 模式 | Spring中的体现 |
|---|---|
| 工厂 | BeanFactory / ApplicationContext — IoC容器 |
| 单例 | Bean默认scope为singleton |
| 代理 | Spring AOP — 基于JDK动态代理/CGLIB |
| 模板方法 | JdbcTemplate, RestTemplate |
| 观察者 | ApplicationEvent / @EventListener |
| 策略 | HandlerMapping 决定请求交给哪个Controller |
| 责任链 | Filter 链、HandlerInterceptor 链 |
4. 实战案例三:游戏开发模式¶
场景:2D RPG游戏核心架构¶
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import List, Dict
from enum import Enum
import copy
# ================================
# 1. 状态模式:角色状态机
# ================================
class CharacterState(ABC):
@abstractmethod
def handle_input(self, character, action): pass
@abstractmethod
def update(self, character): pass
class IdleState(CharacterState):
def handle_input(self, character, action):
if action == "move":
character.state = MovingState()
elif action == "attack":
character.state = AttackingState()
def update(self, character):
character.stamina = min(100, character.stamina + 1) # 休息恢复
class MovingState(CharacterState):
def handle_input(self, character, action):
if action == "stop":
character.state = IdleState()
elif action == "attack":
character.state = AttackingState()
def update(self, character):
character.stamina -= 0.5
class AttackingState(CharacterState):
def __init__(self):
self.frames = 0
def handle_input(self, character, action):
pass # 攻击中不接受输入
def update(self, character):
self.frames += 1
character.stamina -= 2
if self.frames >= 10: # 攻击动画结束
character.state = IdleState()
# ================================
# 2. 策略模式:攻击方式
# ================================
class AttackStrategy(ABC):
@abstractmethod
def calculate_damage(self, attacker, target) -> int:
pass
class MeleeAttack(AttackStrategy):
def calculate_damage(self, attacker, target):
damage = attacker.strength * 2 - target.defense
return max(1, damage)
class MagicAttack(AttackStrategy):
def calculate_damage(self, attacker, target):
damage = attacker.intelligence * 3 - target.magic_defense
return max(1, damage)
class RangedAttack(AttackStrategy):
def calculate_damage(self, attacker, target):
damage = attacker.dexterity * 2.5 - target.defense * 0.5
return max(1, int(damage))
# ================================
# 3. 观察者模式:游戏事件
# ================================
class GameEventSystem:
_handlers: Dict[str, List] = {}
@classmethod # @classmethod类方法,第一个参数为类本身
def on(cls, event, handler):
cls._handlers.setdefault(event, []).append(handler)
@classmethod
def emit(cls, event, **kwargs): # *args接收任意位置参数;**kwargs接收任意关键字参数
for handler in cls._handlers.get(event, []):
handler(**kwargs)
# ================================
# 4. 命令模式:可撤销操作
# ================================
class GameCommand(ABC):
@abstractmethod
def execute(self): pass
@abstractmethod
def undo(self): pass
class MoveCommand(GameCommand):
def __init__(self, character, dx, dy):
self.character = character
self.dx, self.dy = dx, dy
def execute(self):
self.character.x += self.dx
self.character.y += self.dy
def undo(self):
self.character.x -= self.dx
self.character.y -= self.dy
# ================================
# 5. 原型模式:怪物批量生成
# ================================
@dataclass # 自动生成__init__等方法
class Monster:
name: str
hp: int
attack: int
defense: int
loot: List[str] = field(default_factory=list)
def clone(self):
return copy.deepcopy(self)
class MonsterFactory:
"""原型工厂:预设怪物模板"""
_prototypes: Dict[str, Monster] = {}
@classmethod
def register(cls, name, prototype: Monster):
cls._prototypes[name] = prototype
@classmethod
def create(cls, name, **overrides) -> Monster:
proto = cls._prototypes.get(name)
if not proto:
raise ValueError(f"Unknown monster: {name}")
monster = proto.clone()
for key, value in overrides.items():
setattr(monster, key, value) # hasattr/getattr/setattr动态操作对象属性
return monster
# ====== 注册怪物模板 ======
MonsterFactory.register("slime", Monster("Slime", hp=30, attack=5, defense=2, loot=["jelly"]))
MonsterFactory.register("goblin", Monster("Goblin", hp=50, attack=12, defense=5, loot=["gold", "dagger"]))
MonsterFactory.register("dragon", Monster("Dragon", hp=500, attack=80, defense=40, loot=["dragon_scale"]))
# 批量生成:基于原型快速创建不同变种
slime1 = MonsterFactory.create("slime")
slime_boss = MonsterFactory.create("slime", name="King Slime", hp=200, attack=25)
goblin = MonsterFactory.create("goblin")
print(f"{slime1.name}: HP={slime1.hp}") # Slime: HP=30
print(f"{slime_boss.name}: HP={slime_boss.hp}") # King Slime: HP=200
# ====== 注册事件处理 ======
GameEventSystem.on("monster_killed", lambda **kw: # lambda匿名函数:简洁的单行函数
print(f" 🎉 {kw['monster']} defeated! Loot: {kw.get('loot', [])}"))
GameEventSystem.on("monster_killed", lambda **kw:
print(f" ⭐ +{kw.get('exp', 0)} EXP"))
# 触发事件
GameEventSystem.emit("monster_killed", monster="Goblin", loot=["gold"], exp=50)
游戏开发常用模式总结¶
| 模式 | 在游戏中的用途 |
|---|---|
| 状态 | 角色状态机(idle/move/attack/dead) |
| 策略 | AI行为、攻击方式、移动算法 |
| 观察者 | 成就系统、UI更新、音效触发 |
| 命令 | 回放系统、网络同步、撤销 |
| 原型 | NPC/怪物批量生成 |
| 享元 | 地图Tile、粒子系统 |
| 组合 | 场景图、UI组件树 |
5. 反模式与过度设计¶
常见反模式¶
1. God Object(上帝对象)¶
# ❌ 反模式:一个类做所有事
class Application:
def handle_request(self): ...
def connect_database(self): ...
def send_email(self): ...
def render_template(self): ...
def validate_input(self): ...
def generate_report(self): ...
def process_payment(self): ...
# 几千行代码...
2. Singleton滥用¶
# ❌ 反模式:把所有共享状态都做成单例
class UserManager: ... # 单例
class OrderManager: ... # 单例
class ProductManager: ... # 单例
class ConfigManager: ... # 单例
# 本质上是全局变量,不利于测试和并发
# ✅ 应该用依赖注入
class OrderService:
def __init__(self, user_repo, product_repo, config):
self.user_repo = user_repo
self.product_repo = product_repo
self.config = config
3. 过度设计¶
# ❌ 过度设计:一个简单功能用了3个模式
class AbstractLoggerFactory(ABC): ... # ABC抽象基类;abstractmethod强制子类实现
class ConsoleLoggerFactory(AbstractLoggerFactory): ...
class FileLoggerFactory(AbstractLoggerFactory): ...
class LoggerDecorator: ...
class TimestampDecorator(LoggerDecorator): ...
class LevelDecorator(LoggerDecorator): ...
# 实际只需要打印日志...
# ✅ 简单实现
import logging
logger = logging.getLogger(__name__)
logger.info("This is enough!")
何时引入设计模式¶
三次法则:当同样的问题出现三次时,才考虑引入设计模式。
代码"坏味道"→ 对应模式¶
| 坏味道 | 建议模式 |
|---|---|
| 大量 if/elif/else 分支 | 策略模式 / 状态模式 |
| 一个改动需要修改多处 | 观察者模式 |
| 对象创建逻辑散落各处 | 工厂模式 |
| 需要扩展功能但不想继承 | 装饰器模式 |
| 复杂子系统API难用 | 外观模式 |
| 大量重复的大对象 | 享元模式 |
6. 面试策略与高频题¶
面试回答框架 STAR-P¶
- Situation — 什么场景/问题
- Task — 需要解决什么
- Action — 选择了什么模式,为什么
- Result — 带来了什么好处
- Principle — 遵循了什么设计原则
例如:"在电商系统中(S),促销规则频繁变化(T),我们使用策略模式将每种促销封装为独立类(A),新增促销只需添加类不用改核心代码(R),符合开闭原则(P)。"
20道高频面试题¶
基础概念¶
Q1: 什么是设计模式? A: 设计模式是针对反复出现的软件设计问题的通用可复用解决方案。由GoF提出23种经典模式,分为创建型、结构型、行为型三类。它不是可以直接使用的代码,而是经过验证的设计思想。
Q2: 面向对象设计的SOLID原则是什么? A: S单一职责、O开闭原则、L里氏替换、I接口隔离、D依赖倒置。其中开闭原则最重要——对扩展开放、对修改关闭,几乎所有设计模式都在实践这个原则。
Q3: 组合优于继承,为什么? A: 继承是编译时绑定的强耦合关系,组合是运行时灵活调整的弱耦合。继承破坏封装性(子类依赖父类实现),组合只依赖接口。策略模式、装饰器模式都是组合优于继承的典型体现。
创建型¶
Q4: 单例模式如何保证线程安全? A: Python可用模块级变量(天然线程安全)或 threading.Lock + 双重检查;Java推荐用枚举实现(最简单安全)或 volatile + synchronized 双重检查锁。
Q5: 工厂方法和抽象工厂的区别? A: 工厂方法创建一种产品,靠子类决定创建哪种。抽象工厂创建一族相关产品(如按钮+输入框+弹框),保证产品间风格一致。
Q6: 建造者模式和工厂模式的区别? A: 工厂关注"创建什么"(不同类型产品),建造者关注"如何创建"(相同类型但不同配置的复杂对象)。建造者适合参数很多的对象构造。
结构型¶
Q7: 装饰器和代理模式的区别? A: 装饰器为对象添加新功能(如Java IO的BufferedReader包装FileReader),客户端通常知道有装饰。代理控制对对象的访问(如缓存代理、权限代理),对客户端透明。
Q8: 适配器模式的实际用例? A: 统一多个第三方支付SDK接口、旧系统到新系统的接口适配、不同数据格式之间的转换层。核心是让不兼容的接口协同工作。
Q9: 外观模式解决什么问题? A: 为复杂子系统提供简单统一接口。如 requests.get(url) 封装了Socket连接、HTTP协议构造、SSL握手等复杂操作。
Q10: 享元模式和缓存的区别? A: 享元模式共享不可变的内部状态以节省内存(如字体渲染中的字形对象)。缓存存储可变的计算结果以节省时间。享元关注内存,缓存关注速度。
行为型¶
Q11: 策略模式和状态模式的区别? A: 策略模式由客户端外部选择算法,策略互不知道对方。状态模式的状态对象知道其他状态,转换由内部自动完成。策略是"做什么"的选择,状态是"处于什么阶段"的管理。
Q12: 观察者模式和发布-订阅的区别? A: 经典观察者模式中Subject直接通知Observer(紧耦合)。发布-订阅引入了消息中间件/事件总线,发布者和订阅者完全解耦。Redis Pub/Sub、Kafka就是发布-订阅。
Q13: 命令模式如何实现撤销? A: 每个命令对象实现 execute() 和 undo() 方法。执行时压入历史栈,撤销时弹出栈顶调用 undo(),重做时从重做栈弹出调用 execute()。
Q14: 模板方法和策略模式怎么选? A: 如果算法骨架固定只有部分步骤变化→模板方法(继承)。如果整个算法可替换→策略模式(组合)。一般优先策略模式,因为组合比继承灵活。
Q15: 责任链模式在Web开发中的应用? A: Django/Flask的中间件就是责任链。请求经过:日志→CORS→限流→认证→路由。每个中间件可以处理请求、修改请求/响应、或终止传递。
综合¶
Q16: 你在项目中用过哪些设计模式? A: (用STAR-P框架回答)以策略模式为例:需求是电商系统支持多种促销规则→每增加促销需改核心代码→将每种促销封装为策略类,通过工厂创建→新增促销只加一个类+配置,符合开闭原则。
Q17: 如何避免设计模式的过度使用? A: "三次法则"——问题出现三次再引入模式。优先选择最简单的方案,当代码出现"坏味道"(如重复分支、频繁修改)时才考虑模式。设计模式不是目的,而是解决问题的工具。
Q18: MVC是设计模式吗? A: MVC是架构模式,不是GoF设计模式,但内部用到了多种设计模式:观察者(Model通知View更新)、策略(Controller选择不同的处理方式)、组合(View组件树)。
Q19: 设计模式和设计原则的关系? A: 设计原则是"道",设计模式是"术"。SOLID是高层指导思想,设计模式是这些原则在特定场景下的具体实现方案。比如策略模式体现了开闭原则和依赖倒置。
Q20: 如何学习和掌握设计模式? A: (1)先理解SOLID原则 (2)学习高频5-8种模式 (3)阅读开源框架源码看模式如何应用 (4)在项目中重构时实践 (5)不要背概念,关注每个模式解决的核心问题。
7. 练习与自我检查¶
✏️ 练习题¶
- 电商系统:基于本章的电商案例,添加以下功能:
- 新增一种促销策略"限时折扣"(指定时间范围内打折)
- 新增"ApplePay"支付方式(通过工厂注册)
-
添加一个事件监听器:下单后给用户推送APP内通知
-
框架分析:阅读你常用的Web框架(Django/Flask/Spring)源码,找出至少5种设计模式的使用,写出文件位置和模式名称。
-
重构练习:将以下代码用合适的设计模式重构:
-
面试模拟:选择3个你最熟悉的设计模式,用STAR-P框架各准备一段面试回答。
-
迷你项目:用至少3种设计模式实现一个简易的任务管理器(支持添加/删除任务、撤销操作、任务状态流转、不同优先级处理策略)。
自我检查清单¶
- 能在实际场景中识别"用哪个模式"
- 理解策略+工厂+观察者的组合威力
- 能指出常用Web框架中的设计模式
- 知道什么时候不该用设计模式
- 能用STAR-P框架回答"项目中的设计模式"面试题
- 完成至少3道练习题
🎓 课程总结¶
恭喜你完成了设计模式的全部学习!回顾整个学习路线:
核心收获: 1. 设计模式的本质是面向接口编程 + 组合优于继承 + 开闭原则 2. 不需要记住所有23种模式,掌握高频8种即可覆盖95%的场景 3. 最好的学习方式是在重构中自然引入,而不是强行套用
推荐下一步: 阅读《Head First设计模式》深入理解 → 阅读你使用的框架源码 → 在实际项目中实践