🐸 青蛙编程网
首页 / Python / 设计模式 / 5. 结构型模式:装饰器与代理

5. 结构型模式:装饰器与代理

这是什么:装饰器 = 把对象一层层包起来,每次包装叠加一点新职责;代理 = 在"你"和"真对象"之间站一个中间人,访问前后加控制。都是结构型:不改原类,包出来。

为什么用:给咖啡加奶、加糖,不可能为每种组合各写一个类(奶+糖、双份奶、奶+糖+冰……组合爆炸);查天气想加缓存,又不想把缓存代码塞进天气 API 类里弄脏它。包装模式让"新职责"和"原对象"彻底分离。

如何用:先用包装链给咖啡叠加配料,再写一个缓存代理给天气 API 提速。

装饰器模式:层层包装叠职责

Milk 和 Sugar 都"包住"一杯已有的饮料:价格在原价上加,描述在原文上接——同一杯咖啡想加几层加几层:

class Coffee:
    def cost(self):
        return 10

    def desc(self):
        return "咖啡"


class Milk:
    """加奶:包一层,价钱和描述都叠加"""

    def __init__(self, drink):
        self.drink = drink          # 被包装的那杯

    def cost(self):
        return self.drink.cost() + 3

    def desc(self):
        return self.drink.desc() + "+奶"


class Sugar:
    """加糖:同样套路再包一层"""

    def __init__(self, drink):
        self.drink = drink

    def cost(self):
        return self.drink.cost() + 1

    def desc(self):
        return self.drink.desc() + "+糖"


order = Sugar(Milk(Coffee()))       # 咖啡 → 加奶 → 加糖
print(f"{order.desc()}:{order.cost()} 元")

你会看到:

咖啡+奶+糖:14 元

cost() 的调用像剥洋葱:Sugar 问 Milk,Milk 问 Coffee,一层层往里要价再各自加价。组合(奶+糖、双份奶)随便叠,不用新建任何类。

你其实早用过这个模式:Python 的 @decorator 语法就是装饰器思想的语法糖——@log、@app.route 都是在"不改原函数的前提下包一层加功能"。

代理:访问前后加控制

代理和真对象接口长得一模一样,调用方察觉不出差别,但代理在中间偷偷做事——比如缓存:

class WeatherAPI:
    """真家伙:每次查询都要走网络(这里假装)"""

    def query(self, city):
        print(f"(网络请求:查询 {city}……)")
        return f"{city} 晴 26°C"


class CachedProxy:
    """代理:一样的 query 接口,内部加了缓存"""

    def __init__(self):
        self.api = WeatherAPI()
        self.cache = {}                     # 查过的结果存这里

    def query(self, city):
        if city not in self.cache:
            self.cache[city] = self.api.query(city)   # 第一次:真的去查
        return self.cache[city]             # 之后:直接回缓存


weather = CachedProxy()
print(weather.query("北京"))
print(weather.query("北京"))
print(weather.query("上海"))

你会看到:

(网络请求:查询 北京……)
北京 晴 26°C
北京 晴 26°C
(网络请求:查询 上海……)
上海 晴 26°C

第二次查北京没再出现"网络请求"——缓存代理把重复查询挡在了真 API 之外。除了缓存,代理还能做权限检查、延迟加载、请求计数,套路都一样。

标准库里就有现成的:functools.lru_cache 一个装饰器给任何函数装上缓存代理,先试它,再考虑手写。

装饰器 vs 代理:包装的目的不同

装饰器 代理
包的目的 叠加职责(加价、加功能) 控制访问(缓存、鉴权、延迟)
层数 常多层嵌套 通常一层
调用方感知 知道在加料 感觉不到中间人存在

本章解读

代码 作用
self.drink = drink 包装的核心:持有被包对象(组合)
Sugar(Milk(Coffee())) 从里往外包,从外往里调
CachedProxy 与 WeatherAPI 同名同参的 query 接口一致,调用方无感切换
if city not in self.cache 代理的典型逻辑:先查旁路,不命中才放行

记忆口诀

装饰叠职责,一层加一点;代理管通行,缓存放中间。包装不改动,组合胜继承。

动手改

  1. 写一个 Ice 类(加冰 +2 元、+冰),链出 Ice(Ice(Milk(Coffee()))) 双份冰,算算多少钱
  2. 把 Milk 和 Sugar 合并成一个 Addon(name, price, drink) 通用配料类
  3. 给 CachedProxy 加一句:缓存命中时打印"(缓存命中)",重跑验证

下一章:行为型模式:策略与观察者 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。