理论结合实践
上节说了一下基本的理论知识,例子可能不太好,不过无所谓了,目的是要让大家明白啥是依赖倒置和依赖注入,目的就达到了,简单一句话,这2玩意都是用来解耦合的。
不过依赖倒置这个词哥哥真不敢苟同,哥哥来个颠覆的说法,我说这是依赖正置。
因为本来就应该是上层依赖上层嘛,低层也应该依赖上层,但是由于程序语言的原因,导致代码和实际完全不符合,搞得抽象经常依赖具体,具体更是依赖具体。
体现在代码中就是接口中关联类型,类型中也关联类型。完全反了。所以我们呢要让他正常起来,让接口只依赖接口,类也只依赖接口,这个实际比较相符合,所以哥哥我叫他依赖正置。
说了依赖正置,接下来再说说控制反转IOC,我们要说的autofac就是是IOC的一个框架。
啥是控制反转吧?你想啊,我们一般写代码是这样:举个去死的栗子
public interface IPerson { void GoToHell(IPlace hell); } public class CodeFarmer : IPerson { public void GoToHell(IPlace hell) { hell.Accept(this); } } public interface IPlace { void Accept(IPerson person); } public class Hell : IPlace { public void Accept(IPerson person) { // do sth.. } } public class God { public void Do() { IPerson you = new CodeFarmer(); you.GoToHell(new Hell()); } }
看,咱们是没有违反基本的DIP原则吧,基本都是依据接口编程的。
但是还是老话,在客户端god哪里还是有问题,他要你去死,还必须要知道hell的创建逻辑,god表示你这东西简直不能用,我只是要你去死而已呀,我还要给你指明通向hell的道路?
god表示很忙!把hell的初始化放在person 中如何?可以,不是有句话说,人一出生,就是坐上了通往死亡的列车,谁说的?好像是我自己!!
public interface IPerson { IPlace Hell { get; set; } void GoToHell(); } public class CodeFarmer : IPerson { public IPlace Hell { get; set; } public CodeFarmer() { Hell = new Hell(); } public void GoToHell() { Hell.Accept(this); } } public interface IPlace { void Accept(IPerson person); } public class Hell : IPlace { public void Accept(IPerson person) { // do sth.. } } public class God { public void Do() { IPerson you = new CodeFarmer(); you.GoToHell(); } }
这样,你自己往hell走就行了,god表示我是老板,我只发指令给你,具体的路线你自己去搞!
但是CodeFarmer就不忙?最后要死了都要自己找路去死?简直不能干,这个行业!那怎么办?这个new Hell()的部分放在那里好呢?
其实这个就是我们程序里面常见的问题,依赖的具体对象创建到哪里注入比较好呢?好像哪里都不符合逻辑!
那既然这样,那创世者说我发个公告牌如何?我就告诉你,这个地方有个公告板,里面告诉你了地狱怎么走,你要去的话,你自己去看看就得了,没必要自己摸路。
代码如下:
public interface IPerson { void GoToHell(IPlace hell); } public class CodeFarmer : IPerson { public void GoToHell(IPlace hell) { hell.Accept(this); } } public interface IPlace { void Accept(IPerson person); } public class Hell : IPlace { public void Accept(IPerson person) { // do sth.. } } public class God { public void Do() { IPerson you = InfoBoard.PlaceInfo["person"]; you.GoToHell(InfoBoard.PlaceInfo["hell"]); } } public class InfoBoard { public static Dictionary<string, IPlace> PlaceInfo = new Dictionary<string, IPlace>(); static InfoBoard() { PlaceInfo.Add("hell", new Hell()); PlaceInfo.Add("person", new CodeFarmer()); } }
这样,不管是在god中,还是在codefamer中,直接可以使用InfoBoard.PlaceInfo["hell"]来去到通往地狱的路,有指明灯多好啊。
比较官方的语言:应用控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用,传递给它。也可以说,依赖被注入到对象中。所以,控制反转是,关于一个对象如何获取他所依赖的对象的引用,这个责任的反转。
怎么样,真jb难懂是吧?
我就给你翻译成人类能懂的话,你不是要面向接口编程吗?你接口,你TM最后总要有new的时候吧?你一旦new了,一个对象和另一个对象实际上的强依赖是不是产生了?既然这样,那我们都不new,由一个在程序看来虚无缥缈的地方保存有所有抽象类和接口对象的具体实现。从哪里取就行了,这样可好?那我和你永远只关心接口就行了
就像上面的栗子中,不管是god中还是codefarmer还是hell中,是不是没有任何创建彼此的代码?是不是这个耦合彻底解除了(或者说是转移了到InfoBoard中了,便于集中管理,前文有提到)?
我告诉你,这个就是一个最简单的ioc容器。控制反转,我还是那句话,我自己给他定义一个名词,控制转移比较好!谈不上什么正反,这个!!
那有的同学问,你TM这用你昨天讲的反射不是可以达到一样的效果?你说你玩这个,你玩他有什么用啊?我想说的是,就是可以达到一样的效果!!但是,我只多说一个词,性能!其他的我不多讲。如果你的系统没那么多jjyy的性能的事情,完全用反射就行了
那IOC容器就这样就行了?肯定不行啊,举个栗子:
我们用ado访问数据库
public class InfoBoard { public static Dictionary<string, object> PlaceInfo = new Dictionary<string, object>(); static InfoBoard() { PlaceInfo.Add("conn", new SqlConnection("connect string")); PlaceInfo.Add("comm", new SqlCommand("SELECT *****************", InfoBoard.PlaceInfo["conn"] as SqlConnection)); } }
看如果我执行一次comm后实际上SqlConnection如果释放了,这个comm是不是没用了?
如果要保证comm有效这个SqlConnection是不是不能释放,这2种方式都不是我们想要的,所以,你必须要有个对象过期策略,和非托管资源的释放问题。
这个SqlConnection根被就不能写在静态构造中,实际的ioc框架还会遇到很多问题,这就是一个autofac代码茫茫多的原因
西门子哪个广告语,我一直拿着用,精于心,简于形,为了一点点的性能问题,背后的工作是巨大的,还好有现成的框架帮我们做了很多事情。
下面说下Autofac
下载,引用,或者通过NUGET方式获取框架,这些不多说。正常人都能搞定
using Autofac; public interface IPerson { IPlace Hell { get; set; } string Name { get; set; } void GoToHell(); } public class CodeFarmer : IPerson { public string Name { get; set; } public IPlace Hell { get; set; } public CodeFarmer(IPlace Hell) { this.Hell = Hell; } public void GoToHell() { Hell.Accept(this); } } public interface IPlace { void Accept(IPerson person); } public class Hell : IPlace { public void Accept(IPerson person) { Console.WriteLine(person.Name+" is gonna die"); Console.ReadKey(); } } class Program { private static IContainer Container { get; set; } static void InitApplication() { var builder = new ContainerBuilder(); builder.RegisterType<Hell>().As<IPlace>(); builder.RegisterType<CodeFarmer>().As<IPerson>(); Container = builder.Build(); } static void Main(string[] args) { InitApplication(); var aSadMan = Container.Resolve<IPerson>(); aSadMan.Name = "hard worker"; //var goodPlace = Container.Resolve<IPlace>(); aSadMan.GoToHell(); } }
主要看Program
private static IContainer Container { get; set; }
有个Container容器有没有?
builder.RegisterType<Hell>().As<IPlace>();
builder.RegisterType<CodeFarmer>().As<IPerson>();
往容器里面加东西有没有?
var aSadMan = Container.Resolve<IPerson>();
从容器里面取东西有没有?
那有的人就问了,你CodeFarmer里面的IPlace对应的hell是怎么实例化的?CodeFarmer的构造函数都没看到调用啊?
这里就是所谓的构造函数注入,你只需要知道,这样写,然后解析他的上层类IPerson,这个hell是自动实例化的,够造函数自动被调用了,里面的参数自动被解析成hell,因为你前面有往container中registertype过,这样就行了,是不是很强大呢?
public IPlace Hell { get; set; } public CodeFarmer(IPlace Hell) { this.Hell = Hell; }
如果Hell的构造里面还要注入其他的依赖,这个解析可以一直嵌套下去,无论有多少层,只要你从最上面的入口做了类似
var aSadMan = Container.Resolve<IPerson>();
的操作!
行了,先说到这,下回再扯吧,欢迎拍砖,往死里拍,上文有个错误,把依赖倒置说成了DI应该是dip,汗!因为,我写这些,例子都是我自己随便想的,基本上时间也仓促,难免有不完全对的地方,但是核心我都说明白了的,大家有问题可以提出来探讨,就是不要直接说有问题,但是不说明问题再那里,这样就没意思了!基本我都不会检查第2遍。人都说第一想到的东西都是最真实和正确的,有没有?