此文从面向对象设计角度分析依赖倒置原则,引入依赖注入,IOC相关内容进行探讨,文中引用女朋友案例可能引起分手风险,故理解为 new 对象。
想起在学校的时候,有一个任务就是设计一款宠物商店项目。那时候引入三层架构设计概念,分为UI(表现层)、BLL(业务逻辑层),DAL(数据访问层)。
▲图/ 简单三层架构示例
每一层各司其职,表现层只管负责用户的界面交互;业务逻辑层只管计算、验证,业务规则等等;数据访问层则负责与数据库的增、删、改,查。
设计完交差后,就会思考,为啥要使用这个,能给我带来什么好处。学完面向对象设计思想之后得出了答案,当时判定程序的良好基本可以通过“高内聚,低耦合”的方式。
确实,三层架构设计能够符合程序的高内聚,达到解耦的目的,但是并没有达到低耦合的层级,自上而下层层依赖,其耦合性还是相对较高。或许当时设计的项目相对较简单,没有直接体现出来。
工作之后才发现,三层架构设计的应用还是相对较广,曾经就有一些项目运用三层架构之后,不断的需求变更导致一层的改动,都会造成联级改动。
如果要解决这个问题,面向对象设计中有一个设计原则,就是依赖倒置原则(DIP)。
高层模块不应该依赖低层模块,两者都应依赖于抽象层。
抽象层不应该依赖于实现,实现应该依赖于抽象。
模块间的依赖通过抽象发生的,实现类之间不发生直接的依赖关系,依赖关系是通过接口或者抽象类产生的。
这个原则听起来很像是“针对接口编程,不针对实现编程”,的确很相似,然而这里更强调“抽象”。这里就是要求设计者们编程时需要有一定的抽象思维啦。
依赖倒置的原则,也可以让三层架构升级,具有抽象化。
▲图/ 升级抽象化三层架构示例
当然DIP的主要目的并不是这,目的就是为了让系统架构更加的稳定、更加灵活,更好的应对需求的变化。
无论多少种设计模式或者多少种设计原则,主要就是让系统稳定,能够应付需求的千变万化,并不是花里胡哨的存在。毕竟,相对于细节的多变性,抽象化会更加稳定。
那么程序中要使用依赖倒置原则,一个呼声最高的名词就是控制反转(Inversion of Control),简称IOC。
IOC的存在可以说是更好的实现依赖倒置原则,也让程序更加的灵活和稳固。
至于怎么理解这个IOC呢,可以请出女朋友的例子来说明。
女朋友饿了,想要吃这吃那,对食物有需求。
此时会怎么做呢?
1.自己去找吃的,去冰柜或者厨房去拿吃的。
2.找男朋友,向你撒娇,给她递食物。
第一种,女朋友可能不太聪明,也不会做饭,拿到的食品没有加工,或者没有清洗就直接吃,会存在一定的风险。
第二种,向男朋友撒娇,表达需求。此时你可能会表现出男友力Max,会去冰柜里拿出食物并洗干净给她,或者进厨房做饭给她吃,甚至还可以喂她。
啊?你要上班或者出差不在家?此时你女朋友发现隔壁邻居老王很富有且很会做饭,便向隔壁老王打电话撒娇表达需求,此时隔壁老王会做一手拿手好菜端到你女友面前并喂她。
我们可以看到,你的女朋友从对食物的需求,从主动去冰柜或者厨房(正转)获取食物,到想要吃食物而被动(你或者老王)获得(反转)食物。
女朋友对食物的需求,食物的控制权转移到了你或者老王的手上。
编程时,相当于组件对象控制权的转移。
例如,方法A中需要使用方法B,方法B需要使用方法C,此时方法B和方法C并不由自己本身来负责,而是由第三方来创建!
这个过程,称为控制反转,最直观的体现就是依赖注入(DI),反转依赖。
上述例子,女朋友想要的是食物(依赖,对食物的依赖),食物由第三方的你(男朋友)或者老王来负责,做好之后拿给(注入)你女朋友。
控制反转的是一种结果,一种由依赖食物到注入给定目标的结果。正是因为有了依赖注入,才需要控制反转,控制依赖的对象的选择权反转到目标结果。
如果明白了以上的内容,就能明白依赖注入和控制反转的目的。
为什么要依赖注入?因为要实现控制反转。
为什么要控制反转?因为软件设计体系中需要符合依赖倒置原则。
说到IOC(控制反转),我们会听到IOC容器的概念,IOC和IOC容器也有本质的区别。
IOC,定义就是一种反转流、依赖和接口的方式,它把传统上由程序代码直接操控的对象的调用权交给第三方(容器),通过第三方来实现对象组件的装配和管理。
所谓的IOC容器,可以理解为一个对象,通常是由依赖注入框架来提供的。负责映射依赖、管理对象的创建和生命周期。
说到IOC对象,我想大家应该有一个概念,就是对象池的概念。IOC容器可以理解为一个对象池的概念。
例如耳熟能详的数据库连接池,线程池等都是对象池的概念。
所谓的对象池,就是对象的容器,通过在一个容器中池化(实例化,也就是new)对象,并根据需要重复使用这些池化对象来满足性能上的需求。
当一个对象被激活时,便从池子里取出。用完时,又放回池中,等待下一个请求。一般用于对象的初始化过程代价较大或者使用频率较高的场景。
通俗讲,就是多个对象的实例化,资源释放都是交给对象池来处理。
依赖注入框架,目前使用较多的有 Unity、Autofac、Ninject等。
相关的框架也可以通过搜集文档进行了解,此处以Unity框架作为例子来实现。
▲图/ 抽象化鸟类示例
//鸟类接口public interface IBird{public string Eat();}public class Cuckoo : IBird{public string Eat(){return "布谷鸟正在吃东西...";}}public class Sparrow : IBird{public string Eat(){return "麻雀鸟正在吃东西...";}}//鸟类工厂public static IBird GetEat(string birdType){if (string.IsNullOrEmpty(birdType)){return null;//这里可以使用空对象模式}switch (birdType){case "Cuckoo": return new Cuckoo();case "Sparrow": return new Sparrow();default:return null;}}//工厂方法调用 public class BirdSample{public IBird _bird { get; set; }public BirdSample(string birdType){_bird = BirdFactory.GetEat(birdType);}public void Eat(){Console.WriteLine($"小鸟们开始,吃东西了");Console.WriteLine(_bird.Eat());}}}
//普通调用方法,由工厂实例对象public static void Main(string[] args){BirdSample birdSample = new BirdSample("Cuckoo");birdSample.Eat();}
//使用Unity容器化public static void Main(string[] args){var container = new UnityContainer();//此处通过反射的方式实现,通过扫描程序集,找到相应的类进行主从到容器//注册类型可以更改,也可以通过配置文件,动态加载container.RegisterType<IBird, Cuckoo>();container.RegisterType<BirdSample>();var bird = container.Resolve<BirdSample>();bird.Eat();}
以上内容,就是依赖倒置原则,IOC的内容了。
希望能够帮助到伙伴们,文中示例有可能引发分手风险,在此希望多加支持点个赞!
👇 更多有趣内容,请多关注!👇