我們首先通過一段小故事來了解為什么要使用IOC。
有一天我的老師燕小燕走進課堂,在黑板上寫了一段程序,我有一台電腦,但是不能播放音樂,你們能不能寫一個驅動讓其具備播放音樂的功能。
1 /// <summary> 2 /// 我的電腦 3 /// </summary> 4 public class Computer 5 { 6 7 }
冥思苦想后我開發了一個多媒體驅動程序用於識別並播放mp3。

1 /// <summary> 2 /// 我的電腦 3 /// </summary> 4 public class Computer 5 { 6 MediaDriver driver = new MediaDriver(); 7 8 /// <summary> 9 /// 播放 10 /// </summary> 11 public void PlayMedia() 12 { 13 driver.LoadMedia(new Mp3()); 14 driver.Play(); 15 } 16 } 17 18 /// <summary> 19 /// 電腦多媒體驅動 20 /// </summary> 21 public class MediaDriver 22 { 23 private Mp3 _mp3 = null; 24 public void LoadMedia(Mp3 mp3) 25 { 26 _mp3 = new Mp3(); 27 //dosomething 28 } 29 public void Play() 30 { 31 _mp3.Play(); 32 } 33 } 34 35 /// <summary> 36 /// mp3文件 37 /// </summary> 38 public class Mp3 39 { 40 /// <summary> 41 /// 播放 42 /// </summary> 43 public void Play() 44 { 45 //播放音樂 46 } 47 }
於是我便迫不及待向老師展示我的成果,老師看后首先誇獎了我一番,但是隨即向我提出來一個問題,你的這個驅動寫的確實是沒問題,但是如果我以后想觀看mp4視頻該怎么辦呢?
我:我可以再編寫一個支持播放mp4的驅動程序啊!
燕小燕:那如果后面還有其他的媒體文件該怎么辦呢?你總不可能每個媒體文件的播放都需要一個驅動程序吧,那最后這個電腦上安裝的都是各種媒體的驅動程序。現在你的問題是你的驅動中嚴重依賴了Mp3的類。
我:嗯,有道理。
由於mp3和mp4都是媒體文件,所以我對他們分析后抽象出了一個IMedia的接口。於是乎我對驅動進行了改造。

1 /// <summary> 2 /// 我的電腦 3 /// </summary> 4 public class Computer 5 { 6 MediaDriver driver = new MediaDriver(); 7 8 /// <summary> 9 /// 播放 10 /// </summary> 11 public void PlayMedia() 12 { 13 driver.LoadMedia(new Mp3()); 14 driver.Play(); 15 16 driver.LoadMedia(new Mp4()); 17 driver.Play(); 18 } 19 } 20 21 /// <summary> 22 /// 電腦多媒體驅動 23 /// </summary> 24 public class MediaDriver 25 { 26 private IMedia _media = null; 27 public void LoadMedia(IMedia media) 28 { 29 _media = media; 30 //dosomething 31 } 32 public void Play() 33 { 34 _media.Play(); 35 } 36 } 37 38 /// <summary> 39 /// 媒體接口 40 /// </summary> 41 public interface IMedia 42 { 43 void Play(); 44 } 45 46 /// <summary> 47 /// mp3文件 48 /// </summary> 49 public class Mp3: IMedia 50 { 51 /// <summary> 52 /// 播放 53 /// </summary> 54 public void Play() 55 { 56 //播放音樂 57 } 58 } 59 60 /// <summary> 61 /// mp4文件 62 /// </summary> 63 public class Mp4: IMedia 64 { 65 /// <summary> 66 /// 播放 67 /// </summary> 68 public void Play() 69 { 70 //播放視頻 71 } 72 }
老師:嗯,不錯,這樣的話,不管以后還有其他的媒體文件,只要繼承自IMedia,那么就可以使用這個驅動進行播放了。但是還有一個問題,每次播放需要手動在電腦類中進行實例化mp3\mp4類非常的不方便,萬一我們要播放mp5,那不可能把電腦拆掉然后修改他的PalyMedia方法吧,換句話說,你的電腦類嚴重依賴了mp3、mp4的類。
為了解耦電腦對於Mp3類的直接依賴,於是我想能不能電腦只是發起一個指令表達自己需要什么對象,不需要關心對象從何而來,由其他的組件把需要的實例化對象返回給他,那么電腦需要什么告訴這個組件就行了,但是為了讓這個組件知道要實例化什么對象返回給我,那我也肯定要在初始化這個組件的時候告訴他我需要的對象與實例化的對象之間的關系,所以必須有一個關系注冊的環節,帶着這些思考於是我又對代碼做了一些修改。

1 /// <summary> 2 /// 我的電腦 3 /// </summary> 4 public class Computer 5 { 6 private MediaDriver _driver; 7 public Computer() 8 { 9 _driver = new MediaDriver(); 10 IocContainer.Instance.Register(); 11 } 12 13 /// <summary> 14 /// 播放 15 /// </summary> 16 public void PlayMedia(IMedia media) 17 { 18 _driver.LoadMedia(media); 19 _driver.Play(); 20 } 21 22 public interface IMP3 : IMedia { } 23 public interface IMP4 : IMedia { } 24 25 /// <summary> 26 /// mp3文件 27 /// </summary> 28 public class Mp3: IMedia,IMP3 29 { 30 /// <summary> 31 /// 播放 32 /// </summary> 33 public void Play() 34 { 35 //播放音樂 36 Console.WriteLine("播放音樂"); 37 } 38 } 39 40 /// <summary> 41 /// mp4文件 42 /// </summary> 43 public class Mp4: IMedia, IMP4 44 { 45 /// <summary> 46 /// 播放 47 /// </summary> 48 public void Play() 49 { 50 //播放視頻 51 Console.WriteLine("播放視頻"); 52 } 53 } 54 55 /// <summary> 56 /// IOC容器 57 /// </summary> 58 public class IocContainer 59 { 60 private static IocContainer _container; 61 private Dictionary<Type, Type> _regDic; 62 public static IocContainer Instance 63 { 64 get 65 { 66 if(_container==null) 67 { 68 _container = new IocContainer(); 69 _container._regDic = new Dictionary<Type, Type>(); 70 71 } 72 return _container; 73 } 74 } 75 private IocContainer() { } 76 77 public void Register() 78 { 79 _container.Register<IMP3, Mp3>(); 80 _container.Register<IMP4, Mp4>(); 81 } 82 83 /// <summary> 84 /// 獲取實例對象 85 /// </summary> 86 /// <typeparam name="T"></typeparam> 87 /// <returns></returns> 88 public T GetService<T>() where T : class 89 { 90 if (_regDic.TryGetValue(typeof(T), out Type value)) 91 //生成T的實例對象返回 92 return (T)Activator.CreateInstance(value); 93 return null; 94 } 95 96 /// <summary> 97 /// 注冊 98 /// </summary> 99 /// <typeparam name="T"></typeparam> 100 /// <typeparam name="Implement"></typeparam> 101 public void Register<T, Implement>() 102 where T : class 103 where Implement : T 104 { 105 _regDic.Add(typeof(T), typeof(Implement)); 106 } 107 }
我:現在可以了,Computer類不再直接依賴於mp3類,而是直接依賴IocContainer類,需要獲取mp3的實例對象時只需使用IocContainer.Instance.GetService<IMP3>()即可得到。
其實到現在為止,上面的IocContainer類就算是一個簡單的IOC容器了。我們通過這個IOC容器實現了控制反轉,讓電腦類和驅動類不直接依賴mp3、mp4等多種媒體類型,而是依賴外部的容器,實現了中上層程序對下層程序的解耦。
再回過頭來,我們看一下什么是IOC?
IOC(Inversion of Control)即控制反轉,它是一種編程思想,是一個重要的面向對象編程的法則。用來消減程序之間的耦合問題,把中上層程序內部對下層依賴,轉移到一個外部來裝配。
IOC是程序設計的目標,實現方式包含依賴注入和依賴查找,在.net中只有依賴注入。
IOC容器主要用於解決中上層對下層的直接依賴。讓程序中上層對下層控制權從主動轉為被動,程序從原來的主動生成需要對象轉為只需告訴自己需要什么對象,而不管對象生成的具體實現,一切由IOC自動生成並返回程序需要的對象。除了控制反轉實現解耦,同時還實現了依賴注入(DI),可以看到我們上面生成實例對象是通過IocContainer.Instance.GetService<>()的方式手動生成,而通過IOC容器的注入,開發人員可以通過注入的方式實現自動生成實例對象。
一個完整的IOC容器會為我們實現控制反轉、依賴注入還有生命周期的管理。
依賴注入有三種形式:構造函數注入、屬性注入、接口注入。用的最多的就是構造函數注入,其他兩種用的較少。
上面編寫的代碼只是為了講解控制反轉的思想,不恰當和不完善的地方希望大家不要較真。這里也不再過多介紹容器的用法了,目前IOC容器已經有很多現成可以使用的,比如用的比較多的就是Unity和Autofac,大家可以網上自行搜索。