享元模式核心思想:將對象的內部狀態(不變、可共享)與外部狀態(變化、不可共享)分離,建立享元池複用內部狀態對象。適用於大量相似對象場景:文檔字符、地圖瓦片、數據庫連接池、線程池、字符串常量池。本文從結構、實現、XML 數據處理實戰三維度深度解析。
| 角色 | 職責 | 關鍵點 |
|---|---|---|
| Flyweight (抽象享元) | 定義操作接口,接受外部狀態參數 | void operation(ExtrinsicState state) |
| ConcreteFlyweight (具體享元) | 實現接口,存儲內部狀態,必須可共享 | 線程安全、無外部狀態依賴 |
| UnsharedConcreteFlyweight (非共享享元) | 不進入池,每次新建,可攜帶外部狀態 | 組合模式葉子節點常用此類 |
| FlyweightFactory (享元工廠) | 維護享元池,getFlyweight(key) 獲取/創建 | 池通常用 Map/ConcurrentHashMap |
| Client (客戶端) | 持有外部狀態,調用工廠獲取享元,調用操作 | 負責計算/維護外部狀態 |
// 1. 外部狀態接口(自定義)
public interface ExtrinsicState { }
// 2. 抽象享元
public interface Flyweight {
void operation(ExtrinsicState state);
}
// 3. 具體享元(可共享)
public class ConcreteFlyweight implements Flyweight {
private final IntrinsicState intrinsicState; // 內部狀態,final 不變
public ConcreteFlyweight(IntrinsicState intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void operation(ExtrinsicState extrinsicState) {
// 結合內部狀態 + 外部狀態執行業務邏輯
}
}
// 4. 非共享享元
public class UnsharedConcreteFlyweight implements Flyweight {
@Override
public void operation(ExtrinsicState state) { /* 獨享邏輯 */ }
}
// 5. 享元工廠
public class FlyweightFactory {
private final Map
FlyweightFactory factory = new FlyweightFactory();
// 獲取共享享元(內部狀態由 key 決定)
Flyweight fly1 = factory.getFlyweight("Fred"); // 內部狀態="Fred"
Flyweight fly2 = factory.getFlyweight("Wilma"); // 內部狀態="Wilma"
// 客戶端維護外部狀態
ExtrinsicState context1 = new ExtrinsicStateImpl(/* 具體上下文 */);
ExtrinsicState context2 = new ExtrinsicStateImpl(/* 不同上下文 */);
// 調用操作:享元不保存外部狀態,每次傳入
fly1.operation(context1);
fly2.operation(context2);
場景:解析包含大量重複歌手信息的 CD 目錄 XML,歌手名重複率高。
<catalog>
<cd>
<year>2023</year>
<artist>周杰倫</artist> <!-- 高頻重複 -->
<title>最偉大的作品</title>
</cd>
<cd>
<year>2022</year>
<artist>周杰倫</artist> <!-- 共享享元候選 -->
<title>說好不哭</title>
</cd>
...
</catalog>
| 字段 | 類型 | 享元角色 |
|---|---|---|
| artist (歌手) | 高重複 | ConcreteFlyweight — 共享享元池 |
| year (年份) | 低重複 | UnsharedConcreteFlyweight / 外部狀態 |
| title (專輯) | 唯一 | 外部狀態 / 非享元 |
// 內部狀態:歌手信息
public class Artist implements IntrinsicState {
private final String name;
private final String debutYear;
private final String genre;
public Artist(String name, String debutYear, String genre) {
this.name = name;
this.debutYear = debutYear;
this.genre = genre;
}
// getters...
}
// 外部狀態:專輯上下文
public class AlbumContext implements ExtrinsicState {
private final int year;
private final String title;
public AlbumContext(int year, String title) { ... }
}
// 享元工廠:解析時構建
public class ArtistFlyweightFactory {
private final Map pool = new ConcurrentHashMap<>();
public Artist getArtist(String name, String debutYear, String genre) {
return pool.computeIfAbsent(name,
n -> new Artist(name, debutYear, genre));
}
}
// 解析器片段
public class CdCatalogParser {
private final ArtistFlyweightFactory artistFactory = new ArtistFlyweightFactory();
public List parse(Document doc) {
List records = new ArrayList<>();
NodeList cdNodes = doc.getElementsByTagName("cd");
for (int i = 0; i < cdNodes.getLength(); i++) {
Element cd = (Element) cdNodes.item(i);
String artistName = getText(cd, "artist");
String debutYear = getText(cd, "debutYear"); // 假設 XML 有
String genre = getText(cd, "genre");
// 獲取/創建共享歌手享元
Artist artist = artistFactory.getArtist(artistName, debutYear, genre);
// 構建外部狀態
AlbumContext ctx = new AlbumContext(
Integer.parseInt(getText(cd, "year")),
getText(cd, "title")
);
// 記錄引用享元 + 外部狀態
records.add(new CdRecord(artist, ctx));
}
return records;
}
}
// 最終記錄:輕量引用
public class CdRecord {
private final Artist artist; // 共享享元引用
private final AlbumContext context; // 外部狀態
// getters...
}
| 指標 | 無享元 | 享元模式 | 提升 |
|---|---|---|---|
| 10萬條記錄,1000位歌手 | 10萬 Artist 對象 | 1000 Artist 對象 | 內存減 99% |
| GC 壓力 | 高(大量短命對象) | 極低(池化長期存活) | GC 停頓大幅下降 |
| 解析速度 | 基準 | 快 20-30%(少 new、少 GC) | 顯著提升 |
== 比較),只依賴內容(equals())ConcurrentHashMap,享元類不可變享元模式是「以時間換空間」的經典權衡——用工廠查找/計算外部狀態的 CPU 時間,換取巨大的內存節省與 GC 壓力降低。在大數據、高併發、嵌入式場景,威力驚人。