我用段子讲.NET之依赖注入(一)
1)
西城的某个人工湖畔,湖水清澈见底,湖畔柳树成荫。人工湖往北,坐落着两幢写字楼,水晶大厦靠近地铁站,由于为了与湖面天际线保持一致,楼层只有26层高,但外观非常好看,水蓝色的玻璃幕墙,波纹状的外观装饰,无处不洋溢着青春时尚。而另一座创新大厦与其隔河相对,楼层相对较高,设计风格偏中规中矩,外观朴实无华,标准的写字楼风格,与水晶大厦相比,虽然没有好看的外表,但却显得厚重充满内涵。
年轻程序员木哥和老程序员董哥,他们就分别在这两座写作楼中工作,他们也将作为本公众号未来【我用故事讲.NET】系列文章的主角出现。当然,他们都是小编自己塑造出来的人物形象,如有雷同,纯属巧合。
小木是一位.NET工程师,年轻而风华正茂,从大学毕业起就一直从事该领域,迄今已经约莫三年有余,他也是一位技术爱好者,经常追逐于各种新颖的互联网技术和概念,加了很多技术社区,经常参加各种大佬组织的技术沙龙,当然,他也自感技术之无止境,经常感慨新技术刷新飞快。
而董哥则是一位底层工程师,他今年将近40岁,四十而不惑,在这个35岁就淘汰的年代,他并没有被时代淘汰,而是顽强的奋斗着。
他外表稳重质朴,长时间在办公室工作,显得皮肤格外白皙,事实上其他人第一次见到他就能猜到他一定是一位技术老司机,因为头顶上有一小块区域已经开始出现了水土流失的迹象。他在电气制造领域和.NET底层应用方面拥有丰富的经验,虽然董哥的主业是偏嵌入式方向,但他对.NET也同样充满兴趣。
二人因为一次偶然的机会互相认识,共同的技术体系和兴趣爱好,加之办公地点也离得不远,他们经常碰面,互相讨论技术问题。
以上都是背景。
2)
最近小木同学发现越来越多的技术博客都在介绍微软新出的技术框架.NET Core,虽然他们公司的主要产品体系依然还是基于.NET Framework,但作为技术爱好者的他也按耐不住内心的激情,早就按照官方的教程,在自己本地安装了最新版的开发工具Visualstudio2019。
他准备摩拳擦掌,大干一场,好好的把该技术消化好,尽快在公司完成一波输出,成为带领公司技术革新的带头人。
这几天他的心情特别好,激情澎湃,充满了创造一番新事业的热情。某个周日,他打算开始动手了。他首先把dotnetcore的sdk部署好,然后开始按照官方的步骤,准备开始构建自己的第一个asp.netcore应用。
他给自己准备了一个项目作为实例,按照结构化方法的标准步骤,编写了一套用于开发该实例项目的设计文档,在这个项目中,他设计了几个简单的业务表,并按照官方教程的步骤编写了几个控制器和业务逻辑层及数据访问层,虽然他听人说过单元测试是实现代码效率提升的好办法,但他还不会用,所以只能按照最传统的方法=》直接进行代码调试。
他的电脑运行速度很快,所以环境很快就跑起来了,可是他运行第一个接口方法就报错了,提示【对象实例化失败,未能创建指定的对象】,这是啥意思?触及了啥未知领域?难道.netcore框架还不成熟,还是新的技术体系不能直接这么用了?
他去网上寻求解决方案,并很快就搜到了,原来他没有正确的使用依赖注入框架,由于他的业务逻辑层未在应用程序启动时配置注入方式,使得控制器调用该逻辑层时,出现了实例化失败的错误。他按照网上的方法很快就解决了该问题,并能够正常的开始进行代码调试了。
可是,这个问题始终困扰着他,为啥.net core里面的业务开发都会先写一个接口,并通过依赖注入的方式来实现类的加载,这样的操作难道不累吗?这个依赖注入究竟是啥意思,对我们crud工程师来说,有啥意义呢?
他决定上班时,找董哥去请教一番。
3)
次日,他跟董哥约好了下班后去拜访他,并在6点左右如约来到董哥的办公室。
董哥的办公室坐落在创新大厦的顶楼,窗外正对人工湖,还挺好看风景的,也是一个思考人生的好去处。董哥今天身着他们公司的工装,正在电脑旁思考着啥技术问题,看到小木同学进来,微笑着迎接他进来。今天的小木同学没有心情看风景,单刀直入,一落座就向董哥抛出了前面几个问题。
董哥微微一笑,拿出一张草稿纸,并在草稿纸上绘下了一个图案,画完后,将纸递给小木同学,并微笑着说:
“你先看下这张图,我给你倒一杯水,再慢慢解释”。
小木同学接过草稿纸,仔细打量了起来,只见这张纸上画着的图案有点像一棵树,笔直的树干上,零零碎碎长着几根枝桠,枝桠上点缀着几片树叶。
“这是一棵树?啥意思啊?“,他一脸诧异。
董哥给他递上水,哈哈一笑:“这就是困扰你的问题啊。”
“额。。我才疏学浅,不太理解,你的意思是不是说,这些问题少想一点,不然像这棵树一样,树叶都掉光了,年纪轻轻就秃顶?”
“噗”,董哥一口水喷溅而出,连呛几下。
”董哥,你别卖关子了,快告诉我吧。“
4)
”木哥,我问下你,如果你种了一棵苹果树,如果有一天想让他结出梨子,你会怎么做?“
”额。。有几种方法,例如基因工程,转个基因?当然,我显然实现不了,那我只能用嫁接了“。
”是的,那依赖注入就是这样类似于嫁接的抽象性思维。“
”再讲深入一点?“
”首先,应用程序中所有的对象,就像是一棵树上长出来的枝桠和树叶,都是从母体长出来的。“
“我知道,在.NET里面这叫root对吧?“
”不是,我们今天先不讨论这个问题,而是先讨论最基础的问题,什么叫做依赖注入。你刚刚提到的这个root的问题,在.NET里面属于垃圾回收问题,这个问题有点花时间,且与你之前提的话题关系有点远。今天我们还是讨论这个依赖注入问题,在.NETCore中,我们往往会使用一种技术框架,这种框架恰好就叫依赖注入框架。“
“我看到过设计原则这个说法,网上大佬们说可以用SOLID单词来进行总结。“
”是的,包括单一职责原则,开闭原则,里式替换原则,接口分离原则,依赖倒置原则。后来又新增了一种合成复用原则,其他原则今天由于时间关系先不提,我们重点来看看这个依赖倒置原则。而这个原则也有人将它称为依赖注入原则。“
董哥喝了一杯水,慢悠悠的解释道:
”这个原则其实只有一句话,高层模块不依赖于低层模块,它们都依赖于抽象。回到我们平时写的业务代码,往往都是从根开始构造对象,接着我们写了一堆的new语句,并继而实例化出了许多对象,而这些对象又实例化出许多类,每个类直接引用了许多其他看似有关的业务类。
类和类之间的关系就像之前画的那棵树的树枝一般,互相搅合在一起,高层模块严格依赖于低层模块,低层模块的任何改变都可能对高层模块造成影响,这将极大的提高代码的维护成本,事实上变成了一个高耦合问题。“
小木同学突然感觉到一个问题:”听说多态也是用来解决对象与对象之间依赖问题的?“
5)
董哥说:”你说的对,其例如在早期计算机中,由于各个程序模块可能被写入到不同的存储介质中,如果每个模块与其他模块强耦合,一旦某个模块被重写,那跟他建立链接关系的所有代码都将不得不重写。
所以人们引入了多态的概念,首先定义一层抽象,并定义实现类,父类通过依赖这些抽象,并维护抽象和实现之间的关系,这样如果某个用于实现的模块被替换了,那只需替换这个用于维护链接关系的存储介质即可,对原有代码几乎没有任何改动。“
小木同学恍然大悟:”依赖注入中定义接口也是为了实现模块间所调用时更好替换的问题么?”
董哥说:”是的。就像我们在一棵普通茶树上,想让它长出十八罗汉,结出各种姹紫嫣红、不同外观的山茶花,那我们要做的首先是准备砧木,然后再准备接穗。事实上是将某种山茶花的某些共性“抽象化”出来,并让它们通过与砧木相结合,实现“接口”与“调用者”的耦合,将抽象出来的“接口”注入到“调用者”里面。“
小木同学连连点头:“这样调用者不依赖于其他实现者,它们都依赖于抽象,虽然看起来调用者同样依赖了某些业务逻辑,但没有直接耦合其他的实现侧代码,只要接口不变,那调用者代码就几乎没有任何改动了?”
董哥继续说到:
“在面向对象开发中,接口就像一种契约,是业务规则的抽象。我们都可能见过这个单词,用户界面。这个单词的英语原文是User Interface,按翻译,这个单词,不应该是用户接口么?其实,可以换一个角度来理解,我们所设计的界面是用户与计算机进行数据交换的一种行为接口,通过将行为接口事先确定,将使得我们代码开发的过程变得更加简单。而在应用程序中设计接口也同样是为了这个目的。“
小木说:”好像接口无处不在,例如webapi,也是基于web的应用程序接口。“
董哥回答道:”是的。回到正题,在.NET Core中使用接口和依赖注入不仅仅只为了代码的整洁性或维护性,随着我们将业务代码抽象化成接口和实现两部分,这也使得对象生命周期的统一管理成为可能,这就引发了第二个问题,.NET Core中的依赖注入框架。“
--end