# 12. Flyweight ###### tags: `DesignPatterns` ## 關於 Flyweight 本篇將討論以下幾個問題 > ### 1. 關於 Flyweight > ### 2. UML > ### 3. 將 UML 轉為程式碼 > ### 4. 情境 --- ## 測試環境: >OS:Windows 10 >IDE:Visual Studio 2019 --- ## 1. 關於 Flyweight > Use sharing to support large numbers of fine-grained objects efficiently. > > by [Gang of Four](https://en.wikipedia.org/wiki/Design_Patterns) - 用來有效率的共享大量細粒度的對象 Flyweight(享元)屬於結構型(Structural Patterns),當遇到**系統中頻繁創建且只會異動狀態而不改變物件本身**的小物件時,可藉由 Flyweight 來緩存物件,提供不同呼叫端重複使用,達到節省記憶體的目的。 優點: - 藉由共用相似物件來達到節省記憶體 缺點: - 使用 Flyweight 的物件存於緩存中,就算沒使用也不會被 GC 回收 --- ## 2. UML ![](https://i.imgur.com/VzKnYvl.jpg) Class 間關聯: - FlyweightFactory 可包含 Flyweight - Client 關聯 FlyweightFactory & ConcreteFlyweight - ConcreteFlyweight 繼承 Flyweight Class: - Client:呼叫端 - FlyweightFactory:以工廠方法實作享元工廠 - Flyweight:享元的抽象類別或介面 - ConcreteFlyweight:享元實作 --- ## 3. 將 UML 轉為程式碼 享元介面 ```C# /// <summary> /// 享元介面 /// </summary> public interface IFlyweight { void Operation(int extrinsicState); } ``` 享元工廠 ```C# /// <summary> /// 享元工廠 /// </summary> public class FlyweightFactory { private Hashtable flyweights = new Hashtable(); public FlyweightFactory() { flyweights.Add("One", new ConcreteFlyweight()); flyweights.Add("Two", new ConcreteFlyweight()); flyweights.Add("Three", new ConcreteFlyweight()); } public IFlyweight GetFlyweight(string key) { return ((IFlyweight)flyweights[key]); } } ``` 享元實作 ```C# /// <summary> /// 享元實作 /// </summary> public class ConcreteFlyweight : IFlyweight { public void Operation(int extrinsicState) { Console.WriteLine($"ConcreteFlyweight: {extrinsicState}"); } } ``` 1. 建立享元工廠`factory` 2. 取得享元工廠中已建立實體 3. 操作享元物件 ```C# static void Main(string[] args) { int extrinsicstate = 0; Default.FlyweightFactory factory = new Default.FlyweightFactory(); Default.IFlyweight flyweight_1 = factory.GetFlyweight("One"); flyweight_1.Operation(++extrinsicstate); Default.IFlyweight flyweight_2 = factory.GetFlyweight("Two"); flyweight_2.Operation(++extrinsicstate); Default.IFlyweight flyweight_3 = factory.GetFlyweight("Three"); flyweight_3.Operation(++extrinsicstate); Console.ReadLine(); } ``` 執行結果 ```Console ConcreteFlyweight: 1 ConcreteFlyweight: 2 ConcreteFlyweight: 3 ``` --- ## 4. 情境 我們接到了一個提供線上商城客戶結帳時轉換匯率的需求 - 線上商城客戶很多,每個客戶都要 new 匯率 class 很吃記憶體 - 匯率固定提供部分貨幣類別,介面相同 貨幣類別 Enum ```C# /// <summary> /// 貨幣類別 /// </summary> public enum CurrencyType { TWD, JPY, USD, EUR, } ``` 實作匯率工廠,並在建構子提供對應匯率 ```C# /// <summary> /// 匯率工廠 /// </summary> public class ExchangeRatesFactory { private Hashtable flyweights = new Hashtable(); public ExchangeRatesFactory() { flyweights.Add(CurrencyType.TWD, new ExchangeRates((decimal)1)); flyweights.Add(CurrencyType.JPY, new ExchangeRates((decimal)0.273)); flyweights.Add(CurrencyType.USD, new ExchangeRates((decimal)28.235)); flyweights.Add(CurrencyType.EUR, new ExchangeRates((decimal)34.35)); } public IExchangeRates GetConverter(CurrencyType currencyType) { return ((IExchangeRates)flyweights[currencyType]); } } ``` 轉換介面 ```C# /// <summary> /// 轉換介面 /// </summary> public interface IExchangeRates { public decimal Converter(int amount); } ``` 傳換實作 ```C# /// <summary> /// 傳換實作 /// </summary> public class ExchangeRates : IExchangeRates { private decimal _CashRate { get; } public ExchangeRates (decimal cashRate) { _CashRate = cashRate; } public decimal Converter(int amount) { var newAmount = amount / _CashRate; return decimal.Parse(newAmount.ToString("#0.00")); } } ``` 1. 建立享元工廠`factory` 2. 取得享元物件(匯率轉換) 3. 透過享元物件計算金額 ```C# static void Main(string[] args) { Situation.ExchangeRatesFactory factory = new Situation.ExchangeRatesFactory(); int amount = 100; Situation.IExchangeRates converter_JPY = factory.GetConverter(Situation.CurrencyType.JPY); var jpy = converter_JPY.Converter(amount); Console.WriteLine($"TWD 轉換至 JPY:{amount} → {jpy}"); Situation.IExchangeRates converter_USD = factory.GetConverter(Situation.CurrencyType.USD); var usd = converter_USD.Converter(amount); Console.WriteLine($"TWD 轉換至 USD:{amount} → {usd}"); Situation.IExchangeRates converter_EUR = factory.GetConverter(Situation.CurrencyType.EUR); var eur = converter_EUR.Converter(amount); Console.WriteLine($"TWD 轉換至 EUR:{amount} → {eur}"); Console.ReadLine(); } ``` 執行結果 ```Console TWD 轉換至 JPY:100 → 366.30 TWD 轉換至 USD:100 → 3.54 TWD 轉換至 EUR:100 → 2.91 ``` --- ## 完整程式碼 GitHub:[Structural_06_Flyweight](https://github.com/darionnnnnn/blog/tree/master/Blog/Structural_06_Flyweight) --- ## 總結 ### Flyweight 屬於結構型,重點是重複使用而並非創建,所以創建的部分使用工廠來處理。 Flyweight 的物件是全部呼叫端共用,若是可修改內部狀態,則可能會造成 A 處理到一半時狀態被 B 修改,而造成 A 的結果與預期不同,這問題也許開發測試期間未必會發現,等到實際上線時才發現已經亂成一團了。 --- ## 參考資料 1. [Design Patterns](https://en.wikipedia.org/wiki/Design_Patterns) 2. [大話設計模式](https://www.tenlong.com.tw/products/9789866761799) 2. [dofactory](https://www.dofactory.com/) 3. [Refactoring.Guru](https://refactoring.guru/) --- ## 新手上路,若有錯誤還請告知,謝謝