設計模式:享元模式(Flyweight Pattern)

文章目錄

    • 一、享元模式的介紹
    • 二、實例分析
    • 三、示例代碼

一、享元模式的介紹

??享元模式(Flyweight Pattern) 是一種結構型設計模式。通過共享相同對象,減少內存消耗,提高性能。 它摒棄了在每個對象中保存所有數據的方式, 通過共享多個對象所共有的相同狀態, 讓你能在有限的內存容量中載入更多對象。
在這里插入圖片描述

適用場景:
當系統中存在大量相似對象時,每個對象包含許多重復的狀態(數據),這會浪費大量內存。
享元模式把對象的狀態分為兩類:

  • 內部狀態(Intrinsic State):可共享的,不隨環境變化的部分。
  • 外部狀態(Extrinsic State):不能共享的,依賴于具體環境的部分。

通過將內部狀態共享,只在需要時傳入外部狀態,即可避免創建大量對象。

享元模式的角色:

  • Flyweight(享元接口):定義可以共享的接口,聲明接收外部狀態的方法。
  • ConcreteFlyweight(具體享元類):實現享元接口,存儲內部狀態。
  • FlyweightFactory(享元工廠類):管理享元對象的創建和復用。
  • Client(客戶端):負責傳入外部狀態,使用享元對象。

享元模式結構:
在這里插入圖片描述

二、實例分析

問題:
假如你希望在長時間工作后放松一下, 所以開發了一款簡單的游戲: 玩家們在地圖上移動并相互射擊。 你決定實現一個真實的粒子系統, 并將其作為游戲的特色。 大量的子彈、 導彈和爆炸彈片會在整個地圖上穿行, 為玩家提供緊張刺激的游戲體驗。

開發完成后, 你推送提交了最新版本的程序, 并在編譯游戲后將其發送給了一個朋友進行測試。 盡管該游戲在你的電腦上完美運行, 但是你的朋友卻無法長時間進行游戲: 游戲總是會在他的電腦上運行幾分鐘后崩潰。 在研究了幾個小時的調試消息記錄后, 你發現導致游戲崩潰的原因是內存容量不足。 朋友的設備性能遠比不上你的電腦, 因此游戲運行在他的電腦上時很快就會出現問題。

真正的問題與粒子系統有關。 每個粒子 (一顆子彈、 一枚導彈或一塊彈片) 都由包含完整數據的獨立對象來表示。 當玩家在游戲中鏖戰進入高潮后的某一時刻, 游戲將無法在剩余內存中載入新建粒子, 于是程序就崩潰了。

在這里插入圖片描述

解決方案:
仔細觀察粒子Particle類, 你可能會注意到顏色 (color) 和精靈圖 (sprite)這兩個成員變量所消耗的內存要比其他變量多得多。 更糟糕的是, 對于所有的粒子來說, 這兩個成員變量所存儲的數據幾乎完全一樣 (比如所有子彈的顏色和精靈圖都一樣)。

在這里插入圖片描述
每個粒子的另一些狀態 (坐標、 移動矢量和速度) 則是不同的。 因為這些成員變量的數值會不斷變化。 這些數據代表粒子在存續期間不斷變化的情景, 但每個粒子的顏色和精靈圖則會保持不變。

對象的常量數據通常被稱為內在狀態, 其位于對象中, 其他對象只能讀取但不能修改其數值。 而對象的其他狀態常常能被其他對象 “從外部” 改變, 因此被稱為外在狀態。

享元模式建議不在對象中存儲外在狀態, 而是將其傳遞給依賴于它的一個特殊方法。 程序只在對象中保存內在狀態, 以方便在不同情景下重用。 這些對象的區別僅在于其內在狀態 (與外在狀態相比, 內在狀態的變體要少很多), 因此你所需的對象數量會大大削減。

在這里插入圖片描述
讓我們回到游戲中。 假如能從粒子類中抽出外在狀態, 那么我們只需三個不同的對象 (子彈、 導彈和彈片) 就能表示游戲中的所有粒子。 你現在很可能已經猜到了, 我們將這樣一個僅存儲內在狀態的對象稱為享元。

外在狀態存儲:
那么外在狀態會被移動到什么地方呢? 總得有類來存儲它們, 對不對? 在大部分情況中, 它們會被移動到容器對象中, 也就是我們應用享元模式前的聚合對象中。

在我們的例子中, 容器對象就是主要的游戲Game對象, 其會將所有粒子存儲在名為 粒子particles的成員變量中。 為了能將外在狀態移動到這個類中, 你需要創建多個數組成員變量來存儲每個粒子的坐標、 方向矢量和速度。 除此之外, 你還需要另一個數組來存儲指向代表粒子的特定享元的引用。 這些數組必須保持同步, 這樣你才能夠使用同一索引來獲取關于某個粒子的所有數據。

在這里插入圖片描述
更優雅的解決方案是創建獨立的情景類來存儲外在狀態和對享元對象的引用。 在該方法中, 容器類只需包含一個數組。

稍等! 這樣的話情景對象數量不是會和不采用該模式時的對象數量一樣多嗎? 的確如此, 但這些對象要比之前小很多。 消耗內存最多的成員變量已經被移動到很少的幾個享元對象中了。 現在, 一個享元大對象會被上千個情境小對象復用, 因此無需再重復存儲數千個大對象的數據。

享元與不可變性:
由于享元對象可在不同的情景中使用, 你必須確保其狀態不能被修改。 享元類的狀態只能由構造函數的參數進行一次性初始化, 它不能對其他對象公開其設置器或公有成員變量。

享元工廠:
為了能更方便地訪問各種享元, 你可以創建一個工廠方法來管理已有享元對象的緩存池。 工廠方法從客戶端處接收目標享元對象的內在狀態作為參數, 如果它能在緩存池中找到所需享元, 則將其返回給客戶端; 如果沒有找到, 它就會新建一個享元, 并將其添加到緩存池中。

你可以選擇在程序的不同地方放入該函數。 最簡單的選擇就是將其放置在享元容器中。 除此之外, 你還可以新建一個工廠類, 或者創建一個靜態的工廠方法并將其放入實際的享元類中。

三、示例代碼

示例一:
在一個文本編輯器里,每個字符都是一個對象,如果每個字符都單獨創建,就會消耗大量內存。實際上,每個字符的字形(內部狀態) 是可以共享的,只需要保存它們在文檔中的位置、大小、顏色等外部狀態 即可。

#include <iostream>
#include <map>
#include <string>
using namespace std;// 享元接口
class Character {
public:virtual void display(int size, int x, int y) = 0; // 外部狀態:字體大小、位置virtual ~Character() {}
};// 具體享元類:具體字符
class ConcreteCharacter : public Character {
private:char symbol; // 內部狀態:字符本身
public:ConcreteCharacter(char c) : symbol(c) {}void display(int size, int x, int y) override {cout << "字符: " << symbol<< "  字體大小: " << size<< "  位置: (" << x << "," << y << ")\n";}
};// 享元工廠類:管理共享對象
class CharacterFactory {
private:map<char, Character*> pool; // 享元池
public:~CharacterFactory() {for (auto& kv : pool) delete kv.second;}Character* getCharacter(char c) {if (pool.find(c) == pool.end()) {pool[c] = new ConcreteCharacter(c);}return pool[c];}
};// 客戶端
int main() {CharacterFactory factory;Character* c1 = factory.getCharacter('A');Character* c2 = factory.getCharacter('B');Character* c3 = factory.getCharacter('A'); // 復用已有對象// 顯示字符(外部狀態由客戶端傳入)c1->display(12, 10, 20);c2->display(14, 15, 25);c3->display(16, 50, 100);return 0;
}

注意:雖然創建了三個字符,但實際上 ‘A’ 只創建了一次,第二次復用,節省了內存。

示例二:
使用用五子棋(棋盤游戲)舉例實現享元模式

場景分析:

  • 在五子棋中:棋子分為黑子和白子兩種。
  • 每顆棋子的顏色可以共享(內部狀態 Intrinsic State)。
  • 棋子的位置 (x, y) 每次都不同(外部狀態 Extrinsic State)。

因此,整個棋盤上無論下多少顆棋子,實際上只需要兩個享元對象(黑子、白子)。

#include <iostream>
#include <map>
#include <string>
using namespace std;// 抽象享元類:棋子
class ChessPiece {
public:virtual void draw(int x, int y) = 0; // 外部狀態:棋子位置virtual ~ChessPiece() {}
};// 具體享元類:黑子
class BlackPiece : public ChessPiece {
public:void draw(int x, int y) override {cout << "黑子落在位置 (" << x << "," << y << ")\n";}
};// 具體享元類:白子
class WhitePiece : public ChessPiece {
public:void draw(int x, int y) override {cout << "白子落在位置 (" << x << "," << y << ")\n";}
};// 享元工廠:管理棋子對象
class PieceFactory {
private:map<string, ChessPiece*> pool; // 棋子池
public:~PieceFactory() {for (auto& kv : pool) delete kv.second;}ChessPiece* getPiece(const string& color) {if (pool.find(color) == pool.end()) {if (color == "black") {pool[color] = new BlackPiece();} else if (color == "white") {pool[color] = new WhitePiece();}}return pool[color];}
};// 客戶端:模擬下棋
int main() {PieceFactory factory;ChessPiece* black = factory.getPiece("black");ChessPiece* white = factory.getPiece("white");// 下棋(外部狀態:位置由客戶端傳入)black->draw(3, 3);white->draw(4, 4);black->draw(5, 5);white->draw(6, 6);black->draw(7, 7);return 0;
}

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

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

相關文章

【Go語言入門教程】 Go語言的起源與技術特點:從誕生到現代編程利器(一)

文章目錄前言1. Go語言的起源與發展2. Go語言的核心設計團隊2.1 Ken Thompson&#xff08;肯湯普森&#xff09;2.2 Rob Pike&#xff08;羅布派克&#xff09;2.3 Robert Griesemer&#xff08;羅伯特格瑞澤默&#xff09;設計動機&#xff1a;解決C的痛點3. Go語言的核心特性…

rocketmq啟動與測試

1.更改runserver.sh的內存大小 vi runserver.sh 2.更改 runbroker.sh內存大小 vi runbroker.sh3.設置環境變量 vi ~/.bash_profile 新增 export NAMESRV_ADDRlocalhost:98764.啟動 --在bin的上一級目錄啟動 nohup bin/mqnamesrv & nohup bin/mqbroker &5.查看日志 le…

11.《簡單的路由重分布基礎知識探秘》

11_路由重分布 文章目錄11_路由重分布路由重分布概述路由重分布的核心作用基礎實驗實驗流程實驗拓撲配置示例(基本操作省略)實驗結論路由重分布概述 路由重分布&#xff08;又稱路由引入&#xff09;是指在不同路由協議之間交換路由信息的技術。在復雜網絡中&#xff0c;可能同…

C++ 左值引用與右值引用介紹

C 左值引用與右值引用詳解 在 C 的類型系統中&#xff0c;引用&#xff08;reference&#xff09; 是一種為已有對象起別名的機制。在早期&#xff08;C98/03&#xff09;中&#xff0c;C 只有 左值引用&#xff08;lvalue reference&#xff09;&#xff0c;主要用于函數參數…

基于物聯網設計的園林灌溉系統(華為云IOT)_274

文章目錄 一、前言 1.1 項目介紹 【1】項目開發背景 【2】設計實現的功能 【3】項目硬件模塊組成 【4】設計意義 【5】國內外研究現狀 【6】摘要 1.2 設計思路 1.3 系統功能總結 1.4 開發工具的選擇 【1】設備端開發 【2】上位機開發 1.5 參考文獻 1.6 系統框架圖 1.7 系統原理…

uni-app iOS 應用版本迭代與上架實踐 持續更新的高效流程

很多團隊在使用 uni-app 開發 iOS 應用時&#xff0c;往往能順利完成第一次上架&#xff0c;但一到 版本更新和迭代 環節&#xff0c;就會頻繁遇到瓶頸&#xff1a;證書是否能復用&#xff1f;如何快速上傳&#xff1f;怎樣保持節奏不被打亂&#xff1f; 本文結合實戰經驗&…

解決由Tomcat部署前端改成nginx部署,導致大寫.JPG結尾文件無法訪問問題

前言&#xff1a;因信創替代要求&#xff0c;在麒麟服務器部署新的應用。原先的架構&#xff1a;前端tomcat部署&#xff0c;源碼部署java應用&#xff08;ps&#xff1a;前后端&#xff0c;文件都在同一臺服務器上&#xff09;&#xff0c;前端訪問后端&#xff0c;再通過后端…

【設計模式】三大原則 單一職責原則、開放-封閉原則、依賴倒轉原則

系列文章目錄 文章目錄系列文章目錄一、單一職責原則方塊游戲的設計二、開放-封閉原則原則介紹何時應對變化三、依賴倒轉原則依賴倒轉原則介紹里氏代換原則總結一、單一職責原則 單一職責原則&#xff0c;聽字面意思&#xff0c;就是說功能要單一&#xff0c;他的準確解釋是&a…

(3dnr)多幀視頻圖像去噪 (一)

一、多幀視頻圖像去噪 原理當攝像機每秒捕捉的圖像達到60FPS&#xff0c;除了場景切換或者一些快速運動的場 景外&#xff0c;視頻信號中相鄰的兩幀圖像內容大部分是相同的。并且視頻信號中的噪 聲大部分都是均值為零的隨機噪聲&#xff0c;因此在時間上對視頻信號做幀平均&…

從靜態到智能:用函數式接口替代傳統工具類

在 Java 早期開發中&#xff0c;我們習慣使用**靜態實用程序類&#xff08;Utility Class&#xff09;**來集中放置一些通用方法&#xff0c;例如驗證、字符串處理、數學計算等。這種模式雖然簡單直接&#xff0c;但在現代 Java 開發&#xff08;尤其是 Java 8 引入 Lambda 和函…

免殺偽裝 ----> R3進程偽裝實戰(高階) ---->培養紅隊免殺思路

目錄 R3進程偽裝(免殺技術)高階技術說明 深入剖析Windows進程規避免殺技術 學習R3進程偽裝的必備技能 R3進程偽裝的核心知識點與實現步驟 核心知識點 實現步驟 免殺實現步驟 PEB與EPROCESS的深入解析 1. PEB&#xff08;進程環境塊&#xff09; 2. EPROCESS 3. PEB與…

深度學習——基于卷積神經網絡實現食物圖像分類(數據增強)

文章目錄 引言 一、項目概述 二、環境準備 三、數據預處理 3.1 數據增強與標準化 3.2 數據集準備 四、自定義數據集類 五、構建CNN模型 六、訓練與評估 6.1 訓練函數 6.2 評估函數 6.3 訓練流程 七、關鍵技術與優化 八、常見問題與解決 九、完整代碼 十、總結 引言 本文將詳細介…

【開題答辯全過程】以 基于微信小程序的教學輔助系統 為例,包含答辯的問題和答案

個人簡介一名14年經驗的資深畢設內行人&#xff0c;語言擅長Java、php、微信小程序、Python、Golang、安卓Android等開發項目包括大數據、深度學習、網站、小程序、安卓、算法。平常會做一些項目定制化開發、代碼講解、答辯教學、文檔編寫、也懂一些降重方面的技巧。感謝大家的…

【代碼解讀】Deepseek_vl2中具體代碼調用

【代碼解讀】Deepseek_vl2中具體代碼調用 文章目錄【代碼解讀】Deepseek_vl2中具體代碼調用DeepseekVLV2Processor解讀DeepseekVLV2ForCausalLM - 多模態模型DeepSeek-VL2 Processor的輸入格式單樣本格式多樣本格式DeepSeek-VL2模型的輸出形式總結主要輸出類型&#xff1a;Deep…

Git 9 ,.git/index.lock 文件沖突問題( .git/index.lock‘: File exists. )

目錄 前言 一、問題背景 1.1 問題出現場景 1.2 典型報錯信息 1.3 問題影響 二、問題原因分 2.1 Git 的 index 與鎖機制 2.2 主要作用 2.3 根本原因 三、解決方案 3.1 確認進程 3.2 手動刪除 3.3 再次執行 四、注意事項 4.1 確保運行 4.2 問題排查 4.3 自動化解…

Proteus8 仿真教學全指南:從入門到實戰的電子開發利器

在電子設計、單片機課程設計或創客實踐中&#xff0c;你是否常因實物采購貴、新手怕燒板、調試排錯難而頭疼&#xff1f;Proteus8 作為一款 “全能型” EDA 仿真工具&#xff0c;完美解決這些痛點 —— 它集「原理圖繪制 PCB 設計 虛擬仿真」于一體&#xff0c;支持 51、STM3…

系統科學:結構、功能與層級探析

摘要本文旨在系統性地梳理和辨析系統科學中的核心概念——結構、功能與層級。文章首先追溯系統思想的理論源流&#xff0c;確立其作為一種超越還原論的整體性研究范式。在此基礎上&#xff0c;深度剖析系統結構的內在構成&#xff08;組分、框架、動態性&#xff09;、系統層級…

面試官問:你如何看待薪資待遇?

在面試過程中&#xff0c;“你如何看待薪資待遇&#xff1f;”這個問題&#xff0c;是很多面試官都會提出的經典問題之一。雖然表面上看起來是一個簡單的提問&#xff0c;但它實則關乎候選人的職業價值觀、工作態度以及對自己能力的認知。薪資是工作的重要動力之一&#xff0c;…

HarmonyOS 應用開發新范式:深入剖析 Stage 模型與 ArkUI 最佳實踐

好的&#xff0c;請看這篇基于 HarmonyOS (鴻蒙) 最新技術棧的深度技術文章。 HarmonyOS 應用開發新范式&#xff1a;深入剖析 Stage 模型與 ArkUI 最佳實踐 引言 隨著 HarmonyOS 4、5 的持續演進和未來 6 的規劃&#xff0c;其應用開發框架經歷了革命性的重構。對于技術開發者…

【Python數據可視化:Matplotlib高級技巧】

Python數據可視化&#xff1a;Matplotlib高級技巧引言在數據科學和分析領域&#xff0c;數據可視化是理解和傳達信息的關鍵工具。Python中最流行的可視化庫之一就是Matplotlib。雖然初學者可以快速上手Matplotlib的基礎功能&#xff0c;但掌握其高級技巧才能真正發揮這個強大庫…