深入理解設計模式:享元模式(Flyweight Pattern)

在軟件開發中,我們經常會遇到需要創建大量相似對象的情況。如果每個對象都獨立存儲所有數據,將會消耗大量內存資源,導致系統性能下降。享元模式(Flyweight Pattern)正是為解決這一問題而生的經典設計模式。本文將深入探討享元模式的核心概念、實現原理、應用場景以及實際案例,幫助讀者全面理解并掌握這一高效的對象共享技術。

一、享元模式概述

1.1 什么是享元模式

享元模式是一種結構型設計模式,它通過共享技術來有效地支持大量細粒度對象的復用,從而減少內存消耗。該模式的核心思想是將對象的狀態分為內部狀態(Intrinsic State)和外部狀態(Extrinsic State),其中內部狀態是可以共享的,而外部狀態則由客戶端在需要時傳遞給享元對象。

"Flyweight"一詞源于拳擊運動中的"輕量級"概念,寓意這種模式創建的對象的"輕量"特性——它們只包含最少量的內部狀態,大部分狀態由外部提供。

1.2 歷史背景

享元模式最早由Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides在1994年的經典著作《設計模式:可復用面向對象軟件的基礎》中提出。這四位作者被稱為"四人幫"(Gang of Four,GoF),他們系統化地整理了23種經典設計模式,享元模式是其中之一。

1.3 模式動機

在面向對象編程中,一切皆對象。但當系統中需要創建大量相似對象時,會面臨以下問題:

  1. 內存消耗過大:每個對象都占用一定的內存空間,大量對象會迅速耗盡可用內存

  2. 創建開銷大:頻繁創建和銷毀對象會導致性能瓶頸

  3. GC壓力大:在垃圾回收環境中,大量對象會增加GC負擔

享元模式通過共享技術解決了這些問題,它使得多個對象可以共享相同的狀態,而不是每個對象都保存一份副本。

二、享元模式的結構與實現

2.1 模式結構

享元模式包含以下幾個關鍵角色:

  1. Flyweight(抽象享元類):定義對象的接口,聲明操作外部狀態的方法

  2. ConcreteFlyweight(具體享元類):實現抽象享元接口,存儲內部狀態

  3. UnsharedConcreteFlyweight(非共享具體享元類):不需要共享的子類

  4. FlyweightFactory(享元工廠類):創建并管理享元對象,確保合理共享

  5. Client(客戶端):維護外部狀態,并在需要時將外部狀態傳遞給享元對象

2.2 狀態劃分

享元模式成功的關鍵在于正確區分內部狀態和外部狀態:

  • 內部狀態(Intrinsic State):存儲在享元對象內部且不會隨環境改變的狀態,可以被共享

  • 外部狀態(Extrinsic State):隨環境改變而改變的狀態,不可共享,由客戶端保存

2.3 Java實現示例

// 抽象享元類
interface ChessPiece {void draw(int x, int y); // x,y是外部狀態(位置)
}// 具體享元類 - 白棋
class WhiteChessPiece implements ChessPiece {private final String color = "白色"; // 內部狀態@Overridepublic void draw(int x, int y) {System.out.println(color + "棋子放置在(" + x + "," + y + ")");}
}// 具體享元類 - 黑棋
class BlackChessPiece implements ChessPiece {private final String color = "黑色"; // 內部狀態@Overridepublic void draw(int x, int y) {System.out.println(color + "棋子放置在(" + x + "," + y + ")");}
}// 享元工廠
class ChessPieceFactory {private static final Map<String, ChessPiece> pieces = new HashMap<>();static {pieces.put("白", new WhiteChessPiece());pieces.put("黑", new BlackChessPiece());}public static ChessPiece getChessPiece(String color) {return pieces.get(color);}
}// 客戶端
public class ChessGame {public static void main(String[] args) {// 下白棋ChessPiece white1 = ChessPieceFactory.getChessPiece("白");white1.draw(1, 1);ChessPiece white2 = ChessPieceFactory.getChessPiece("白");white2.draw(1, 2);// 下黑棋ChessPiece black1 = ChessPieceFactory.getChessPiece("黑");black1.draw(2, 1);// 再次下白棋ChessPiece white3 = ChessPieceFactory.getChessPiece("白");white3.draw(2, 2);System.out.println("實際創建的棋子對象數量: " + ChessPieceFactory.getPieceCount());}
}

在這個示例中,無論棋盤上有多少白棋或黑棋,系統都只創建了兩個棋子對象(一白一黑),所有同顏色的棋子共享同一個對象,只是位置(外部狀態)不同。

三、享元模式的深入分析

3.1 內部狀態與外部狀態的確定

正確區分內部狀態和外部狀態是應用享元模式的關鍵。以下是一些判斷標準:

  1. 內部狀態

    • 對象固有的、不隨環境變化的屬性

    • 可以被多個對象共享

    • 通常是不變(immutable)的

    • 例如:字符的字形、棋子的顏色、樹的種類等

  2. 外部狀態

    • 取決于對象所處的上下文環境

    • 每個對象特有的、不可共享的屬性

    • 可能會頻繁變化

    • 例如:字符的位置、棋子的位置、樹的位置等

3.2 線程安全考慮

在多線程環境下使用享元模式時需要注意:

  1. 享元對象通常是不可變的(只有內部狀態),因此本質上是線程安全的

  2. 如果享元對象包含可變狀態,需要采取同步措施

  3. 享元工廠的創建方法應考慮并發訪問問題

3.3 與其他模式的關系

  1. 與單例模式

    • 都可以限制對象的數量

    • 單例模式確保一個類只有一個實例

    • 享元模式可以有多個實例,但相同內部狀態的實例被共享

  2. 與組合模式

    • 可以結合使用,共享的享元對象可以作為組合結構的葉子節點

  3. 與狀態模式/策略模式

    • 享元對象可以持有對狀態或策略對象的引用

四、享元模式的應用場景

享元模式在以下場景中特別有用:

4.1 圖形編輯器

在圖形編輯器中,字符、圖形等對象可能有大量重復實例。例如:

  • 每個字符的字形(內部狀態)可以被共享

  • 字符的位置、顏色等(外部狀態)由外部維護

4.2 游戲開發

游戲中經常需要創建大量相似對象:

  • 粒子系統中的粒子

  • 地圖中的樹木、建筑等重復元素

  • 同類型的NPC或敵人

4.3 數據庫連接池

連接池是享元模式的典型應用:

  • 連接對象被創建后放入池中

  • 客戶端從池中獲取連接而不是新建

  • 使用完畢后歸還到池中

4.4 其他應用

  • 文本處理中的字符串池

  • 瀏覽器中的DOM節點復用

  • 財務系統中的共享會計科目對象

五、享元模式的優缺點

5.1 優點

  1. 大幅減少內存使用:通過共享相同內部狀態的對象,顯著降低內存消耗

  2. 提高性能:減少了對象創建和垃圾回收的開銷

  3. 集中管理共享狀態:所有共享狀態集中存儲,便于管理和維護

  4. 外部狀態獨立:外部狀態的變化不會影響共享的內部狀態

5.2 缺點

  1. 增加系統復雜性:需要區分內部狀態和外部狀態,增加了設計難度

  2. 可能引入線程安全問題:如果外部狀態處理不當,可能導致并發問題

  3. 查找開銷:維護共享對象池可能需要額外的查找開銷

  4. 不適用于所有場景:當對象間差異很大時,享元模式可能不適用

六、實際案例分析

6.1 Java String常量池

Java中的String類使用了享元模式的思想。字符串常量池(String Pool)是享元模式的經典實現:

String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");System.out.println(s1 == s2); // true,引用同一個對象
System.out.println(s1 == s3); // false,s3是新創建的對象

JVM會維護一個字符串常量池,相同的字符串字面量會指向池中的同一個對象。

6.2 瀏覽器中的DOM渲染

現代瀏覽器在渲染DOM時也使用了享元模式的思想:

  • 相同類型的DOM節點可以共享相同的樣式計算

  • 只有位置、內容等外部狀態需要單獨存儲

  • 這大大提高了頁面渲染性能

6.3 棋牌游戲開發

以麻將游戲為例:

// 麻將牌享元工廠
class MahjongTileFactory {private static Map<String, MahjongTile> tiles = new HashMap<>();static {String[] types = {"萬", "條", "筒"};for (String type : types) {for (int i = 1; i <= 9; i++) {tiles.put(type + i, new MahjongTile(type, i));}}}public static MahjongTile getTile(String type, int num) {return tiles.get(type + num);}
}// 客戶端
public class MahjongGame {public static void main(String[] args) {// 玩家手牌List<MahjongTile> handTiles = new ArrayList<>();// 添加牌,相同牌號的牌會共享同一個對象handTiles.add(MahjongTileFactory.getTile("萬", 1));handTiles.add(MahjongTileFactory.getTile("條", 5));handTiles.add(MahjongTileFactory.getTile("萬", 1)); // 與第一個是同一個對象System.out.println("手牌數量: " + handTiles.size());System.out.println("實際創建的牌對象數量: " + MahjongTileFactory.getTileCount());}
}

在這個例子中,相同牌號的麻將牌共享同一個對象,大大減少了內存使用。

七、享元模式的最佳實踐

  1. 合理劃分內部和外部狀態:這是享元模式成功應用的關鍵

  2. 使用工廠管理享元對象:集中管理可以確保正確共享

  3. 考慮線程安全性:特別是在多線程環境中

  4. 權衡性能與內存:不是所有情況都適合使用享元模式

  5. 結合其他模式使用:如工廠模式、組合模式等

八、總結

享元模式是一種通過共享技術來支持大量細粒度對象的高效設計模式。它通過區分內部狀態和外部狀態,使得具有相同內部狀態的對象可以被共享,從而顯著減少內存消耗和提高系統性能。正確應用享元模式需要對業務場景有深入理解,能夠準確識別可共享的內部狀態和不可共享的外部狀態。

雖然享元模式在特定場景下非常有效,但它并非銀彈。開發者需要根據實際情況權衡利弊,決定是否采用享元模式。當系統中存在大量相似對象且內存是瓶頸時,享元模式無疑是一個強大的工具;但當對象間差異很大或外部狀態過于復雜時,可能需要考慮其他解決方案。

理解并掌握享元模式,能夠幫助開發者設計出更加高效、優雅的軟件系統,特別是在資源受限的環境中。

Java中的String類使用了享元模式的思想。字符串常量池(String Pool)是享元模式的經典實現:

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

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

相關文章

網絡大提速,RDMA,IB,iWrap

本章第一節介紹的存儲設備方面的創新解決了CPU訪問存儲設備的性能問題。但在實際的業務當中,數據的傳輸除了在節點內部的CPU與存儲設備間外,節點之間也存在數據傳輸的需求。本節我們就介紹在網絡傳輸方面是如何提速的。 在介紹新的網絡技術之前,我們看看傳統網絡是如何傳輸…

【C++】紅黑樹,“紅“與“黑”的較量

各位大佬好&#xff0c;我是落羽&#xff01;一個堅持不斷學習進步的大學生。 如果您覺得我的文章有所幫助&#xff0c;歡迎多多互三分享交流&#xff0c;一起學習進步&#xff01; 也歡迎關注我的blog主頁: 落羽的落羽 一、紅黑樹的概念與規則 紅黑樹是一種更加特殊的平衡二…

【愚公系列】《MIoT.VC》001-認識、安裝 MIoT.VC 軟件

??【行業認證權威頭銜】 ? 華為云天團核心成員:特約編輯/云享專家/開發者專家/產品云測專家 ? 開發者社區全滿貫:CSDN博客&商業化雙料專家/阿里云簽約作者/騰訊云內容共創官/掘金&亞馬遜&51CTO頂級博主 ? 技術生態共建先鋒:橫跨鴻蒙、云計算、AI等前沿領域…

git:tag標簽遠程管理

git tag v1&#xff1a;在當前所在分支創建標簽v1git tag -a v2 -m release version&#xff1a;創建一個帶有附注的標簽git tag -d v2&#xff1a;刪除本地標簽git tag&#xff1a;查看標簽git push origin 標簽1 標簽2……&#xff1a;把多個標簽推送到遠程git push origin -…

力扣 hot100 Day49

105. 從前序與中序遍歷序列構造二叉樹 給定兩個整數數組 preorder 和 inorder &#xff0c;其中 preorder 是二叉樹的先序遍歷&#xff0c; inorder 是同一棵樹的中序遍歷&#xff0c;請構造二叉樹并返回其根節點。 //抄的 class Solution { private:unordered_map<int, i…

jvm-sandbox-repeater 錄制和回放

https://github.com/alibaba/jvm-sandbox-repeater/blob/master/docs/user-guide-cn.md 快速錄制自己應用 step0 安裝sandbox和插件到應用服務器 curl -s https://github.com/alibaba/jvm-sandbox-repeater/releases/download/v1.0.0/install-repeater.sh | sh step1 修改repe…

【C++底層剖析】++a vs a++:到底誰是左值,誰是右值?

在 C 編程中&#xff0c;我們經常使用 a 和 a 來實現自增操作。乍一看它們只是“先加還是后加”的語法糖&#xff0c;但你真的理解它們的底層機制、返回值類型和左值右值屬性嗎&#xff1f;1. a 和 a 的基礎區別表達式名稱語義返回值類型左值 / 右值a前置自增先將 a 加 1&#…

【世紀龍科技】汽車故障診斷與排除仿真教學軟件讓課堂更高效安全

隨著汽車產業向智能化、電動化快速轉型&#xff0c;職業院校汽修專業的教學模式正面臨全新挑戰。傳統實車實訓存在成本高、風險大、場景單一等問題&#xff0c;而行業對人才的要求卻越來越高——既需要扎實的理論基礎&#xff0c;又必須具備熟練的故障診斷能力。如何在保證安全…

網絡基礎9:按流負載均衡實驗(等價路由)

實驗eNS拓撲圖&#xff1a;1. 網絡拓撲與 IP 配置AR5&#xff1a;GE0/0/0: 192.168.1.1/24&#xff08;連接 AR6&#xff09;GE0/0/1: 192.168.3.1/24&#xff08;連接 AR8&#xff09;Loopback0: 1.1.1.1/32&#xff08;源地址&#xff09;AR6&#xff1a;GE0/0/0: 192.168.1.…

4G模塊 A7680發送中文短信到手機

命令說明 基礎AT指令 ATi顯示產品的標志信息 ATCIMI查詢IMSI ATCICCID從SIM卡讀取ICCID ATCGSN查詢產品序列號 ATCPIN查詢卡狀態 ATCSQ查詢信號強度 ATCGATT查詢當前PS域狀態 ATCREG查詢GPRS注冊狀態 ATCEREG查詢4G注冊狀態 ATCGPADDR查詢PDP地址 ATCMGF選擇短信格式 ATCMGS發…

深度學習-線性神經網絡

文章目錄線性回歸基本概念隨機梯度下降矢量化加速正態分布和平方損失極大似然估計線性回歸實現從0開始**torch.no_grad()的兩種用途****為什么需要 l.sum().backward()&#xff1f;**調用現成庫softmax回歸圖像數據集從0開始實現softmax利用框架API實現課程學習自李牧老師B站的…

【王樹森推薦系統】推薦系統漲指標的方法04:多樣性

漲指標的方法有哪些&#xff1f; 改進召回模型&#xff0c;添加新的召回模型改進粗排和精排模型提升召回&#xff0c;粗排&#xff0c;精排的多樣性特殊對待新用戶嗎&#xff0c;低活用戶等特殊人群利用關注&#xff0c;轉發&#xff0c;評論這三種交互行為 排序的多樣性 精排多…

1. Spring AI概述

一、前言 Spring AI 是由 Spring 團隊推出的開源項目&#xff0c;旨在為 Java 開發者提供簡潔、一致的 Spring 風格開發體驗&#xff0c;用于構建基于生成式人工智能&#xff08;GenAI&#xff09;和大型語言模型&#xff08;LLM&#xff09;的應用程序。它通過標準化抽象層簡…

[每日隨題10] DP - 重鏈剖分 - 狀壓DP

整體概述 難度&#xff1a;1600 →\rightarrow→ 2200 →\rightarrow→ 2600 P6005 [USACO20JAN] Time is Mooney G 標簽&#xff1a;DP 前置知識&#xff1a;鏈式前向星 難度&#xff1a;綠 1600 題目描述&#xff1a; 輸入格式&#xff1a; 輸出格式&#xff1a; 樣例輸…

【Ubuntu22.04】repo安裝方法

背景 repo是Google開發的用于基于git管理Android版本庫的一個工具&#xff0c;管理多個Git倉庫的工具&#xff0c;它可以幫助您在一個代碼庫中管理多個Git倉庫的代碼。其在鴻蒙操作系統中大量使用。下面我們就介紹repo在wsl中的安裝部署。 安裝方法 使用中國科技大學資源 腳本i…

Vue3的definePros和defineEmits

在 Vue 3 中&#xff0c;defineProps 和 defineEmits 是組合式 API 中用于定義組件的 props 和 事件 的方法&#xff0c;提供了一種更簡潔和明確的方式來管理組件的輸入和輸出。它們屬于 Composition API 的一部分&#xff0c;在 Vue 2 中通常使用 props 和 $emit 來實現。1. d…

【華為機試】122. 買賣股票的最佳時機 II

文章目錄122. 買賣股票的最佳時機 II描述示例 1示例 2示例 3提示解題思路核心觀察關鍵洞察算法實現方法1&#xff1a;貪心算法&#xff08;推薦&#xff09;方法2&#xff1a;動態規劃方法3&#xff1a;動態規劃&#xff08;空間優化&#xff09;方法4&#xff1a;波峰波谷法算…

Spring MVC @RequestParam注解全解析

RequestParam 注解詳解 RequestParam 是 Spring MVC 中最常用的注解之一&#xff0c;用于從 HTTP 請求中提取查詢參數&#xff08;Query String&#xff09;或表單數據。它主要處理 application/x-www-form-urlencoded 類型的請求&#xff08;如 GET 請求或 POST 表單提交&…

從零掌握XML與DTD實體:原理、XXE漏洞攻防

本文僅用于技術研究&#xff0c;禁止用于非法用途。 Author:枷鎖 文章目錄一、XML基礎1. 什么是XML&#xff1f;2. XML語法規則3. 數據類型二、DTD1. 認識DTD2. 聲明DTD3. DTD實體4. 如何防御XXE攻擊&#xff1f;5. 總結一、XML基礎 1. 什么是XML&#xff1f; XML &#xff1…

.NET 8 Release Candidate 1 (RC1)現已發布,包括許多針對ASP.NET Core的重要改進!

.NET 8 Release Candidate 1 (RC1)發布&#xff1a;ASP.NET Core重大改進來襲&#xff01; 近日&#xff0c;.NET 8 Release Candidate 1 (RC1)正式發布&#xff0c;這是在今年晚些時候計劃發布的最終 .NET 8 版本之前的兩個候選版本中的第一個。此版本包含了大部分計劃中的功…