当前位置:首页>python>Python 零基础100天—Day89 设计模式

Python 零基础100天—Day89 设计模式

  • 2026-09-07 10:26:11
Python 零基础100天—Day89 设计模式

🐍 Python Day89:设计模式 — 写出优雅的代码

🕐 预计用时:3-4 小时 | 🎯 目标:掌握单例、工厂、观察者、策略四种经典设计模式


📖 今日目录

  1. 什么是设计模式?
  2. 单例模式
  3. 工厂模式
  4. 观察者模式
  5. 策略模式
  6. 装饰器模式(回顾)
  7. 今日练习
  8. 今日小结

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 什么时候用?

当一个对象的状态变化时,需要自动通知其他对象。比如:

  • 你关注了一个UP主,他发视频时你收到通知
  • 股票价格变化,所有订阅者收到提醒
  • UI 中按钮被点击,触发对应的处理函数

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 实现
典型应用
单例
全局唯一实例
__new__
 或装饰器
配置管理、连接池
工厂
根据条件创建对象
字典映射 + 工厂方法
解析器、通知系统
观察者
状态变化自动通知
事件发射器
UI事件、消息推送
策略
算法可互换
函数参数
折扣、排序、支付
装饰器
增强功能不改原代码
@decorator
计时、日志、权限

🎯 一句话总结:设计模式是"代码套路"——单例保证唯一,工厂按需创建,观察者自动通知,策略灵活切换。Python 的函数式特性让这些模式写起来更简洁。

🔮 明天预告:Day90 我们学习代码质量——单元测试(unittest/pytest)、代码规范(flake8/black)、类型注解。写代码不仅要能跑,还要跑得稳、写得美!

最新文章

随机文章