1.享元模式介紹
在軟體開發中我們經常遇到多次使用相似或者相同物件的情況,如果每次使用這個物件都去new一個新的例項會很浪費資源。這時候很多人會想到前邊介紹過的一個設計模式:原型模式,原型模式通過拷貝現有物件來生成一個新的例項,使用拷貝來替代new。原型模式可以很好的解決建立多個相同/相似例項的問題,為什麼還要用享元模式呢?這是因為這兩種模式的使用場景是不同的,原型模式側重於”建立“,我們通過拷貝確確實實的建立了新的例項,它屬於建立型設計模式;而享元模式側重於“重用”,即如果有現有的例項就不去建立了,直接拿來用就行了。
下面以大頭兒子家開車為例介紹享元模式的用法。我們都知道大頭兒子家裡有三個人,這裡就不用介紹了,家裡現有一輛紅色車和一輛藍色車,小頭爸爸,扁頭媽媽和大頭兒子開車時都是用家裡現有的車,而不是每次開車都要新買一輛,只有想開的車家裡沒有時才會去買一輛,如大頭兒子想開白色的車,但家裡沒有白色的車,這時候才去買一輛回來。我們直接在程式碼中理解享元模式的用法:
抽象車類Car定義了具體車共有的介面方法Use,無論什麼車都就是可以用來開的,具體車類RealCar實現了Use介面。我們獲取Car的例項不是通過new來獲取,而是通過車庫CarFactory的GetCar方法來獲取,在GetCar方法中獲取車時,首先判斷車庫中是否存在我們想要的車,如果有直接拿來用,如果沒有才去買(new)一輛新車。
///抽象車類 public abstract class Car { //開車 public abstract void Use(Driver d); } /// <summary> /// 具體的車類 /// </summary> public class RealCar : Car { //顏色 public string Color { get; set; } public RealCar(string color) { this.Color = color; } //開車 public override void Use(Driver d) { Console.WriteLine($"{d.Name}開{this.Color}的車"); } } /// <summary> /// 車庫 /// </summary> public class CarFactory { private Dictionary<string, Car> carPool=new Dictionary<string, Car>(); //初始的時候,只有紅色和綠色兩輛汽車 public CarFactory() { carPool.Add("紅色", new RealCar("紅色")); carPool.Add("綠色", new RealCar("藍色")); } //獲取汽車 public Car GetCar(string key) { //如果車庫有就用車庫裡的車,車庫沒有就買一個(new一個) if (!carPool.ContainsKey(key)) { carPool.Add(key, new RealCar(key)); } return carPool[key]; } } /// <summary> /// 司機類 /// </summary> public class Driver { public string Name { get; set; } public Driver(string name) { this.Name = name; } }
客戶端呼叫:
class Program { static void Main(string[] args) { CarFactory carFactory = new CarFactory(); //小頭爸爸開藍色的車 Driver d1 = new Driver("小頭爸爸"); Car c1=carFactory.GetCar("藍色"); c1.Use(d1); //扁頭媽媽開藍色的車 Driver d2 = new Driver("扁頭媽媽"); Car c2 = carFactory.GetCar("藍色"); c2.Use(d2); if (c1.Equals(c2)) { Console.WriteLine("小頭爸爸和扁頭媽媽開的是同一輛車"); } //車庫沒有白色的車,就new一輛白色的車 Driver d3 = new Driver("大頭兒子"); Car c3 = carFactory.GetCar("白色"); c3.Use(d3); Console.ReadKey(); } }
執行程式結果如下:我們可以看到小頭爸爸和扁頭媽媽用的是同一輛車,就是複用了一個例項。
在使用享元模式時一個最大的問題是分離出物件的外部狀態和內部狀態。我們把物件內部的不會受環境改變而改變的部分作為內部狀態,如例子中車的顏色,車的顏色不會隨著外部因素司機的不同而改變;外部狀態指的是隨環境改變而改變的部分,對車來說,司機就是外部狀態,我們可以通過公共介面的引數來傳入外部狀態。
2.小結
上邊例子的類圖
享元模式的使用場景:
當系統中大量使用某些相同或者相似的物件,這些物件要耗費大量的記憶體,並且這些物件剔除外部狀態後可以通過一個物件來替代,這時可以考慮使用享元模式。在軟體系統中享元模式大量用於各種池技術,如資料庫連線物件池,字串快取池,HttpApplication池等。
享元模式的優點:
通過物件的複用減少了物件的數量,節省記憶體。
享元模式的缺點:
需要分離物件的外部狀態和內部狀態,使用不當會引起執行緒安全問題,提高了系統的複雜度。