新谈设计模式 Chapter 09 — 装饰器模式 Decorator

张开发
2026/4/10 18:45:39 15 分钟阅读

分享文章

新谈设计模式 Chapter 09 — 装饰器模式 Decorator
Chapter 09 — 装饰器模式 Decorator灵魂速记套娃——一层套一层每层加一个新功能但还是同一种东西。秒懂类比你买了一杯基础咖啡10 块。加牛奶3 块加糖1 块加奶油5 块。每次加料都是在原来的基础上包一层但它还是一杯咖啡——你喝的方式不会变。如果用继承CoffeeWithMilk、CoffeeWithMilkAndSugar、CoffeeWithMilkAndSugarAndCream……类爆炸。装饰器动态地给对象添加功能而不改变其接口。问题引入// 灾难现场通知系统classNotifier{voidsend(string msg);};// 需求变化有时要短信通知、有时要邮件短信微信// 用继承classSMSNotifier:publicNotifier{...};classEmailSMSNotifier:publicNotifier{...};classEmailSMSWechatNotifier:publicNotifier{...};// 组合爆炸3种通知方式 → 7种子类 (2³-1)模式结构┌──────────────┐ │ Component │ ← 定义接口 ├──────────────┤ │ operation() │ └──────┬───────┘ │ ┌────┴────────────────┐ │ │ ┌─┴──────────┐ ┌─────┴────────┐ │ConcreteComp│ │ Decorator │ ← 持有 Component 的引用 │ (基础咖啡) │ ├──────────────┤ └────────────┘ │ -wrapped │ │ operation() │ ← 调用 wrapped 的 operation 自己的逻辑 └──────┬───────┘ │ ┌─────────┴──────────┐ │ │ ┌─────┴──────┐ ┌──────┴─────┐ │DecoratorA │ │DecoratorB │ │ (加牛奶) │ │ (加糖) │ └────────────┘ └────────────┘关键Decorator 和 ConcreteComponent 实现同一个接口。C 实现#includeiostream#includememory#includestring// 组件接口 classCoffee{public:virtual~Coffee()default;virtualstd::stringdescription()const0;virtualdoublecost()const0;};// 基础组件 classSimpleCoffee:publicCoffee{public:std::stringdescription()constoverride{return基础咖啡;}doublecost()constoverride{return10.0;}};// 装饰器基类 classCoffeeDecorator:publicCoffee{public:explicitCoffeeDecorator(std::unique_ptrCoffeecoffee):wrapped_(std::move(coffee)){}protected:std::unique_ptrCoffeewrapped_;// 被装饰的对象};// 具体装饰器 classMilkDecorator:publicCoffeeDecorator{public:// using 继承构造函数直接复用父类 CoffeeDecorator 的构造函数// 等价于写 MilkDecorator(std::unique_ptrCoffee c) : CoffeeDecorator(std::move(c)) {}// 省了一堆重复代码。C11 引入。usingCoffeeDecorator::CoffeeDecorator;std::stringdescription()constoverride{returnwrapped_-description() 牛奶;}doublecost()constoverride{returnwrapped_-cost()3.0;}};classSugarDecorator:publicCoffeeDecorator{public:usingCoffeeDecorator::CoffeeDecorator;std::stringdescription()constoverride{returnwrapped_-description() 糖;}doublecost()constoverride{returnwrapped_-cost()1.0;}};classCreamDecorator:publicCoffeeDecorator{public:usingCoffeeDecorator::CoffeeDecorator;std::stringdescription()constoverride{returnwrapped_-description() 奶油;}doublecost()constoverride{returnwrapped_-cost()5.0;}};intmain(){// 基础咖啡std::unique_ptrCoffeecoffeestd::make_uniqueSimpleCoffee();std::coutcoffee-description() ¥coffee-cost()\n;// 一层层套上去coffeestd::make_uniqueMilkDecorator(std::move(coffee));std::coutcoffee-description() ¥coffee-cost()\n;coffeestd::make_uniqueSugarDecorator(std::move(coffee));std::coutcoffee-description() ¥coffee-cost()\n;coffeestd::make_uniqueCreamDecorator(std::move(coffee));std::coutcoffee-description() ¥coffee-cost()\n;// 一行搞定std::cout\n 一步到位 \n;autolattestd::make_uniqueCreamDecorator(std::make_uniqueMilkDecorator(std::make_uniqueSimpleCoffee()));std::coutlatte-description() ¥latte-cost()\n;}输出基础咖啡 ¥10 基础咖啡 牛奶 ¥13 基础咖啡 牛奶 糖 ¥14 基础咖啡 牛奶 糖 奶油 ¥19 一步到位 基础咖啡 牛奶 奶油 ¥18核心洞察装饰器的魔法每个装饰器接收一个 Coffee返回一个 Coffee。所以可以无限嵌套。// 不管套了多少层对外还是 Coffee*// 客户端代码完全不知道被装饰了几次voidserveCoffee(constCoffeecoffee){std::cout您的 coffee.description()共 ¥coffee.cost()\n;}实战案例IO 流C 的 iostream 就是装饰器模式的经典应用// 基础流std::ofstreamfile(log.txt);// 装饰加缓冲std::ostreambufferedfile;// iostream 内部已经做了// Java 更明显// new BufferedWriter(new FileWriter(log.txt))// ↑ BufferedWriter 装饰 FileWriter什么时候用✅ 适合❌ 别用需要动态添加/移除功能功能固定不变组合种类太多继承会爆炸只有一两种组合需要在不改原类的前提下增强可以直接修改原类多个可选功能自由搭配功能之间有强依赖顺序防混淆Decorator vs AdapterDecoratorAdapter接口不变进出同一个接口转换不同接口之间目的加功能兼容接口类比给手机贴膜加壳电源转接头Decorator vs ProxyDecoratorProxy目的增强功能控制访问嵌套可以多层嵌套通常只有一层创建客户端组装代理自己管理生命周期类比加料中介/保镖一句话分清Decorator 加东西Proxy 管权限。Decorator vs CompositeDecoratorComposite结构链一对一树一对多目的给单一对象加功能统一操作树形结构嵌套每层只包一个每个节点可包多个现代 C 小贴士如果只是简单的函数增强可以用std::function Lambda 装饰#includechrono#includefunctional#includeiostream#includestringusingHandlerstd::functionstd::string(conststd::string);// 基础处理Handler baseHandler[](conststd::stringinput){return处理: input;};// 装饰加日志autowithLogging[](Handler inner)-Handler{return[inner](conststd::stringinput){std::cout[LOG] 输入: input\n;autoresultinner(input);std::cout[LOG] 输出: result\n;returnresult;};};// 装饰加计时autowithTiming[](Handler inner)-Handler{return[inner](conststd::stringinput){autostartstd::chrono::steady_clock::now();autoresultinner(input);autoelapsedstd::chrono::steady_clock::now()-start;std::cout[TIME] std::chrono::duration_caststd::chrono::microseconds(elapsed).count()μs\n;returnresult;};};// 组合装饰autodecoratedwithTiming(withLogging(baseHandler));decorated(hello);函数式的装饰器——轻量、灵活、零类负担。 上一章 | 目录 | 下一章 外观

更多文章