[Unity 設計模式]IOC依賴倒置
1.前言
最近在看《遊戲開發與設計模式》一書,看到控制反轉設計模式,作者說:上層模塊不應該依賴於下層模塊,上層模塊和下層模塊都應該依賴於接口,這樣能減少耦合。然後附帶舉了個例子,我覺得特別好,就是一臺計算機是屬於上層模塊,裏面硬盤屬於下層模塊,計算機依賴於硬盤,硬盤是計算機的基本組成部件之一。這裏提到依賴一詞,下面就詳細談談依賴。
2.依賴
依賴就是一種聯系關系,人對人的依賴那是一種羈絆關系。再拿上面的計算機舉例,華碩是我們都耳熟能詳的計算機廠商,西部數據和希捷都是硬盤廠商,如果說華碩依賴於某一個具體型號的硬盤,那麽華碩同一款型號的電腦那得生產多少臺內置硬盤不同的型號的電腦。假設華碩廠商喜歡用西部數據的硬盤,那好假如西部數據倒閉或者硬盤升級,那麽華碩廠商是不是會造成連鎖反應,華碩已經生產的電腦要重新回爐重造或者說要被迫改成使用其他希捷廠商的電腦。造成這種問題的原因就是華碩對西部數據有嚴重的依賴關系,解決方法就是硬盤廠商生產硬盤的時候遵循硬盤接口SATA接口協議,然後計算機廠商也遵循這種接口協議,這就是我們看到的PC具有好多接口例如我們熟知的USB接口,USB是更通用的接口,無論鼠標、鍵盤還是優盤都具備這樣的接口,所以只要遵循USB接口或者ASTA接口的設備都能用用在任何一個PC計算機上。我們如果寫代碼的話,也能這樣設計我們的代碼模塊,那麽模塊跟模塊之間耦合度就很低了。下面我們來看一個簡單的例子:
1 public class FlashDisk 2 { 3 public string TypeName { get; set; } 4 public void TransportData() 5 { 6 Console.WriteLine(string.Format("讀取{0}硬盤的數據")); 7 } 8 9 public FlashDisk(string name) 10 { 11 TypeName = name;View Code12 } 13 } 14 15 public class HardDisk 16 { 17 public string TypeName { get; set; } 18 public void TransportData() 19 { 20 Console.WriteLine(string.Format("讀取{0}硬盤的數據")); 21 } 22 23 public HardDisk(string name) 24 {25 TypeName = name; 26 } 27 } 28 public class Computer 29 { 30 public Computer(string name) 31 { 32 TypeName = name; 33 } 34 public void ReadHardDiskData(HardDisk m_HardDisk) 35 { 36 m_HardDisk.TransportData(); 37 } 38 39 public void ReadFlashDiskData(FlashDisk m_FlashDisk) 40 { 41 m_FlashDisk.TransportData(); 42 } 43 } 44 static void ReadData() 45 { 46 HardDisk hardDisk = new HardDisk("西部數據"); 47 FlashDisk flashDisk = new FlashDisk("閃迪"); 48 Computer pc = new Computer("聯想"); 49 pc.ReadHardDiskData(hardDisk); 50 pc.ReadFlashDiskData(flashDisk); 51 }
上面是一個簡單的示例,想要實現傳輸數據的功能,用戶的操作一個PC來讀取某個硬盤或者優盤才能實現。先看看依賴關系:
- Computer依賴HardWare
- ReadData操作依賴Computer
- ReadData操作依賴HardDisk/FlashDisk
3.依賴倒置
如果我們這時候用不用的電腦比如聯想來讀取金斯頓優盤的數據,我們就要抽象出來減少耦合。
耦合就是依賴,如果依賴過於嚴重就會牽一發而動全身,所謂城門失火,殃及池魚。依賴關系越少,維護就越容易,所以必須要減少依賴。
幸虧Robert Martin提出面向對象設計原則-依賴倒置原則:
- 上層模塊不應該依賴於下層模塊,他們共同依賴於一個抽象。
- 抽象不依賴於具體,具體依賴於抽象。
理解:
- 上層模塊就是使用者,這裏的PC使用優盤、硬盤,而後者自然就是被使用者就是下層模塊。他們應該都共同依賴於抽象——接口。
- 面向對象編程時需要面向抽象或者面向接口編程,抽象或者接口一般比較穩定,接口不要依賴於具體的對象。
根據控制反轉的原則我們來重新設計上的實現:
- 優盤或者硬盤都要遵循一個接口協議,這裏我們就定義ISATA和IUSB協議,
- 數據傳輸接口ISATA和IUSB都要遵循數據傳輸協議我們就定義ITransportData
- ReadData操作依賴PC,就讓ReadData和HardDisk/FlashDisk都依賴ISATA和IUSB協議,於是優化過的代碼就出來了
1 using System; 2 using System.Collections; 3 using System.Collections.Generic; 4 using UnityEngine; 5 6 public interface ITransportData 7 { 8 void TransPortData(); 9 } 10 11 public interface IUSB : ITransportData 12 { 13 string TypenName { get; set; } 14 } 15 16 public interface ISATA : ITransportData 17 { 18 string TypenName { get; set; } 19 } 20 21 public class UFlashDisk : IUSB 22 { 23 public string TypenName { get; set; } 24 25 public UFlashDisk(string typeName) 26 { 27 TypenName = typeName; 28 } 29 public void TransPortData() 30 { 31 UnityEngine.Debug.Log(string.Format("優盤型號:{0}傳輸數據", TypenName)); 32 } 33 } 34 35 public class HardDisk : ISATA 36 { 37 public string TypenName { get; set; } 38 39 public HardDisk(string typeName) 40 { 41 TypenName = typeName; 42 } 43 44 public void TransPortData() 45 { 46 UnityEngine.Debug.Log(string.Format("硬盤型號:{0}傳輸數據", TypenName)); 47 } 48 } 49 50 public class Computer 51 { 52 ISATA m_HardDisk; 53 54 public void SetHardWare(ISATA hardDisk) 55 { 56 m_HardDisk = hardDisk; 57 } 58 59 public void ReadData() 60 { 61 if (m_HardDisk == null) 62 throw new Exception(); 63 m_HardDisk.TransPortData(); 64 } 65 66 public void UseUFlashDisk(IUSB flashDisk) 67 { 68 flashDisk.TransPortData(); 69 } 70 } 71 72 public class IOCDemo : MonoBehaviour 73 { 74 void Start() 75 { 76 ISATA hardDisk = new HardDisk("西部數據硬盤"); 77 IUSB uFlashDisk = new UFlashDisk("閃迪優盤"); 78 var pc = new Computer(); 79 pc.SetHardWare(hardDisk); 80 pc.ReadData(); 81 pc.UseUFlashDisk(uFlashDisk); 82 pc.UseUFlashDisk(new UFlashDisk("西門子優盤")); 83 } 84 }View Code
4.控制反轉(IOC)
現實生活中,我們想要讀取數據,無論我們用什麽電腦,文件數據在哪個優盤裏,這都取決於我們用戶自己。
上面基本實現了隔離,具體的電腦跟優盤隔離,具體的優盤、硬盤跟接口有關系,如果我們對象在代碼裏面寫死,假如就用華碩電腦讀取閃迪優盤,這樣又不靈活了,如果需求是我用聯想電腦去讀取金斯頓優盤,我們是不是又要改代碼。這樣我們就可以把決定權交給我們配置的人,遊戲開發中這類人就稱之為策劃,我們就把權利給他們,讓他們想用什麽電腦讀取什麽優盤就讓他們來配表實現,然後程序寫一個讀表生成具體對象的讀取數據的操作。
這樣控制權交給了配表的文件,這種就是我們這裏要說的控制反轉。
1 public class Computer 2 { 3 public string Name { get; set; } 4 public Computer(string name) 5 { 6 Name = name; 7 } 8 9 ISATA m_HardDisk; 10 11 public void SetHardWare(ISATA hardDisk) 12 { 13 m_HardDisk = hardDisk; 14 } 15 16 public void ReadData() 17 { 18 if (m_HardDisk == null) 19 throw new Exception(); 20 m_HardDisk.TransPortData(); 21 } 22 23 public void UseUFlashDisk(IUSB flashDisk) 24 { 25 flashDisk.TransPortData(); 26 } 27 } 28 29 var pc = new Computer(“讀取配置表”); 30 pc.SetHardWare(new UFlashDisk(“讀取配置表”)); 31 pc.ReadData();View Code
控制反轉IOC是Inversion of control的縮寫,說將對象進行轉移,轉移到第三方,比如交給了配表文件。它就像一個對象創建工程,想要什麽對象就配置什麽對象,這樣原先的依賴關系沒有了,都變成IOC容器依賴了。
5.依賴註入(ID)
上面說的控制反轉是一個思想概念,要看具體的實現,配表也是一種實現方式。依賴註入提出了具體的思想。
依賴註入DI是Dependency Injection縮寫,它提出了“哪些東東的控制權被反轉了,被轉移了?”,它也給出了答案:“依賴對象的創建獲得被反轉”。
所謂依賴註入,就是由IoC容器在運行期間,動態地將某種依賴關系註入到對象之中。
上面SetHardWare其實就是運行時候依賴註入,將具體的依賴對象傳遞進去,傳遞的切入點可以是方法,可以是屬性,可以是構造函數。
6.小結
通過一個小例子來由淺入深講解IOC模式,看完之後記得自己在敲一遍哦,這樣感悟才深刻!
7.工程下載
https://git.oschina.net/dingxiaowei/UnityIOC.git
8.相關設計模式好文連接
http://gpp.tkchu.me/
[Unity 設計模式]IOC依賴倒置