01、Python - 设计模式介绍

01、Python - 设计模式介绍:从烂代码到企业级架构的蜕变之路

本文为「Python设计模式」系列第一篇,带你从零理解设计模式的本质,掌握六大核心原则,并通过企业级实战代码学会常用模式的落地用法。全文无废话,小白也能直接上手。


文章目录

一、痛点场景:你的代码是不是也变成了"屎山"?

先讲一个真实的故事。

我曾经接手过一个电商项目的支付模块,打开代码的那一刻,我整个人都麻了。一个 pay.py 文件里写了三千多行代码,if-elif-else 嵌套了八层,支付宝、微信、银联、花呗、余额、优惠券的逻辑全部揉在一起。新增一个支付渠道?不好意思,你得在八个地方加判断,改完之后测试跑了三天,还是漏了一个边界条件,线上直接炸了。

这不是个例。几乎每个程序员在职业生涯中都会遇到以下场景:

  • 场景一 :项目里到处都是 if type == "A" ... elif type == "B" ...,每加一种类型就要改半个项目的代码,改完还不知道哪里会崩。
  • 场景二 :数据库连接被创建了十几次,内存占用飙升,排查半天发现每个模块都自己 new 了一个连接对象。
  • 场景三 :用户下单后要发短信、发邮件、扣库存、加积分,四个逻辑全写在 order.py 里,哪天要加个推送通知,又得动核心订单代码。
  • 场景四:接手别人的代码,一个类干了十件事,改一个功能牵一发而动全身,测试覆盖率为零,根本不敢动。

这些问题的本质是什么?不是你写代码不够努力,而是你缺少一套被验证过的、可复用的设计思想。 这套思想,就是设计模式。


二、是什么:设计模式到底是什么?

2.1 专业解释

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它与编程语言无关,是一种通用的解决思路,是先辈们在实践中总结出的精华,综合考虑了封装性、复用性、效率性、可修改性、可一致性等各种因素的高度抽象。

简单来说,设计模式不是框架,不是库,更不是什么高深的算法。它是解决特定场景下软件设计问题的可复用方案

2.2 大白话

如果把写代码比作盖房子,设计模式就是建筑图纸。你不需要每次盖房子都从零研究"承重墙怎么放""水管怎么走",前人已经把这些最优解总结成了图纸,你照着做就行。

再打个比方,设计模式就像棋谱。下象棋的人都知道"马走日象走田",但高手还知道"当头炮马来跳""屏风马"这些固定套路。这些套路不是规则,而是无数人实战中总结出来的最优应对方式。设计模式就是编程界的棋谱。

2.3 GOF:设计模式的起源

1994年,Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为《Design Patterns - Elements of Reusable Object-Oriented Software》(中文译名:《设计模式 - 可复用的面向对象软件元素》)的书,该书首次系统地提出了软件开发中设计模式的概念。

四位作者合称 GOF(Gang of Four,四人帮)。他们提出的设计模式主要基于以下两个面向对象设计原则:

  • 对接口编程,而不是对实现编程
  • 优先使用对象组合,而不是继承

这两句话是整个设计模式体系的基石,后面所有模式都是围绕这两个原则展开的。记住它们,比记住23种模式的名字更重要。


三、为什么用:设计模式能给你带来什么?

很多初学者会问:"我不用设计模式,代码不也能跑吗?"

能跑,和跑得好、维护得起、扩展得了,是两回事。设计模式给你带来的价值是长期的:

维度 没有设计模式 有设计模式
可读性 一个类三千行,看了就头疼 职责清晰,看类名就知道干什么的
可维护性 改一个bug引出三个新bug 改动局部化,影响范围可控
可扩展性 加功能要改核心代码 新增类即可,不动旧代码
团队协作 每个人写法不同,互相看不懂 统一范式,新人也能快速上手
面试竞争力 只会写业务代码 能讲出架构思路,薪资上一个档次

一句话总结:设计模式不能让你的代码跑起来,但能让你的代码在三年后还能被人维护。


四、六大原则:设计模式的灵魂

设计模式不是凭空发明的,它们都遵循一套核心设计原则。理解了这六大原则,你甚至能自己推导出设计模式。下面每个原则我都会用"专业解释+大白话+生活案例"的方式讲透。

4.1 开闭原则(Open Close Principle)

专业解释:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是通过新增代码来实现扩展,达到热插拔的效果。

大白话:写好的代码就别乱动了,要加新功能?写新的去。老代码是经过测试验证的,你改它就有可能引入新bug。

生活案例:你买了一台电脑,想加个硬盘,不需要把电脑拆开重新焊接主板,直接插一根数据线就行。电脑的接口就是"对扩展开放",主板内部电路就是"对修改关闭"。

企业实战 :支付模块支持支付宝、微信,现在要加银联。正确的做法是新增一个 UnionPayClient 类,而不是在原来的 pay() 方法里加 elif

4.2 里氏代换原则(Liskov Substitution Principle)

专业解释:任何基类可以出现的地方,子类一定可以出现。只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才算真正被复用。

大白话:儿子要能替老子干活。如果老子能做的事儿子做不了,那这个继承关系就是假的,是在滥用继承。

生活案例:父亲是一名司机,会开卡车。儿子继承了父亲的"司机"身份,但儿子只会开小轿车,不会开卡车。那你就不能说儿子能替换父亲,这个继承关系有问题。正确的做法是把"司机"抽象成接口,卡车司机和小轿车司机分别实现。

企业实战 :如果你有一个 Bird 基类有 fly() 方法,那 Ostrich(鸵鸟)就不能继承 Bird,因为鸵鸟不会飞。强行继承会导致调用 fly() 时出问题。

4.3 依赖倒转原则(Dependence Inversion Principle)

专业解释:针对接口编程,依赖于抽象而不依赖于具体。高层模块不应该依赖低层模块,二者都应该依赖其抽象。

大白话:别直接依赖具体的实现类,要依赖接口。就像你插充电器,依赖的是"插座"这个标准接口,而不是依赖某个特定品牌的插座。

生活案例:你家里的电器插头是国标两孔/三孔,不管你换什么牌子的插线板,只要是国标插座就能用。如果电器直接焊死在某个插线板上,换插线板就得把电器也拆了,这就是依赖具体实现的坏处。

企业实战 :业务层不应该直接依赖 MySQLDatabase 类,而应该依赖 Database 接口。这样哪天从 MySQL 切到 PostgreSQL,业务层代码一行都不用改。

4.4 接口隔离原则(Interface Segregation Principle)

专业解释:使用多个隔离的接口,比使用单个庞大的接口要好。降低类之间的耦合度,一个类不应该依赖它不需要的方法。

大白话:接口要小而专,不要大而全。你不需要的方法,就别强迫你实现。

生活案例:你去餐厅吃饭,菜单上有中餐、西餐、日料、韩餐,你只点中餐。如果餐厅要求你必须把所有菜系的菜都点一遍才能吃饭,你肯定不干。接口隔离就是让你只点你需要的菜。

企业实战 :一个 UserService 接口里同时有 login()register()deleteUser()exportUserData()。前台用户只需要 login()register(),却被迫实现了管理后台才用的 deleteUser()。正确做法是拆成 UserAuthServiceUserAdminService 两个接口。

4.5 迪米特法则(Demeter Principle)

专业解释:又称最少知道原则,一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。只和你的直接朋友通信,不和陌生人说话。

大白话:各干各的,别瞎操心别人的事。你需要什么,直接找对应的人要,别通过A找B找C绕一大圈。

生活案例:你想买一本书,直接去书店买就行。不需要先问邻居"你知道哪个书店好吗",邻居再问他的朋友,朋友再问批发商报价格。绕了三圈,信息还可能传错。

企业实战OrderService 要获取用户地址,不应该写 user.getAddress().getCity().getName() 这种链式调用(俗称"火车失事"),而应该让 User 类提供一个 getCityName() 方法,直接调用。

4.6 合成复用原则(Composite Reuse Principle)

专业解释:尽量使用合成/聚合/组合的方式复用代码,而不是使用继承。继承是增加耦合性、减少代码量的一种方式,不要随意滥用。

大白话:能用"组合"就别用"继承"。继承是白盒复用,父类的内部细节对子类可见,耦合度高;组合是黑盒复用,通过对象之间的协作完成功能,耦合度低。

生活案例:你想要一辆带导航的车。方案一:继承"车",造一个"带导航的车"的新子类。方案二:买一台车,再买一个导航仪装上去。方案二就是组合,导航仪坏了可以换,车还能继续用。方案一的话,导航坏了整车都得回厂。

企业实战 :不要为了复用日志功能就让 OrderService 继承 Logger。正确的做法是 OrderService 持有一个 Logger 对象(组合),需要打日志时调用 logger.info()


五、三大分类:23种设计模式总览

GOF提出的23种设计模式分为三大类:

5.1 创建型模式(5种)

关注"对象怎么创建",将对象的创建与使用分离。

模式 一句话说明 常用场景
单例模式(Singleton) 确保一个类只有一个实例 数据库连接池、配置管理器、日志器
工厂方法(Factory Method) 定义创建对象的接口,子类决定实例化哪个类 日志记录器、跨平台组件
抽象工厂(Abstract Factory) 创建一组相关对象,无需指定具体类 跨平台UI组件、数据库方言切换
建造者模式(Builder) 分步构建复杂对象 SQL查询构建器、配置对象、HTTP请求
原型模式(Prototype) 通过克隆创建对象 复杂对象的复制、原型注册表

5.2 结构型模式(7种)

关注"类和对象怎么组合",让结构更合理。

模式 一句话说明 常用场景
适配器模式(Adapter) 让不兼容的接口一起工作 第三方API对接、旧系统兼容
桥接模式(Bridge) 分离抽象与实现,让两者独立变化 多维度变化场景(如形状+颜色)
组合模式(Composite) 树形结构统一处理 文件系统、组织架构、菜单
装饰器模式(Decorator) 动态给对象添加职责 中间件、AOP、权限校验、日志增强
外观模式(Facade) 提供统一接口简化子系统调用 SDK封装、复杂系统的统一入口
享元模式(Flyweight) 共享细粒度对象减少内存 连接池、线程池、文字编辑器字符
代理模式(Proxy) 控制对对象的访问 缓存代理、权限代理、远程代理

5.3 行为型模式(11种)

关注"对象之间怎么通信和分配职责"。

模式 一句话说明 常用场景
责任链模式(Chain of Responsibility) 请求沿处理链传递 中间件、过滤器、审批流程
命令模式(Command) 将请求封装为对象 撤销/重做、任务队列、宏命令
解释器模式(Interpreter) 定义语言的文法解释器 规则引擎、表达式解析、SQL解析
迭代器模式(Iterator) 遍历集合不暴露内部结构 Python的for循环、生成器
中介者模式(Mediator) 用中介对象封装交互 聊天室、调度中心、MVC的Controller
备忘录模式(Memento) 保存和恢复对象状态 编辑器撤销、游戏存档、快照
观察者模式(Observer) 状态变化时通知依赖者 事件监听、发布订阅、消息通知
状态模式(State) 行为随状态改变 订单状态机、TCP连接状态、播放器
策略模式(Strategy) 封装可互换的算法族 支付方式、排序算法、优惠计算
模板方法(Template Method) 定义算法骨架,子类实现步骤 框架生命周期、数据处理流水线
访问者模式(Visitor) 不改变类的前提下增加操作 编译器AST遍历、报表统计

六、企业项目实战:五大常用模式代码演示

理论说了这么多,下面直接上代码。我挑选了企业项目中最常用的五种模式,每个都给出可直接运行的Python代码。

6.1 单例模式:数据库连接池管理

痛点场景:项目中多个模块各自创建数据库连接,导致连接数爆炸,数据库服务器连接数被打满。

解决方案:使用单例模式,确保全局只有一个连接池实例,所有模块共享。

python 复制代码
import threading
from typing import Optional


class DatabaseConnectionPool:
    """数据库连接池 - 线程安全的单例模式"""

    _instance: Optional["DatabaseConnectionPool"] = None
    _lock = threading.Lock()

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            with cls._lock:
                # 双重检查锁定,避免多线程下重复创建
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
                    cls._instance._initialized = False
        return cls._instance

    def __init__(self, max_connections: int = 10):
        if self._initialized:
            return
        self.max_connections = max_connections
        self._pool = []
        self._initialized = True
        print(f"[初始化] 连接池创建成功,最大连接数: {max_connections}")

    def get_connection(self):
        """从连接池获取连接"""
        if self._pool:
            return self._pool.pop()
        return f"connection-{id(self)}"

    def release_connection(self, conn):
        """归还连接到连接池"""
        if len(self._pool) < self.max_connections:
            self._pool.append(conn)


# 测试:无论创建多少次,都是同一个实例
if __name__ == "__main__":
    pool1 = DatabaseConnectionPool(max_connections=20)
    pool2 = DatabaseConnectionPool(max_connections=5)

    print(f"pool1 is pool2: {pool1 is pool2}")  # True
    print(f"pool1.max_connections: {pool1.max_connections}")  # 20(第一次初始化的值)

Pythonic补充:在Python中,模块本身就是天然的单例。如果不需要类的形式,直接把连接池写在模块级别即可:

python 复制代码
# db_pool.py
_max_connections = 10
_pool = []

def get_connection():
    global _pool
    if _pool:
        return _pool.pop()
    return "new-connection"

def release_connection(conn):
    global _pool
    if len(_pool) < _max_connections:
        _pool.append(conn)

其他模块 from db_pool import get_connection 即可,因为Python模块只会被导入一次。

6.2 工厂模式:多渠道支付网关

痛点场景 :支付模块写了一堆 if payment_method == "alipay",每加一个支付渠道就要改核心代码,违反开闭原则。

解决方案:使用工厂模式,将支付对象的创建与业务逻辑分离。新增支付渠道只需新增类,不动核心代码。

python 复制代码
from abc import ABC, abstractmethod
from typing import Dict, Type


# ========== 抽象产品 ==========
class PaymentClient(ABC):
    """支付客户端抽象基类"""

    @abstractmethod
    def pay(self, order_id: str, amount: float) -> Dict:
        """发起支付"""
        pass

    @abstractmethod
    def refund(self, transaction_id: str, amount: float) -> bool:
        """退款"""
        pass


# ========== 具体产品 ==========
class AlipayClient(PaymentClient):
    """支付宝支付客户端"""

    def __init__(self, app_id: str, private_key: str):
        self.app_id = app_id
        self.private_key = private_key

    def pay(self, order_id: str, amount: float) -> Dict:
        print(f"[支付宝] 订单 {order_id} 支付 ¥{amount}")
        return {"status": "success", "transaction_id": f"ALIPAY-{order_id}"}

    def refund(self, transaction_id: str, amount: float) -> bool:
        print(f"[支付宝] 交易 {transaction_id} 退款 ¥{amount}")
        return True


class WeChatPayClient(PaymentClient):
    """微信支付客户端"""

    def __init__(self, mch_id: str, api_key: str):
        self.mch_id = mch_id
        self.api_key = api_key

    def pay(self, order_id: str, amount: float) -> Dict:
        print(f"[微信支付] 订单 {order_id} 支付 ¥{amount}")
        return {"status": "success", "transaction_id": f"WXPAY-{order_id}"}

    def refund(self, transaction_id: str, amount: float) -> bool:
        print(f"[微信支付] 交易 {transaction_id} 退款 ¥{amount}")
        return True


class UnionPayClient(PaymentClient):
    """银联支付客户端"""

    def __init__(self, mer_id: str, cert_path: str):
        self.mer_id = mer_id
        self.cert_path = cert_path

    def pay(self, order_id: str, amount: float) -> Dict:
        print(f"[银联支付] 订单 {order_id} 支付 ¥{amount}")
        return {"status": "success", "transaction_id": f"UNION-{order_id}"}

    def refund(self, transaction_id: str, amount: float) -> bool:
        print(f"[银联支付] 交易 {transaction_id} 退款 ¥{amount}")
        return True


# ========== 工厂类 ==========
class PaymentFactory:
    """支付客户端工厂 - 统一管理所有支付渠道的创建"""

    _registry: Dict[str, Type[PaymentClient]] = {}

    @classmethod
    def register(cls, method: str, client_class: Type[PaymentClient]):
        """注册支付渠道(支持动态扩展)"""
        cls._registry[method] = client_class

    @classmethod
    def create(cls, method: str, config: Dict) -> PaymentClient:
        """根据支付方式创建对应的支付客户端"""
        client_class = cls._registry.get(method)
        if not client_class:
            raise ValueError(f"不支持的支付方式: {method}")
        return client_class(**config)


# ========== 注册与使用 ==========
PaymentFactory.register("alipay", AlipayClient)
PaymentFactory.register("wechat", WeChatPayClient)
PaymentFactory.register("unionpay", UnionPayClient)

if __name__ == "__main__":
    # 业务代码只依赖工厂和抽象接口
    configs = {
        "alipay": {"app_id": "app123", "private_key": "key123"},
        "wechat": {"mch_id": "mch456", "api_key": "key456"},
        "unionpay": {"mer_id": "mer789", "cert_path": "/path/to/cert"},
    }

    for method, config in configs.items():
        client = PaymentFactory.create(method, config)
        result = client.pay(f"ORDER-{method}", 99.9)
        print(f"  结果: {result}\n")

核心优势 :新增支付渠道时,只需写一个新的 XxxPayClient 类并调用 PaymentFactory.register() 注册,业务代码零改动。这就是开闭原则的完美实践。

6.3 观察者模式:订单事件通知

痛点场景 :用户下单后要发短信、发邮件、扣库存、加积分,所有逻辑全写在 order.py 里,每加一个后续操作就要改订单核心代码。

解决方案:使用观察者模式(发布-订阅),订单服务只负责发布事件,各个观察者自行处理。

python 复制代码
from abc import ABC, abstractmethod
from typing import List, Dict, Any


# ========== 观察者接口 ==========
class EventObserver(ABC):
    """事件观察者抽象接口"""

    @abstractmethod
    def update(self, event: str, data: Dict[str, Any]):
        """事件回调"""
        pass


# ========== 被观察者(事件发布者) ==========
class EventPublisher:
    """事件发布者 - 维护观察者列表并通知"""

    def __init__(self):
        self._observers: List[EventObserver] = []

    def attach(self, observer: EventObserver):
        """订阅事件"""
        self._observers.append(observer)

    def detach(self, observer: EventObserver):
        """取消订阅"""
        self._observers.remove(observer)

    def notify(self, event: str, data: Dict[str, Any]):
        """通知所有观察者"""
        for observer in self._observers:
            observer.update(event, data)


# ========== 具体观察者 ==========
class SmsNotifier(EventObserver):
    """短信通知观察者"""

    def update(self, event: str, data: Dict[str, Any]):
        if event == "order_created":
            print(f"[短信] 发送下单成功短信给用户 {data['user_id']}")


class EmailNotifier(EventObserver):
    """邮件通知观察者"""

    def update(self, event: str, data: Dict[str, Any]):
        if event == "order_created":
            print(f"[邮件] 发送订单确认邮件到 {data['email']}")


class InventoryDeductor(EventObserver):
    """库存扣减观察者"""

    def update(self, event: str, data: Dict[str, Any]):
        if event == "order_created":
            print(f"[库存] 扣减商品 {data['product_id']} 库存 {data['quantity']} 件")


class PointsAdder(EventObserver):
    """积分增加观察者"""

    def update(self, event: str, data: Dict[str, Any]):
        if event == "order_created":
            points = int(data["amount"])
            print(f"[积分] 给用户 {data['user_id']} 增加 {points} 积分")


# ========== 订单服务 ==========
class OrderService(EventPublisher):
    """订单服务 - 只负责创建订单并发布事件"""

    def create_order(self, user_id: int, product_id: int, quantity: int, amount: float, email: str):
        # 1. 创建订单(核心逻辑)
        order_id = f"ORD-{user_id}-{product_id}"
        print(f"[订单] 创建订单 {order_id},金额 ¥{amount}")

        # 2. 发布事件,观察者自行处理
        self.notify("order_created", {
            "order_id": order_id,
            "user_id": user_id,
            "product_id": product_id,
            "quantity": quantity,
            "amount": amount,
            "email": email,
        })
        return order_id


if __name__ == "__main__":
    order_service = OrderService()

    # 注册观察者(按需添加,互不影响)
    order_service.attach(SmsNotifier())
    order_service.attach(EmailNotifier())
    order_service.attach(InventoryDeductor())
    order_service.attach(PointsAdder())

    # 创建订单
    order_service.create_order(
        user_id=1001,
        product_id=2001,
        quantity=2,
        amount=199.8,
        email="user@example.com"
    )

核心优势 :订单服务完全不需要知道有哪些后续操作。要加一个"推送通知"?写一个 PushNotifier 类,attach 上去就行,订单代码一行都不用改。

6.4 策略模式:多种优惠计算

痛点场景 :促销活动有满减、折扣、优惠券、新人价等多种优惠方式,代码里全是 if promo_type == "discount",活动一多就乱成一锅粥。

解决方案:使用策略模式,将每种优惠算法封装为独立的策略类,运行时动态切换。

python 复制代码
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Dict, Type


# ========== 策略接口 ==========
class DiscountStrategy(ABC):
    """优惠策略抽象接口"""

    @abstractmethod
    def calculate(self, original_price: float, context: Dict) -> float:
        """计算优惠后的价格"""
        pass


# ========== 具体策略 ==========
class FixedDiscountStrategy(DiscountStrategy):
    """固定金额优惠(如满100减20)"""

    def calculate(self, original_price: float, context: Dict) -> float:
        threshold = context.get("threshold", 0)
        discount = context.get("discount", 0)
        if original_price >= threshold:
            return max(0, original_price - discount)
        return original_price


class PercentageDiscountStrategy(DiscountStrategy):
    """百分比折扣(如打8折)"""

    def calculate(self, original_price: float, context: Dict) -> float:
        percentage = context.get("percentage", 1.0)
        return round(original_price * percentage, 2)


class CouponStrategy(DiscountStrategy):
    """优惠券(固定面额)"""

    def calculate(self, original_price: float, context: Dict) -> float:
        coupon_amount = context.get("coupon_amount", 0)
        return max(0, original_price - coupon_amount)


class NewUserStrategy(DiscountStrategy):
    """新人专享价"""

    def calculate(self, original_price: float, context: Dict) -> float:
        new_user_price = context.get("new_user_price", original_price)
        is_new_user = context.get("is_new_user", False)
        return new_user_price if is_new_user else original_price


# ========== 策略上下文 ==========
@dataclass
class DiscountContext:
    """优惠计算上下文 - 持有具体策略并委托计算"""

    strategy: DiscountStrategy

    def set_strategy(self, strategy: DiscountStrategy):
        """运行时切换策略"""
        self.strategy = strategy

    def apply_discount(self, original_price: float, params: Dict) -> float:
        """应用优惠"""
        return self.strategy.calculate(original_price, params)


# ========== 策略工厂(可选,方便管理) ==========
class DiscountStrategyFactory:
    """优惠策略工厂"""

    _strategies: Dict[str, Type[DiscountStrategy]] = {
        "fixed": FixedDiscountStrategy,
        "percentage": PercentageDiscountStrategy,
        "coupon": CouponStrategy,
        "new_user": NewUserStrategy,
    }

    @classmethod
    def get_strategy(cls, strategy_type: str) -> DiscountStrategy:
        strategy_class = cls._strategies.get(strategy_type)
        if not strategy_class:
            raise ValueError(f"不支持的优惠类型: {strategy_type}")
        return strategy_class()


if __name__ == "__main__":
    original_price = 299.0

    # 各种优惠场景
    scenarios = [
        ("满减", "fixed", {"threshold": 200, "discount": 50}),
        ("打折", "percentage", {"percentage": 0.8}),
        ("优惠券", "coupon", {"coupon_amount": 30}),
        ("新人价", "new_user", {"new_user_price": 99, "is_new_user": True}),
    ]

    for name, strategy_type, params in scenarios:
        strategy = DiscountStrategyFactory.get_strategy(strategy_type)
        context = DiscountContext(strategy)
        final_price = context.apply_discount(original_price, params)
        saved = original_price - final_price
        print(f"[{name}] 原价 ¥{original_price} -> 实付 ¥{final_price}(省 ¥{saved:.2f})")

核心优势 :新增一种优惠方式(比如"拼团价"),只需新增一个 GroupBuyStrategy 类并注册到工厂,原有代码不受影响。每种策略独立测试,互不干扰。

6.5 装饰器模式:接口权限与日志增强

痛点场景:每个接口都要写权限校验、日志记录、异常处理的重复代码,一个接口写几十行样板代码,真正的业务逻辑只有几行。

解决方案:使用装饰器模式,将横切关注点(权限、日志、缓存)封装为装饰器,动态叠加到业务函数上。Python的装饰器语法本身就是装饰器模式的天然实现。

python 复制代码
import time
import functools
from typing import Callable, Set


# ========== 装饰器:权限校验 ==========
def require_permission(permission: str):
    """权限校验装饰器 - 检查用户是否拥有指定权限"""

    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(user: dict, *args, **kwargs):
            user_permissions: Set[str] = user.get("permissions", set())
            if permission not in user_permissions:
                raise PermissionError(f"用户缺少权限: {permission}")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator


# ========== 装饰器:日志记录 ==========
def log_execution(func: Callable) -> Callable:
    """执行日志装饰器 - 记录函数名、参数、执行时间"""

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        print(f"[日志] 调用 {func.__name__}, 参数: {args}, {kwargs}")
        try:
            result = func(*args, **kwargs)
            elapsed = (time.time() - start) * 1000
            print(f"[日志] {func.__name__} 执行成功,耗时 {elapsed:.2f}ms")
            return result
        except Exception as e:
            elapsed = (time.time() - start) * 1000
            print(f"[日志] {func.__name__} 执行失败,耗时 {elapsed:.2f}ms,错误: {e}")
            raise
    return wrapper


# ========== 装饰器:结果缓存 ==========
def cache_result(func: Callable) -> Callable:
    """结果缓存装饰器 - 简单的内存缓存"""

    cache = {}

    @functools.wraps(func)
    def wrapper(*args):
        if args in cache:
            print(f"[缓存] 命中缓存,直接返回")
            return cache[args]
        result = func(*args)
        cache[args] = result
        return result
    return wrapper


# ========== 业务函数:叠加多个装饰器 ==========
@log_execution
@require_permission("order:delete")
def delete_order(user: dict, order_id: str):
    """删除订单 - 业务逻辑只有一行"""
    print(f"[业务] 删除订单 {order_id}")
    return {"status": "deleted", "order_id": order_id}


@log_execution
@cache_result
def get_product_detail(product_id: int):
    """获取商品详情 - 模拟耗时查询"""
    print(f"[业务] 从数据库查询商品 {product_id}")
    time.sleep(0.5)  # 模拟数据库查询耗时
    return {"id": product_id, "name": f"商品-{product_id}", "price": 99.9}


if __name__ == "__main__":
    # 场景一:有权限的用户删除订单
    admin_user = {"id": 1, "name": "admin", "permissions": {"order:delete", "order:view"}}
    result = delete_order(admin_user, "ORD-001")
    print(f"  结果: {result}\n")

    # 场景二:无权限的用户删除订单(会抛异常)
    normal_user = {"id": 2, "name": "guest", "permissions": {"order:view"}}
    try:
        delete_order(normal_user, "ORD-002")
    except PermissionError as e:
        print(f"  拦截: {e}\n")

    # 场景三:缓存效果演示
    print("第一次查询:")
    get_product_detail(1001)
    print("\n第二次查询(应命中缓存):")
    get_product_detail(1001)

核心优势 :业务函数只关注业务逻辑,权限、日志、缓存全部通过装饰器叠加。需要加新功能?写个新装饰器,@ 一下就行,业务代码零侵入。


七、常用场景教学:什么时候该用什么模式?

很多人学完23种模式还是不会用,因为不知道"什么时候用"。下面给你一张速查表,按实际开发场景对应到具体模式:

业务场景 推荐模式 为什么
数据库连接池、全局配置 单例模式 全局唯一,避免资源浪费
多支付渠道、多消息推送 工厂模式 动态创建对象,开闭原则
跨平台UI组件、多数据库方言 抽象工厂 创建一组相关对象
复杂对象构建(SQL、HTTP请求) 建造者模式 分步构建,链式调用
第三方API对接、旧系统兼容 适配器模式 接口转换,不改原有代码
中间件、AOP、权限日志 装饰器模式 动态增强,零侵入
复杂系统统一入口、SDK封装 外观模式 简化调用,隐藏子系统复杂度
事件通知、发布订阅 观察者模式 解耦发布者和订阅者
多种算法/优惠/排序 策略模式 算法可互换,运行时切换
订单状态、TCP连接状态 状态模式 消除庞大的if-else状态判断
审批流程、过滤器链 责任链模式 请求沿链传递,处理者可动态组合
撤销/重做、任务队列 命令模式 请求封装为对象,支持排队和回滚
框架生命周期、数据流水线 模板方法 固定骨架,自定义步骤

重要提醒:设计模式不是银弹,不要为了用模式而用模式。如果一个场景用简单的函数就能解决,就不要硬套设计模式。过度设计比没有设计更可怕。


八、面试官高频面试题

Q1:简单工厂、工厂方法、抽象工厂有什么区别?

参考答案

  • 简单工厂:一个工厂类根据参数创建不同产品,产品新增时需要修改工厂类,违反开闭原则。适合产品种类固定的场景。
  • 工厂方法:定义创建对象的接口,让子类决定实例化哪个类。每个产品对应一个工厂子类,新增产品只需新增工厂类,符合开闭原则。
  • 抽象工厂:提供一个接口创建一组相关或相互依赖的对象(产品族),而不需要指定具体类。适合跨平台、多系列产品的场景。

一句话区分:简单工厂造一个产品,工厂方法造一种产品,抽象工厂造一族产品。

Q2:单例模式有哪些实现方式?线程安全怎么保证?

参考答案

Python中常见的单例实现方式有四种:

  1. 模块级单例:Python模块天然只导入一次,直接用模块变量即可,最简单最Pythonic。
  2. __new__ 控制 :重写 __new__ 方法,判断实例是否已存在。
  3. 元类(Metaclass):通过自定义元类控制类的实例化过程。
  4. 装饰器:用装饰器包装类,维护实例字典。

线程安全保证:使用双重检查锁定(Double-Checked Locking),在 __new__ 中先判断实例是否存在,不存在时加锁,加锁后再次判断。Python中还可以使用 threading.Lock 保证原子性。

Q3:观察者模式和发布订阅模式有什么区别?

参考答案

  • 观察者模式:被观察者(Subject)直接持有观察者列表,状态变化时直接调用观察者的方法。两者是松耦合但直接关联的。
  • 发布订阅模式:发布者和订阅者之间通过一个事件通道(Event Bus / Broker)解耦,发布者不知道订阅者的存在,订阅者也不知道发布者。两者完全解耦。

简单说:观察者模式是"你直接通知我",发布订阅是"你发到中间件,我自己去订阅"。

Q4:策略模式和工厂模式有什么区别?

参考答案

  • 工厂模式是创建型模式,关注"对象怎么创建",解决的是对象创建的耦合问题。
  • 策略模式是行为型模式,关注"行为怎么选择",解决的是算法/行为的切换问题。

两者经常配合使用:工厂负责创建策略对象,策略负责执行具体算法。比如优惠计算中,工厂根据类型创建策略实例,策略实例执行计算。

Q5:什么是开闭原则?请举例说明。

参考答案

开闭原则(Open Close Principle):对扩展开放,对修改关闭。即新增功能时应该通过新增代码来实现,而不是修改已有代码。

举例:支付模块原来支持支付宝和微信,现在要加银联。不符合开闭原则的做法是在 pay() 方法里加 elif method == "unionpay"。符合开闭原则的做法是新增 UnionPayClient 类,通过工厂注册,原有代码不动。

Q6:装饰器模式和代理模式有什么区别?

参考答案

  • 装饰器模式:动态地给对象添加额外的职责,关注"增强功能",装饰器和被装饰者实现相同接口,客户端可以透明地使用。
  • 代理模式:为其他对象提供一个代理以控制对这个对象的访问,关注"控制访问",代理对象和真实对象实现相同接口,但代理可以决定是否调用真实对象。

两者结构相似,但意图不同:装饰器是"增强",代理是"控制"。Python中的 @decorator 语法是装饰器模式的实现,而 __getattr__ 延迟加载、RPC远程调用是代理模式的典型应用。


九、总结

设计模式不是什么高深莫测的东西,它就是前人踩过无数坑之后总结出来的"最佳实践"。本文从痛点出发,讲清了设计模式的本质、六大核心原则、23种模式分类,并通过五个企业级实战代码演示了最常用的单例、工厂、观察者、策略、装饰器模式。

记住三个关键点:

  1. 先理解原则,再记忆模式。六大原则是根,23种模式是枝叶。理解了原则,你能自己推导出模式。
  2. 模式是工具,不是教条。该用的时候用,不该用的时候别硬套。简单问题简单解,复杂问题才需要模式。
  3. 在实战中学习 。看完本文,去你的项目里找一找:哪里有大段的 if-elif-else?哪里有重复的样板代码?哪里改一个功能牵一发而动全身?找到这些痛点,用对应的模式去重构,你才能真正掌握设计模式。

下一篇我们将深入讲解「普通工厂模式」,从最简单的工厂开始,一步步拆解对象创建的艺术。


转载声明:本文为原创文章,如需转载,请联系作者获得授权,并注明出处。

相关推荐
wujian83111 小时前
【腾讯元宝手机版生成的表格怎么复制下来】?试试 [ AI 导出鸭 ] 的“格式网关”
人工智能·ai·chatgpt·智能手机·ai导出鸭
zzzll11111 小时前
大模型技术原理与应用实践
java·数据库·人工智能
小此方1 小时前
Linux加餐(二):藏在Linux中的设计模式(二)单例模式与线程池
linux·单例模式·设计模式
show4331 小时前
2026微信小程序创作工具生态技术趋势:从单一功能到AI整合型平台演进
人工智能·微信小程序·小程序
聪明蛋子哟1 小时前
自动化Prompt工程与RAG的深度融合:PE2框架在问答系统中的优化实践
人工智能
Maynor9961 小时前
Claude Desktop 桌面端全流程指南(含国内直连配置)
人工智能·开源
AI英德西牛仔1 小时前
让 AI 导出回归优雅:纳米AI excel 与“AI 导出鸭”的 PC 端批量导出技术拆解
人工智能·excel·deepseek·ai导出鸭
asdfg12589631 小时前
java.util包中的ArrayList&&Collections的目的和用途
java·arraylist·collections
余额瞒着我当琳1 小时前
C++STL--list底层实现,迭代器分类,模拟list的迭代器封装、实现
java·开发语言·c++