委托、IOC全知道
话说写代码已有数年,曾经花了很多时间,看了很多大牛的文章也是不能参透,日思夜想都没有理解的概念,通过不断的实践与学习,回过头来再看,总算有了一个清晰的理解与认识,也看到一句话说,最好的学习就是把别人教会,因此想把这些曾经我很难理解的知识以我认为最易理解的形式分享出来。
之所以把委托和IOC放在一起,是因为下文这一个场景可以解释这两个概念。
1)委托
这里只谈委托的使用场景,不谈那些令人烦恼的概念。
比如,你所参与的项目需要一个功能,需要实现在互联网上下载音频文件。这个时候,你要写一个下载类,下载完成后,还要实现播放、备份这两个操作。这个下载类是只有下载一个功能,至于播放和备份,是另外两个类需要做的事。
这种场景,用委托是最合适的了,也很利于以后的扩展,下载完成后,还可能有播放提示音、关机等等的操作。
至于.net里面的事件,也是这样用的,这里下载完成就相当于一个事件,很多框架里也是这样用的。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ConsoleApplication1 { public delegate void DownloadFinish(string file); class DownLoader { public DownloadFinish DownloadFinishHandler; public void Download() { Console.WriteLine("开始下载..."); Thread.Sleep(3000); Console.WriteLine("下载完成"); string downloadedfile = @"c:\fly.mp3"; if (DownloadFinishHandler != null) { DownloadFinishHandler(downloadedfile); } } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ConsoleApplication1 { class MusicPlayer { public void Play(string file) { Console.WriteLine("开始播放文件 " + file); Thread.Sleep(2000); Console.WriteLine("播放文件完成"); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ConsoleApplication1 { class FileBak { public void Back(string file) { Console.WriteLine("开始备份文件"+file); Thread.Sleep(2000); Console.WriteLine("备份文件完成"); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { //委托演示 var downloader = new DownLoader(); downloader.DownloadFinishHandler += new FileBak().Back; downloader.DownloadFinishHandler += new MusicPlayer().Play; downloader.Download(); Console.Read(); } } }
2)IOC(依赖注入)
这里设定的场景是,下载音乐,然后播放。
先看一下没有IOC时的写法。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ConsoleApplication2 { class DownLoader { public void Download() { Console.WriteLine("开始下载..."); Thread.Sleep(3000); Console.WriteLine("下载完成"); string downloadedfile = @"c:\fly.mp3"; var player = new MusicPlayer(); player.Play(downloadedfile); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ConsoleApplication2 { class MusicPlayer { public void Play(string file) { Console.WriteLine("开始播放文件 " + file); Thread.Sleep(2000); Console.WriteLine("播放文件完成"); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication2 { class Program { static void Main(string[] args) { //无IOC演示 var downloader = new DownLoader(); downloader.Download(); Console.Read(); } } }
可以看到下载类和音乐播放类耦合了。
再看用构造函数注入后,也就是使用IOC后的写法。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ConsoleApplication3 { class DownLoader { private IMusicPlayer musicPlayer; public DownLoader() { } public DownLoader(IMusicPlayer player) { musicPlayer = player; } public void Download() { Console.WriteLine("开始下载..."); Thread.Sleep(3000); Console.WriteLine("下载完成"); string downloadedfile = @"c:\fly.mp3"; if (musicPlayer != null) { musicPlayer.Play(downloadedfile); } } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication3 { interface IMusicPlayer { void Play(string file); } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ConsoleApplication3 { class MusicPlayer : IMusicPlayer { public void Play(string file) { Console.WriteLine("开始播放文件 " + file); Thread.Sleep(2000); Console.WriteLine("播放文件完成"); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication3 { class Program { static void Main(string[] args) { //IOC演示 var player = new MusicPlayer(); var downloader = new DownLoader(player); downloader.Download(); Console.Read(); } } }
这个时候,多了个一个接口,下载类在编译时依赖这个接口,实例化时,再把具体的类传给它。
2004年,Martin Fowler探讨了同一个问题,既然IOC是控制反转,那么到底是“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。他的这个答案,实际上给出了实现IOC的方法:注入。所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。