🐍 Python Day89:设计模式 — 写出优雅的代码
🕐 预计用时:3-4 小时 | 🎯 目标:掌握单例、工厂、观察者、策略四种经典设计模式
📖 今日目录
1. 什么是设计模式?
设计模式是前辈程序员总结出来的"套路"——面对特定问题时,怎么写代码最优雅、最可维护。
打个比方:你装修房子,不用每次从零开始设计,而是参考"客厅布局模板""厨房收纳方案"。设计模式就是代码世界的装修模板。
💡 设计模式不是银弹。不要为了用模式而用模式。先有问题,再找模式。Python 本身很多特性(装饰器、鸭子类型)已经天然融入了设计模式的思想。
2. 单例模式 (Singleton)
2.1 什么时候用?
有些东西整个程序只需要一个:数据库连接池、日志记录器、配置管理器。单例模式保证一个类永远只有一个实例。
2.2 实现方式
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
print("创建新实例")
cls._instance = super().__new__(cls)
else:
print("返回已有实例")
return cls._instance
# 测试
a = Singleton()
b = Singleton()
print(a is b) # True — 同一个对象!
创建新实例
返回已有实例
True
2.3 用装饰器实现单例
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
def __init__(self, url):
self.url = url
print(f"连接数据库: {url}")
db1 = Database("mysql://localhost/mydb")
db2 = Database("mysql://localhost/mydb")
print(db1 is db2) # True
2.4 实际应用:配置管理器
import json
class ConfigManager:
_instance = None
def __new__(cls, config_file=None):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._config = {}
if config_file:
with open(config_file, 'r') as f:
cls._instance._config = json.load(f)
return cls._instance
def get(self, key, default=None):
return self._config.get(key, default)
def set(self, key, value):
self._config[key] = value
# 任何地方获取的都是同一个配置
config = ConfigManager()
config.set('db_host', 'localhost')
config.set('db_port', 3306)
# 另一个模块也能拿到同一个实例
same_config = ConfigManager()
print(same_config.get('db_host')) # localhost
print(same_config is config) # True
🎭 Python 程序员更常用:直接用模块级别的变量代替单例。Python 模块天然就是单例——import config 只会执行一次。
3. 工厂模式 (Factory)
3.1 什么时候用?
当你需要根据不同条件创建不同对象时。比如:根据文件类型创建不同的解析器,根据用户角色创建不同的权限。
3.2 简单工厂
class Dog:
def speak(self):
return "汪汪!"
class Cat:
def speak(self):
return "喵喵~"
class Duck:
def speak(self):
return "嘎嘎!"
class AnimalFactory:
"""简单工厂:根据类型创建动物"""
@staticmethod
def create(animal_type):
animals = {
'dog': Dog,
'cat': Cat,
'duck': Duck,
}
if animal_type not in animals:
raise ValueError(f"未知动物: {animal_type}")
return animals[animal_type]()
# 使用
factory = AnimalFactory()
for t in ['dog', 'cat', 'duck']:
animal = factory.create(t)
print(f"{t}: {animal.speak()}")
dog: 汪汪!
cat: 喵喵~
duck: 嘎嘎!
3.3 工厂方法
from abc import ABC, abstractmethod
class Notification(ABC):
@abstractmethod
def send(self, message):
pass
class EmailNotification(Notification):
def send(self, message):
print(f"📧 发送邮件: {message}")
class SMSNotification(Notification):
def send(self, message):
print(f"📱 发送短信: {message}")
class PushNotification(Notification):
def send(self, message):
print(f"🔔 推送通知: {message}")
# 工厂方法:每种通知有自己的工厂
class NotificationFactory(ABC):
@abstractmethod
def create(self) -> Notification:
pass
class EmailFactory(NotificationFactory):
def create(self) -> Notification:
return EmailNotification()
class SMSFactory(NotificationFactory):
def create(self) -> Notification:
return SMSNotification()
class PushFactory(NotificationFactory):
def create(self) -> Notification:
return PushNotification()
# 使用
factories = [EmailFactory(), SMSFactory(), PushFactory()]
for factory in factories:
notification = factory.create()
notification.send("您的订单已发货!")
3.4 实际应用:日志工厂
import logging
class LoggerFactory:
"""根据环境创建不同的 logger"""
@staticmethod
def create(name, level='INFO'):
logger = logging.getLogger(name)
logger.setLevel(getattr(logging, level))
if not logger.handlers:
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
# 使用
db_logger = LoggerFactory.create('database', 'DEBUG')
app_logger = LoggerFactory.create('app', 'INFO')
db_logger.debug("SQL query executed") # 会显示
app_logger.debug("This won't show") # 不会显示(级别不够)
app_logger.info("App started") # 会显示
4. 观察者模式 (Observer)
4.1 什么时候用?
当一个对象的状态变化时,需要自动通知其他对象。比如:
4.2 实现
class EventEmitter:
"""事件发射器(观察者模式)"""
def __init__(self):
self._listeners = {}
def on(self, event, callback):
"""订阅事件"""
if event not in self._listeners:
self._listeners[event] = []
self._listeners[event].append(callback)
def off(self, event, callback):
"""取消订阅"""
if event in self._listeners:
self._listeners[event].remove(callback)
def emit(self, event, *args, **kwargs):
"""触发事件,通知所有订阅者"""
if event in self._listeners:
for callback in self._listeners[event]:
callback(*args, **kwargs)
# 使用
emitter = EventEmitter()
# 订阅者
def on_new_video(title):
print(f"📺 新视频: {title}")
def on_live(title):
print(f"🔴 直播中: {title}")
# 订阅
emitter.on('video', on_new_video)
emitter.on('live', on_live)
# 触发事件
emitter.emit('video', 'Python Day89 设计模式')
emitter.emit('live', '深夜编程直播间')
📺 新视频: Python Day89 设计模式
🔴 直播中: 深夜编程直播间
4.3 实际应用:数据监控
class Stock:
"""股票(被观察者)"""
def __init__(self, name, price):
self.name = name
self._price = price
self._emitter = EventEmitter()
@property
def price(self):
return self._price
@price.setter
def price(self, new_price):
old_price = self._price
self._price = new_price
change = (new_price - old_price) / old_price * 100
if abs(change) >= 2: # 涨跌超过2%才通知
self._emitter.emit('price_change',
self.name, old_price, new_price, change)
def on_change(self, callback):
self._emitter.on('price_change', callback)
# 创建股票
tesla = Stock('TSLA', 1000)
# 订阅者
def alert(name, old, new, change):
direction = "📈 涨" if change > 0 else "📉 跌"
print(f" {direction} {name}: {old:.2f} → {new:.2f} ({change:+.1f}%)")
tesla.on_change(alert)
# 模拟价格变动
tesla.price = 1025 # +2.5%,触发通知
tesla.price = 1020 # -0.5%,不触发
tesla.price = 980 # -3.9%,触发通知
5. 策略模式 (Strategy)
5.1 什么时候用?
当你有多种算法可以互换时。比如:不同的排序方式、不同的支付方式、不同的折扣策略。
5.2 实现
from abc import ABC, abstractmethod
class DiscountStrategy(ABC):
"""折扣策略接口"""
@abstractmethod
def calculate(self, price):
pass
class NoDiscount(DiscountStrategy):
"""不打折"""
def calculate(self, price):
return price
class PercentageDiscount(DiscountStrategy):
"""百分比折扣"""
def __init__(self, percent):
self.percent = percent
def calculate(self, price):
return price * (1 - self.percent / 100)
class FixedDiscount(DiscountStrategy):
"""固定金额折扣"""
def __init__(self, amount):
self.amount = amount
def calculate(self, price):
return max(0, price - self.amount)
class ShoppingCart:
"""购物车"""
def __init__(self):
self.items = []
self._discount = NoDiscount()
def add_item(self, name, price):
self.items.append({'name': name, 'price': price})
def set_discount(self, strategy: DiscountStrategy):
self._discount = strategy
def total(self):
raw = sum(item['price'] for item in self.items)
return self._discount.calculate(raw)
def show(self):
print("购物车:")
for item in self.items:
print(f" {item['name']}: ¥{item['price']:.2f}")
raw = sum(item['price'] for item in self.items)
final = self.total()
print(f" 原价: ¥{raw:.2f}")
print(f" 折后: ¥{final:.2f} (策略: {self._discount.__class__.__name__})")
# 使用
cart = ShoppingCart()
cart.add_item('Python书', 99)
cart.add_item('键盘', 299)
cart.add_item('显示器', 1299)
# 不打折
cart.show()
# 打8折
print("\n--- 打8折 ---")
cart.set_discount(PercentageDiscount(20))
cart.show()
# 固定减100元
print("\n--- 固定减100元 ---")
cart.set_discount(FixedDiscount(100))
cart.show()
5.3 Python 的"鸭子策略"
# Python 中,函数就是一等公民,策略模式可以更简洁
def no_discount(price):
return price
def percent_off_20(price):
return price * 0.8
def fixed_100_off(price):
return max(0, price - 100)
class Cart:
def __init__(self, discount_func=no_discount):
self.items = []
self.discount = discount_func
def add(self, name, price):
self.items.append((name, price))
def total(self):
raw = sum(p for _, p in self.items)
return self.discount(raw)
# 直接传函数作为策略
cart = Cart(discount_func=percent_off_20)
cart.add('Python书', 99)
cart.add('键盘', 299)
print(f"折后总价: ¥{cart.total():.2f}")
🎯 Python 哲学:Python 鼓励用函数和鸭子类型代替复杂的类继承。策略模式在 Python 中通常只需要一个函数参数就够了,不需要定义一堆 Strategy 类。
6. 装饰器模式(回顾)
Day34/35 学过的装饰器,其实就是装饰器模式的 Python 原生实现:
import time
from functools import wraps
def timer(func):
"""计时装饰器"""
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f"⏱ {func.__name__} 耗时: {elapsed:.4f}s")
return result
return wrapper
def retry(max_retries=3):
"""重试装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for i in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"❌ 第{i+1}次失败: {e}")
if i == max_retries - 1:
raise
return wrapper
return decorator
# 使用
@timer
@retry(max_retries=3)
def fetch_data(url):
import random
if random.random() < 0.7:
raise ConnectionError("网络超时")
return {"status": "ok"}
try:
result = fetch_data("https://api.example.com")
except:
print("所有重试均失败")
7. 今日练习
练习 1:数据库连接池(单例)
实现一个数据库连接池,保证全局只有一个实例,支持 get_connection() 和 release_connection() 方法。
练习 2:文件解析工厂
实现一个工厂,根据文件扩展名(.csv, .json, .txt)创建不同的解析器。
练习 3:事件系统(观察者)
实现一个简单的事件系统,支持 on、off、emit,并用它实现一个简单的聊天室。
8. 今日小结
🎯 一句话总结:设计模式是"代码套路"——单例保证唯一,工厂按需创建,观察者自动通知,策略灵活切换。Python 的函数式特性让这些模式写起来更简洁。
🔮 明天预告:Day90 我们学习代码质量——单元测试(unittest/pytest)、代码规范(flake8/black)、类型注解。写代码不仅要能跑,还要跑得稳、写得美!