cover

想像你在做一個文字編輯器。一篇文章有一萬個字,每個字都有字型、大小、顏色等資訊。如果每個字都獨立儲存這些資訊,記憶體就爆了。

但其實大部分字的字型和大小都一樣,不一樣的只是「內容」和「位置」。把共同的部分抽出來共用——這就是 Flyweight。

先講結論

Flyweight 把物件的狀態分成兩種:

  • Intrinsic(可共享):不隨上下文變化的,可以被多個物件共用(例如:交易類型名稱、描述)
  • Extrinsic(不可共享):每個物件獨有的(例如:交易金額、備註)

共享的部分用工廠管理,確保同樣的 intrinsic state 只建立一次。

classDiagram
    class TransactionType {
        -name : String
        -description : String
        +getInfo() String
    }
    class TransactionTypeFactory {
        -types : Map
        +getTransactionType(name, desc) TransactionType
    }
    class Transaction {
        -transactionType : TransactionType
        -details : String
        -amount : Number
        +getDescription() String
    }
    TransactionTypeFactory ..> TransactionType : 快取與回傳
    Transaction o-- TransactionType : 共享內部狀態

實戰:交易系統

Flyweight 模式:共享池提供重複使用的物件

// Flyweight:可共享的交易類型
class TransactionType {
    constructor(name, description) {
        this.name = name;
        this.description = description;
    }
    getInfo() { return `${this.name}: ${this.description}`; }
}
 
// 工廠:確保同一種 type 只建一次
class TransactionTypeFactory {
    constructor() { this.types = new Map(); }
 
    getTransactionType(name, description) {
        const key = `${name}_${description}`;
        if (!this.types.has(key)) {
            this.types.set(key, new TransactionType(name, description));
        }
        return this.types.get(key);
    }
}
 
// 每筆交易共用 TransactionType,但有自己的金額和備註
class Transaction {
    constructor(transactionType, details, amount) {
        this.transactionType = transactionType; // 共享
        this.details = details;                 // 獨有
        this.amount = amount;                   // 獨有
    }
    getDescription() {
        return `[${this.transactionType.getInfo()}] ${this.details} - $${this.amount}`;
    }
}
 
const factory = new TransactionTypeFactory();
 
// 一萬筆存款交易,但 TransactionType 只有一個實例
const depositType = factory.getTransactionType('存款', '將資金存入帳戶');
 
const tx1 = new Transaction(depositType, '薪資入帳', 50000);
const tx2 = new Transaction(depositType, '獎金入帳', 10000);
 
// 兩筆交易共用同一個 TransactionType
console.log(tx1.transactionType === tx2.transactionType); // true

什麼時候值得用 Flyweight?

當你有大量物件且它們之間有大量重複的共享狀態時。如果你只有幾十個物件,共享省下的記憶體根本感覺不到,不值得增加複雜度。

典型場景:文字編輯器的字符、遊戲裡的粒子和樹木、地圖上的圖釘樣式。

要付出什麼代價?

程式碼變複雜了——你需要區分哪些狀態可以共享、哪些不行,還要管理一個工廠。如果分錯了,共享的物件被某一方改掉,其他共用者全部跟著錯。


Flyweight 就像共享單車——車子(intrinsic)是公用的,但你騎去哪裡(extrinsic)是你的事。只是有些人把共享單車騎回家就不還了


延伸閱讀