C++設計模式-裝飾模式:從基本介紹,內部原理、應用場景、使用方法,常見問題和解決方案進行深度解析

一、裝飾模式基本介紹

裝飾模式(Decorator Pattern)是一種結構型設計模式,允許你在不改變對象自身的基礎上,動態地給一個對象添加額外的職責。這種模式創建了一個裝飾類,用來包裝原有的類,并在保持類方法簽名完整性的前提下,提供了額外的功能。其核心思想是動態地為對象添加額外職責,且不改變原有類的結構。它通過組合而非繼承實現功能擴展,解決了繼承體系中因功能疊加導致的子類爆炸問題。

1.1 模式誕生背景

假設需要為手機添加掛件、貼膜等功能,若采用繼承方式會產生大量組合子類(如iPhoneWithCase、NokiaWithSticker等)。裝飾模式通過將功能模塊化,允許運行時動態組合,使系統更靈活。

1.2 核心特點

動態擴展:運行時添加或刪除功能;
避免繼承缺陷:減少子類數量(N+M代替N*M);
遵循開放-封閉原則:擴展開放,修改封閉;

二、內部原理與結構解析

2.1 模式內部角色劃分

  • Component(抽象組件):定義一個抽象接口,規定了被裝飾對象和裝飾器對象的共同行為;
  • ConcreteComponent(具體組件):實現抽象組件接口的基礎功能,是被裝飾的具體對象;
  • Decorator(抽象裝飾器):繼承Component,持有一個抽象組件Component對象的引用,并實現抽象組件接口,其目的是為具體裝飾器提供統一的接口;
  • ConcreteDecorator(具體裝飾器):繼承自抽象裝飾器,負責給具體組件添加額外的職責,以及具體的功能;

2.2 關鍵實現原理

// 抽象組件:手機接口 
class Phone {
public:virtual void show() = 0;virtual ~Phone() {}
};// 具體組件:iPhone基礎款 
class iPhone : public Phone {
public:void show() override { cout << "基礎版iPhone" << endl; }
};// 抽象裝飾器 
class PhoneDecorator : public Phone {
protected:Phone* phone;  // 核心:持有組件對象 
public:PhoneDecorator(Phone* p) : phone(p) {}void show() override { phone->show(); }
};// 具體裝飾器:手機殼裝飾 
class CaseDecorator : public PhoneDecorator {
public:CaseDecorator(Phone* p) : PhoneDecorator(p) {}void show() override {PhoneDecorator::show();addCase();}
private:void addCase() { cout << " + 透明手機殼" << endl; }
};

三、典型應用場景

3.1 動態功能擴展

當你需要在運行時為對象動態添加功能,而不是在編譯時就確定對象的功能時,可以使用裝飾模式。例如,在圖形界面編程中,為一個窗口動態添加滾動條、標題欄等功能。

  • UI控件增強:為按鈕添加邊框、陰影等視覺效果;
  • 流處理系統:對數據流動態添加加密、壓縮等功能;

3.2 多層功能疊加

當多個對象需要共享一些功能時,可以將這些功能封裝成裝飾器,多個對象可以使用這些裝飾器來獲得相同的功能。例如,在一個日志系統中,多個類可能需要記錄日志,將日志記錄功能封裝成裝飾器,多個類可以使用這個裝飾器來實現日志記錄功能。

  • 日志系統:基礎日志輸出 + 時間戳 + 線程ID標記;
  • 支付系統擴展:基礎支付流程 + 風控校驗 + 優惠券抵扣;

3.3 替代多層繼承

如果使用繼承來擴展對象的功能,可能會導致子類數量過多,形成子類爆炸的問題。裝飾模式可以通過組合的方式來擴展對象的功能,避免了子類的大量創建。例如,在一個游戲中,角色有不同的技能和裝備,如果使用繼承來實現不同技能和裝備的組合,會產生大量的子類;而使用裝飾模式,可以在運行時動態為角色添加技能和裝備。

  • 跨平臺文本渲染:基礎文本渲染 + 字體特效 + 多語言支持;
  • 游戲角色裝備:基礎角色屬性 + 武器/護甲加成;

四、使用方法與實現步驟

4.1 標準實現流程(以游戲武器系統為例)

步驟1:定義抽象組件

class Weapon {
public:virtual int getDamage() = 0;virtual ~Weapon() {}
};

步驟2:實現具體組件

class Sword : public Weapon {
public:int getDamage() override { return 50; }
};

步驟3:定義抽象裝飾器

class WeaponDecorator : public Weapon {
protected:Weapon* weapon;
public:WeaponDecorator(Weapon* w) : weapon(w) {}int getDamage() override { return weapon->getDamage(); }
};

步驟4:實現具體裝飾器

// 火焰附魔裝飾 
class FireEnchant : public WeaponDecorator {
public:FireEnchant(Weapon* w) : WeaponDecorator(w) {}int getDamage() override {return weapon->getDamage() + 20;}
};// 鋒利的寶石裝飾 
class SharpGem : public WeaponDecorator {
public:SharpGem(Weapon* w) : WeaponDecorator(w) {}int getDamage() override {return weapon->getDamage() + 15;}
};

步驟5:客戶端組合使用

Weapon* sword = new Sword();
sword = new FireEnchant(sword);  // 疊加火焰附魔 
sword = new SharpGem(sword);     // 再疊加鋒利寶石 cout << "總攻擊力:" << sword->getDamage();  // 輸出 

五、常見問題與解決方案

5.1 典型誤區

問題類型錯誤表現解決方案
裝飾順序錯誤功能疊加順序不對影響結果出錯使用建造者模式來管理裝飾順序
內存泄漏未正確的釋放裝飾鏈對象采用智能指針(如unique_ptr)來管理資源
過度裝飾裝飾層級過多導致性能下降限制裝飾層數或使用緩存機制

5.2 性能優化策略

  • 對象池技術:復用頻繁創建的裝飾器對象;
  • 延遲初始化:僅在需要時創建裝飾器;
  • 裝飾器合并:將多個常組合的裝飾器合并為復合裝飾器;

六、總結與模式對比

6.1 核心優勢

  • 靈活擴展:運行時動態增減功能(如游戲道具在取用時實時生效);
  • 代碼復用:裝飾器可跨多個組件使用(如邊框裝飾器適用于按鈕/輸入框);
  • 符合SOLID原則:單一職責(每個裝飾器只做一件事),以及開閉原則;

6.2 模式對比

模式使用目的實現方式
裝飾模式能夠動態的添加職責組合 + 繼承
策略模式方便進行算法替換接口 + 實現類
適配器模式主要為了接口轉換包裝舊接口
橋接模式分離抽象與實現的耦合分層抽象

6.3 適用性建議

推薦使用場景

  • 需要動態擴展對象功能的系統;
  • 無法通過繼承實現的功能組合;
  • 需要撤銷臨時功能的場景;

不適用場景

  • 功能固定的簡單對象不用使用裝飾模式;
  • 裝飾模式會導致對象接口膨脹,接口比較多的不太合適;

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/73575.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/73575.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/73575.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

2、學習Docker前置操作

docker三件套&#xff1a;鏡像、容器、倉庫 Docker hubhub.docker.com ubuntu安裝【待更新】 CentOS安裝 CentOS 僅發行版本中的內核支持 Docker。Docker 運行在 CentOS 7 (64-bit)上&#xff0c;要求系統為 64 位、Linux 系統內核版本為 3.8 以上&#xff0c;這里選用 Cen…

70. Linux驅動開發與裸機開發區別,字符設備驅動開發

一、裸機驅動開發回顧 1、底層&#xff0c;跟寄存器打交道&#xff0c;有些MCU提供了庫。 二、Linux驅動開發思維 1、Linux下驅動開發直接操作寄存器不現實。 2、根據Linux下的各種驅動框架進行開發。一定要滿足框架&#xff0c;也就是Linux下各種驅動框架的掌握。 3、驅動最…

【JavaScript 簡明入門教程】為了Screeps服務的純JS入門教程

0 前言 0-1 Screeps: World 眾所不周知&#xff0c;?Screeps: World是一款面向編程愛好者的開源大型多人在線即時戰略&#xff08;MMORTS&#xff09;沙盒游戲&#xff0c;其核心機制是通過編寫JavaScript代碼來控制游戲中的單位&#xff08;稱為“Creep”&#xff09;&#…

第12章:優化并發_《C++性能優化指南》notes

優化并發 一、并發基礎與優化核心知識點二、關鍵代碼示例與測試三、關鍵優化策略總結四、性能測試方法論多選題設計題答案與詳解多選題答案&#xff1a; 設計題答案示例 一、并發基礎與優化核心知識點 線程 vs 異步任務 核心區別&#xff1a;std::thread直接管理線程&#xf…

[C++面試] RAII資源獲取即初始化(重點)

一、入門 1、什么是 RAII&#xff1f; RAII&#xff08;Resource Acquisition Is Initialization&#xff0c;資源獲取即初始化&#xff09;是 C 的核心編程范式&#xff0c;核心思想是 ?將資源的生命周期與對象的生命周期綁定&#xff1a; ?資源獲取&#xff1a;在對象構造…

Unity粒子系統

目錄 一、界面參數介紹1.主模塊2.Emission 模塊3.Shape 模塊4.Velocity over Lifetime 模塊5.Noise 模塊6.Limit Velocity Over Lifetime 模塊7.Inherit Velocity 模塊8.Force Over Lifetime 模塊9.Color Over Lifetime 模塊10.Color By Speed 模塊11.Size over Lifetime 模塊1…

Docker-清理容器空間prune

docker system prune -a 是一個非常有用的命令&#xff0c;用于清理 Docker 系統中未使用的資源&#xff0c;包括停止的容器、未使用的網絡、卷以及未被任何容器引用的鏡像&#xff08;懸空鏡像和所有未使用的鏡像&#xff09;。以下是關于該命令的詳細說明&#xff1a; 命令格…

LabVIEW遠程控制通訊接口

abVIEW提供了多種遠程控制與通訊接口&#xff0c;適用于不同場景下的設備交互、數據傳輸和系統集成。這些接口涵蓋從基礎的網絡協議&#xff08;如TCP/IP、UDP&#xff09;到專用技術&#xff08;如DataSocket、遠程面板&#xff09;&#xff0c;以及工業標準協議&#xff08;如…

LeetCode hot 100—尋找重復數

題目 給定一個包含 n 1 個整數的數組 nums &#xff0c;其數字都在 [1, n] 范圍內&#xff08;包括 1 和 n&#xff09;&#xff0c;可知至少存在一個重復的整數。 假設 nums 只有 一個重復的整數 &#xff0c;返回 這個重復的數 。 你設計的解決方案必須 不修改 數組 nums…

linux - centos7 部署 redis6.0.5

事先說明 本篇文章只解決在部署redis中出現的問題&#xff0c;并沒有部署redis的全過程&#xff0c;詳細部署過程可以參考Linux安裝部署Redis(超級詳細) - 長沙大鵬 - 博客園 執行 make 命令時報錯 原因&#xff1a;是因為gcc版本太低 升級gcc版本時 出現沒有可用軟件包 devt…

31天Python入門——第15天:日志記錄

你好&#xff0c;我是安然無虞。 文章目錄 日志記錄python的日志記錄模塊創建日志處理程序并配置輸出格式將日志內容輸出到控制臺將日志寫入到文件 logging更簡單的一種使用方式 日志記錄 日志記錄是一種重要的應用程序開發和維護技術, 它用于記錄應用程序運行時的關鍵信息和…

AI Agent開發大全第八課-Stable Diffusion 3的本地安裝全步驟

前言 就像我們前面幾課所述,本系列是一門體系化的教學,它不像網上很多個別存在的單篇博客走“吃快餐”模式,而是從扎實的基礎來帶領大家一步步邁向AI開發高手。所以我們的AI課程設置是相當全面的,除了有牢固的基礎知識外還有外面互聯網上也搜不到的生產級實戰。 前面講過…

用selenium+ChromeDriver豆瓣電影 肖申克的救贖 短評爬取(pycharm 爬蟲)

一、豆瓣電影 肖申克的救贖 短評urlhttps://movie.douban.com/subject/1292052/comments 二、基本知識點講解 1. Selenium 的基本使用 Selenium 是一個用于自動化瀏覽器操作的庫&#xff0c;常用于網頁測試和爬蟲。代碼中使用了以下 Selenium 的核心功能&#xff1a; webdriv…

開源在線客服系統源碼-前端源碼加載邏輯

客服源碼是使用Golang(又稱Go)開發的&#xff0c;Go是Google公司開發的一種靜態強類型、編譯型、并發型&#xff0c;并具有垃圾回收功能的編程語言。Go 天生支持并發。好處太多就不多說了。 全源碼客服系統用戶&#xff0c;想要針對自己的業務&#xff0c;進行二次開發&#xf…

Oracle數據庫服務器地址變更與監聽配置修改完整指南

一、前言 在企業IT運維中&#xff0c;Oracle數據庫服務器地址變更是常見的運維操作。本文將詳細介紹如何安全、高效地完成Oracle數據庫服務器地址變更及相關的監聽配置修改工作&#xff0c;確保數據庫服務在遷移后能夠正常運行。 二、準備工作 1. 環境檢查 確認新舊服務器I…

g對象在flask中主要是用來實現什么

在Flask中&#xff0c;g對象&#xff08;全稱flask.g&#xff09;是一個線程局部&#xff08;thread-local&#xff09;的臨時存儲對象&#xff0c;主要用于在單個請求的上下文&#xff08;request context&#xff09;中共享數據。它的核心作用是為同一請求的不同處理階段&…

工具介紹《WireShark》

Wireshark 過濾命令中符號含義詳解 一、比較運算符 Wireshark 支持兩種比較運算符語法&#xff1a;英文縮寫&#xff08;如 eq&#xff09;和 C語言風格符號&#xff08;如 &#xff09;&#xff0c;兩者功能等價。 符號&#xff08;英文縮寫&#xff09;C語言風格符號含義示…

JavaScrip-模版字符串的詳解

1.模版字符串的詳解 1.1 模版字符串的使用方法 在ES6之前&#xff0c;如果我們想要將字符串和一些動態的變量&#xff08;標識符&#xff09;拼接到一起&#xff0c;是非常丑陋的&#xff08;ugly) ES6允許我們使用模版字符串來嵌入變量或者表達式來進行拼接 首先&#xff0c;…

STM32C011 進入停止模式和待機模式

對于STM32C011J4M3微控制器&#xff0c;你可以使用HAL庫來實現進入停止模式&#xff08;Stop Mode&#xff09;和待機模式&#xff08;Standby Mode&#xff09;。下面是進入停止模式和待機模式的示例代碼&#xff1a; 進入停止模式代碼示例&#xff1a; #include "stm3…

海康設備http監聽接收報警事件數據

http監聽接收報警事件數據 海康獲取設備報警事件數據兩種方式&#xff1a; 1、sdk 布防監聽報警事件數據&#xff08;前面文章有示例&#xff09; 2、http監聽接收報警事件數據 http監聽接收報警事件數據&#xff0c;服務端可以使用netty通過端口來監聽獲取事件數據。 WEB 端…