摘要
享元設計模式是一種結構型設計模式,旨在通過共享對象減少內存占用和提升性能。其核心思想是將對象狀態分為內部狀態(可共享)和外部狀態(不可共享),并通過享元工廠管理共享對象池。享元模式包含抽象享元類、具體享元類、非共享具體享元類和享元工廠類。它適用于處理大量相似對象的場景,如文檔編輯器中的字符對象。文章還提供了享元模式的實現方式、適合與不適合的使用場景、實戰示例以及與其他設計模式的比較。
1. 享元設計模式定義
享元設計模式(Flyweight Pattern) 是一種結構型設計模式,用于減少對象的數量,以節省內存和提高性能。享元模式通過共享內存中已經存在的對象,避免重復創建相同內容的對象,適用于大量相似對象的場景。
1.1.1. 核心思想
把對象狀態劃分為:
- 內部狀態(可共享,存儲在享元對象中)
- 外部狀態(不可共享,由客戶端維護)
享元工廠(FlyweightFactory): 負責管理共享對象的池,復用已有實例。
1.1.2. 舉例說明
假設一個文檔編輯器中有 10 萬個字符,但字符集就 128 個(ASCII),如果每個字符都是獨立對象,將浪費大量內存。此時可以:
- 把字符內容作為內部狀態(可共享)
- 把字體、大小、顏色作為外部狀態(由外部控制)
- 同一個字符內容只創建一次對象,由享元工廠復用
2. 享元設計模式結構
享元模式包含如下角色:
- Flyweight: 抽象享元類
- ConcreteFlyweight: 具體享元類
- UnsharedConcreteFlyweight: 非共享具體享元類
- FlyweightFactory: 享元工廠類
2.1. 享元設計模式類圖
2.2. 享元設計模式時序圖
3. 享元設計模式實現方式
享元設計模式的實現方式主要圍繞 對象共享池 和 內部狀態與外部狀態的分離。它通過一個享元工廠(FlyweightFactory)來管理共享對象,避免重復創建,從而節省內存。
3.1. 1?? 定義抽象享元接口(Flyweight)
public interface Flyweight {void operation(String externalState); // 外部狀態由調用方傳入
}
3.2. 2?? 創建具體享元類(ConcreteFlyweight)
public class ConcreteFlyweight implements Flyweight {private final String intrinsicState; // 內部狀態,能共享public ConcreteFlyweight(String intrinsicState) {this.intrinsicState = intrinsicState;}@Overridepublic void operation(String externalState) {System.out.println("共享[" + intrinsicState + "],非共享[" + externalState + "]");}
}
3.3. 3?? 創建享元工廠類(FlyweightFactory)
public class FlyweightFactory {private final Map<String, Flyweight> pool = new HashMap<>();public Flyweight getFlyweight(String key) {if (!pool.containsKey(key)) {pool.put(key, new ConcreteFlyweight(key));}return pool.get(key);}public int getPoolSize() {return pool.size();}
}
3.4. 4?? 客戶端調用(Client)
public class Client {public static void main(String[] args) {FlyweightFactory factory = new FlyweightFactory();Flyweight a1 = factory.getFlyweight("A");Flyweight a2 = factory.getFlyweight("A"); // 重復,不創建新對象a1.operation("外部狀態1");a2.operation("外部狀態2");System.out.println("共享對象數量:" + factory.getPoolSize()); // 輸出:1}
}
3.5. ? 享元設計模式總結
要素 | 說明 |
內部狀態 | 可以共享,存儲在享元對象中,如字符、類型 |
外部狀態 | 每次調用時由客戶端傳入,不在享元內部存儲 |
工廠類 | 管理共享對象的創建與復用 |
緩存池 | 使用 HashMap 或 ConcurrentHashMap 存儲共享對象 |
線程安全注意點 | 在并發環境下需保證工廠創建邏輯線程安全(如加鎖或使用 ConcurrentMap) |
4. 享元設計模式適合場景
4.1. ? 適合使用享元設計模式的場景
場景 | 說明 |
大量重復對象需創建,且狀態大部分相同 | 比如:文字編輯器中的字符對象、地圖中的草地格子、游戲中的粒子等,能顯著減少內存開銷。 |
對象創建成本高,希望通過復用來減少系統負擔 | 比如:金融系統中共享的“黑名單規則”、“風控維度元數據”等。 |
對象狀態可拆分為可共享的內部狀態和不可共享的外部狀態 | 共享部分放入享元,變動部分交給外部傳入,從而實現高復用。 |
系統中存在大量細粒度對象,結構相似、功能一致 | 如圖形系統中的形狀節點、文檔編輯器中的字體、格式等。 |
緩存池或對象池機制的實現場景 | 比如:數據庫連接池、線程池、元數據池、圖標池、緩存字典等。 |
4.2. ? 不適合使用享元模式的場景
場景 | 原因 |
對象狀態頻繁變化,不可拆分共享/不共享狀態 | 對象狀態不能被提取為外部狀態時,就無法有效共享,甚至會導致共享污染。 |
對象之間差異太大,無法復用或沒有可共享部分 | 如果每個對象都是完全不同的個體,享元模式無法帶來價值。 |
系統對對象獨立性要求高,不允許共享 | 比如線程不安全或敏感業務邏輯要求每個對象獨立維護生命周期。 |
對象數量本身不多,內存開銷可以接受 | 引入享元結構反而增加了系統復雜度、調試難度,不值得。 |
共享對象內部包含資源引用,如 Socket、File 等 | 資源不允許多個業務共享,使用享元會造成資源沖突或數據錯亂。 |
4.3. 🧠 享元模式的場景總結
項目 | 適合使用享元模式 | 不適合使用享元模式 |
對象數量 | ? 大量重復 | ? 數量少 |
狀態結構 | ? 可拆分內外狀態 | ? 狀態復雜或強耦合 |
系統壓力 | ? 內存敏感,需優化 | ? 性能足夠,優化收益低 |
可復用性 | ? 可復用部分明顯 | ? 無明顯共享邏輯 |
系統復雜度 | ? 有控制成本價值 | ? 小項目/臨時代碼 |
5. 享元設計模式實戰示例
下面是一個 享元設計模式在金融風控系統中的 Spring 實戰示例,場景為:風控規則元數據共享池,用于緩存和復用規則的靜態定義,減少重復加載和內存占用。在風控系統中,不同的策略規則經常引用相同的“規則定義”(如規則編號、描述、字段映射等)。這些定義是 不可變 的、重復使用 的,適合使用享元模式來緩存復用。
5.1. ? 享元接口:RuleDefinition
public interface RuleDefinition {void evaluate(String param); // 示例行為
}
5.2. ? 具體享元類:ConcreteRuleDefinition
public class ConcreteRuleDefinition implements RuleDefinition {private final String ruleCode;private final String description;public ConcreteRuleDefinition(String ruleCode, String description) {this.ruleCode = ruleCode;this.description = description;}@Overridepublic void evaluate(String param) {System.out.println("執行規則 [" + ruleCode + "] - " + description + ",參數:" + param);}public String getRuleCode() {return ruleCode;}public String getDescription() {return description;}
}
5.3. ? 享元工廠:RuleDefinitionFactory
(由 Spring 管理)
@Component
public class RuleDefinitionFactory {private final Map<String, RuleDefinition> pool = new ConcurrentHashMap<>();/*** 獲取共享的規則定義*/public RuleDefinition getRule(String ruleCode) {return pool.computeIfAbsent(ruleCode, this::loadRuleDefinition);}/*** 模擬從數據庫或配置中加載規則元數據*/private RuleDefinition loadRuleDefinition(String ruleCode) {// 實際情況應從數據庫或配置中心加載System.out.println("加載規則定義:" + ruleCode);return new ConcreteRuleDefinition(ruleCode, "規則描述_" + ruleCode);}public int getPoolSize() {return pool.size();}
}
5.4. ? 客戶端服務:RiskEngineService
(注入使用)
@Service
public class RiskEngineService {@Autowiredprivate RuleDefinitionFactory ruleDefinitionFactory;public void processRisk(String ruleCode, String inputParam) {RuleDefinition rule = ruleDefinitionFactory.getRule(ruleCode);rule.evaluate(inputParam);}
}
5.5. ? 啟動類或控制器測試(模擬調用)
@RestController
public class RiskController {@Autowiredprivate RiskEngineService riskEngineService;@GetMapping("/risk/test")public String test() {riskEngineService.processRisk("R001", "用戶A數據");riskEngineService.processRisk("R001", "用戶B數據");riskEngineService.processRisk("R002", "用戶C數據");return "風控規則執行完畢";}
}
5.6. ? 運行結果示例
加載規則定義:R001
執行規則 [R001] - 規則描述_R001,參數:用戶A數據
執行規則 [R001] - 規則描述_R001,參數:用戶B數據
加載規則定義:R002
執行規則 [R002] - 規則描述_R002,參數:用戶C數據
可見:R001
只加載一次,后續復用;實現了享元模式在 Spring 項目下的實戰落地。
要素 | 說明 |
享元類 |
|
享元工廠 |
|
共享池 |
|
注解注入 | 使用 |
應用場景 | 風控規則元數據復用、規則模板復用、評分模型共享 |
6. 享元設計模式思考
6.1. 享元設計模式與原型設計模式?
享元設計模式(Flyweight)和原型設計模式(Prototype)都是創建相關的設計模式,但它們解決的問題、使用方式和結構完全不同。下面是它們的詳細對比:
6.1.1. 🆚 Flyweight vs. Prototype
維度 | 享元模式(Flyweight) | 原型模式(Prototype) |
💡 設計模式類型 | 結構型模式 | 創建型模式 |
🎯 目的 | 通過共享對象來減少內存占用和對象數量 | 通過復制已有對象來創建新對象,避免 new 開銷 |
📦 關注點 | 對象復用與共享,分離內部狀態與外部狀態 | 快速創建新對象(特別是復雜結構) |
🧠 實現機制 | 享元工廠維護共享對象池,通過傳入外部狀態來復用對象 | 使用 方法或拷貝構造函數復制現有對象 |
📂 狀態管理 | 內部狀態共享,外部狀態由使用方維護 | 完整復制所有狀態(深拷貝/淺拷貝) |
📈 適合場景 | - 大量重復對象,如文字編輯器中的字符對象 | - 克隆原型對象 |
🧩 示例 | String Pool、Integer.valueOf、數據庫連接池 | 原型注冊器、工作流模板復制、前端組件克隆等 |
?? 使用注意點 | - 內外部狀態劃分要明確 | - 深拷貝需注意引用類型對象 |
6.1.2. ? 簡單總結
- 享元模式 = 節省內存、共享對象:適用于大量對象重復的場景。
- 原型模式 = 快速復制、提升性能:適用于快速創建復雜對象的場景。
6.1.3. 📌 舉個類比:
類比 | 描述 |
享元 | 比如一個圖書館的“圖書”是共享的,用戶借用的是引用,圖書本身不復制。 |
原型 | 比如一個表格模板,每次創建新文檔都是復制模板,然后修改。 |
博文參考
- 5. 享元模式 — Graphic Design Patterns
- 享元設計模式