在讲依赖注入之前,首先需要明白什么是依赖。
一、什么是依赖?
大家在编程的过程中,往往会听说过这么一句话,叫做"高内聚,低耦合"。高内聚很好理解,比如说面向对象编程,就是为了实现高内聚,通俗地讲,就是“自己的东西自己保管,自己的事情自己做”。那什么是耦合呢?
我之前也是一直弄不明白这个问题,一直处于一种一知半解的状态。直到看到了一篇非常优秀的文章(具体网址放文章最后)。我们都知道,一个对象A要去拿到另一个对象B的东西,一般来说,必须去拿到另外一个对象B的引用(实例)。这时候,A就依赖于B了。因为如果B被删除了,或者被修改到A那边拿到的字段了,A那边肯定报红。这时候,A和B之间的耦合就产生了。
二、什么是依赖注入
前面讲了,在一般情况下,一个对象A要去拿到另一个对象B的东西,必须去拿到另外一个对象B的引用(实例)。这么做虽然比较方便,但是也就让A和B之间产生了耦合。而依赖注入就是来解决这个问题。其实,还有几种方法能让一个对象A拿到另外一个对象B的东西,比如通过构造器、属性(setter)、方法和接口。这几种方法就是依赖注入,他们和一开始说的通过拿到另外一个对象的引用的方法的显著区别是,他们都是通过外部去创建调用者,并将依赖传递给调用者,而不是调用者直接获得依赖。其中,通过接口来传递依赖的好处在于拓展性极强,只需要让新功能继承这个接口即可。
在这种模式下,如果对象有任何更改,则依赖注入会对其进行调查,并且不应影响到使用这些对象的类。这样,如果将来对象发生变化,则依赖注入负责为类提供正确的对象。
就比如在Unity开发中,很多UI被动态创建出来,通过Init方法从外部注入依赖,这就是通过方法来进行依赖注入。
三、依赖倒置原则
依赖倒置原则其实就一句话,"高层模块不应该依赖底层模块,他们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象"。
《资本论》中都曾阐释依赖倒置原则:在商品经济的萌芽时期,出现了物物交换。假设你要买一个IPhone,卖IPhone的老板让你拿一头猪跟他换,可是你并没有养猪,你只会编程。所以你找到一位养猪户,说给他做一个养猪的APP来换他一头猪,他说换猪可以,但是得用一条金项链来换——所以这里就出现了一连串的对象依赖,从而造成了严重的耦合灾难。解决这个问题的最好的办法就是,买卖双方都依赖于抽象——也就是货币——来进行交换,这样一来耦合度就大为降低了。
举一个通俗的例子:假如说某个类A去调用一个抽象类B的抽象方法,A即为高层,B即为底层,然后具体方法实现类C、D、E、F又继承于这个抽象类,实现不同的抽象方法。这个其实也是符合了依赖倒置原则,因为这个高层的这个类A并没有去调用具体的方法实现类C、D、E、F,调用的是抽象类B,所以是依赖于抽象;底层的这些具体方法实现类C、D、E、F又是继承于这个抽象类B,也是依赖于抽象。于是就符合了"高层模块不应该依赖底层模块,他们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象"。
using UnityEngine;
public class A : Monobehaviour
{
public B b;
public void Awake()
{
b.Execute();
}
}
public abstract class B
{
public abstract void Execute();
}
public class C : B
{
public override void Execute()
{
//C的实现方法
}
}
public class D : B
{
public override void Execute()
{
//D的实现方法
}
}
public class E : B
{
public override void Execute()
{
//E的实现方法
}
}
public class F : B
{
public override void Execute()
{
//F的实现方法
}
}
接口也是一样的,都可以实现依赖倒置原则。接口和抽象类的区别就在于,接口是完全抽象,每一个重写接口的方法都不一样;抽象类是部分抽象,如果写成虚函数的话,那些共用代码是可以继承的,适合就是有共用代码的一些类。
这样做的好处就是拓展性极强,并且解耦。都是依赖抽象,并没有依赖具体实现。增加或者修改某些功能时,只需要修改具体实现类里的方法,或者增加一个类来继承这个接口B,不需要去修改到调用者A。即符合了Solid原则中的开闭原则,即“class 应该对扩展开放对修改关闭”。通俗一点讲,就是:
我们应该能在不动 class 已经存在代码的前提下添加新的功能。这是因为当我们修改存在的代码时,我们就面临着创建潜在 bug 的风险。
四、IOC(控制反转)
控制反转和依赖倒置其实都是一种编程思想,依赖倒置着眼于调用的形式,而控制反转则着眼于程序流程的控制权。
比如在TCP/IP协议栈中,我们知道,每层协议为上一层提供服务,那么这里就是一个C/S关系。当我们使用开发框架时,开发框架就是作为服务方,而我们自己编写的业务应用就是客户方。当Client调用server时,这个叫做一般的控制;而当server调用Client时,就是我们所说的控制反转,同时我们也将这个调用称为“回调”。一般来说,程序的控制权属于Client,而一旦控制权交到server,就叫控制反转。
比如你去下馆子,你是Client餐馆是server。你点菜,餐馆负责做菜,程序流程的控制权属于Client;而如果你去自助餐厅,程序流程的控制权就转到server了,也就是控制反转。
当时我看到这儿的时候,就有一点感觉了,想起了以前学的观察者模式。假如有个观察者类SaveManager.cs,用于存储数据,继承了抽象观察者ISaveManager接口,并封装了一个List用来存储需要保存数据的对象。有若干个被观察者类,继承了抽象被观察者ISaveable接口。其中ISaveManager接口定义Save方法,ISaveable接口定义了Add、Remove、Notify方法。
在一般情况下,如果不使用观察者模式的话,都是高层去调用底层的代码,就像上面在讲依赖倒置原则给的例子一样。所以在一般情况下,都是SaveManager这个高层去调用这些需要保存数据的底层对象。
但是大家可能已经发现了,在使用了观察者模式以后,Notify方法的实现放在了这些需要保存数据的底层对象中,即"先添加,不调用",先将这些需要保存数据的对象通过实现ISaveable接口的Add方法来添加到高层的SaveManager的列表中,需要存储数据的时候,再由这些需要保存数据的底层对象调用Notify方法,来调用ISaveManager中的Save方法来实现保存数据。
这样一来,就是底层去调用高层了,即程序流程的控制权从原本的高层转移给了底层,控制权反过来了,即为控制反转。
相关的例子还有许多,比如事件的发布和订阅、触发也是控制反转。
五、IOCContainer
在凉鞋的QFramework中,就实现了一个基于单例的IOCContainer。整个QFramework就new了一次IOCContainer,放在那个Architecture<T>类里。我的理解就是IOCContainer是对单例模式的一种升级。
如果直接使用单例的话,调用单例类里的方法或者字段啥的,虽然是解耦了,但仍旧是调用的是具体的实现,所以高层还是依赖具体的实现,并没有依赖抽象,并且单例模式的类没办法接接口,因为没有办法实例化,所以没办法多态,所以单例的类(底层)仍然是依赖于具体,违背了依赖倒置原则;然后单例类还不能被继承,不可拓展,违背了开闭原则。但是用IOCContainer之后,就符合solid原则了,类跟类之间通过IOCContainer去交互,只需要知道接口,然后通过多态去实例化这个接口,高层就是依赖于抽象;然后底层具体实现类也是继承于接口,也是依赖于抽象。所以符合了依赖倒置原则,并且高层和低层都可以继承,可以拓展,也符合了开闭原则。
还有更复杂的IOCContainer,还没有研究。
如有谬误,望指正,在此感激不尽。
六、参考链接
https://www.cnblogs.com/aoyeyuyan/p/5495219.html(最牛逼的放第一个哈哈哈)
使用 IoC 反转控制的三种设计模式_ioc 3种模式-CSDN博客
https://www.freecodecamp.org/chinese/news/a-quick-intro-to-dependency-injection-what-it-is-and-when-to-use-it/
单例模式:
https://xie.infoq.cn/article/5ebaa608a57a3f4d651fc18ff#:~:text=1%20%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E4%B8%80%E8%88%AC%E6%B2%A1%E6%9C%89%E6%8E%A5%E5%8F%A3%EF%BC%8C%E6%89%A9%E5%B1%95%E5%BE%88%E5%9B%B0%E9%9A%BE%EF%BC%8C%E8%8B%A5%E8%A6%81%E6%89%A9%E5%B1%95%EF%BC%8C%E9%99%A4%E4%BA%86%E4%BF%AE%E6%94%B9%E4%BB%A3%E7%A0%81%E5%9F%BA%E6%9C%AC%E4%B8%8A%E6%B2%A1%E6%9C%89%E7%AC%AC%E4%BA%8C%E7%A7%8D%E9%80%94%20%E5%BE%84%E5%8F%AF%E4%BB%A5%E5%AE%9E%E7%8E%B0%E3%80%82%20%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E8%83%BD%E5%A2%9E%E5%8A%A0%E6%8E%A5%E5%8F%A3%E5%91%A2%EF%BC%9F%20%E5%9B%A0%E4%B8%BA%E6%8E%A5%E5%8F%A3%E5%AF%B9%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E6%98%AF%E6%B2%A1%E6%9C%89%E4%BB%BB%E4%BD%95%E6%84%8F%E4%B9%89%E7%9A%84%EF%BC%8C%E5%AE%83%E8%A6%81%E6%B1%82%E2%80%9C%E8%87%AA%E8%A1%8C%E5%AE%9E%E4%BE%8B%E5%8C%96%E2%80%9D%EF%BC%8C%E5%B9%B6%E4%B8%94%E6%8F%90%E4%BE%9B%E5%8D%95%E4%B8%80%E5%AE%9E%E4%BE%8B%E3%80%81%E6%8E%A5%E5%8F%A3%E6%88%96%E6%8A%BD%E8%B1%A1%E7%B1%BB%E6%98%AF%E4%B8%8D%E5%8F%AF%E8%83%BD%E8%A2%AB%E5%AE%9E%E4%BE%8B%E5%8C%96%E7%9A%84%E3%80%82%20%E5%BD%93%E7%84%B6%EF%BC%8C%E5%9C%A8%E7%89%B9%E6%AE%8A%E6%83%85%E5%86%B5%E4%B8%8B%EF%BC%8C%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E5%8F%AF%E4%BB%A5%E5%AE%9E%E7%8E%B0%E6%8E%A5%E5%8F%A3%E3%80%81%E8%A2%AB%E7%BB%A7%E6%89%BF%E7%AD%89%EF%BC%8C%E9%9C%80%E8%A6%81%E5%9C%A8%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E4%B8%AD%E6%A0%B9%E6%8D%AE%E7%8E%AF%E5%A2%83%E5%88%A4%E6%96%AD%E3%80%82,2%20%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E5%AF%B9%E6%B5%8B%E8%AF%95%E6%98%AF%E4%B8%8D%E5%88%A9%E7%9A%84%EF%BC%8C%E5%9C%A8%E5%B9%B6%E8%A1%8C%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E4%B8%AD%EF%BC%8C%E5%A6%82%E6%9E%9C%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E6%B2%A1%E6%9C%89%E5%AE%8C%E6%88%90%EF%BC%8C%E6%98%AF%E4%B8%8D%E8%83%BD%E8%BF%9B%E8%A1%8C%E6%B5%8B%E8%AF%95%E7%9A%84%EF%BC%8C%E6%B2%A1%E6%9C%89%E6%8E%A5%E5%8F%A3%E4%B9%9F%E4%B8%8D%E8%83%BD%E4%BD%BF%E7%94%A8%20mock%20%E7%9A%84%E6%96%B9%E5%BC%8F%E8%99%9A%E6%8B%9F%E4%B8%80%E4%B8%AA%E5%AF%B9%E8%B1%A1%E3%80%82%203%20%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E4%B8%8E%E5%8D%95%E4%B8%80%E8%81%8C%E8%B4%A3%E5%8E%9F%E5%88%99%E6%9C%89%E5%86%B2%E7%AA%81%E3%80%82%20%E4%B8%80%E4%B8%AA%E7%B1%BB%E5%BA%94%E8%AF%A5%E5%8F%AA%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E9%80%BB%E8%BE%91%EF%BC%8C%E8%80%8C%E4%B8%8D%E5%85%B3%E5%BF%83%E5%AE%83%E6%98%AF%E5%90%A6%E6%98%AF%E5%8D%95%E4%BE%8B%E7%9A%84%EF%BC%8C%E6%98%AF%E4%B8%8D%E6%98%AF%E8%A6%81%E5%8D%95%E4%BE%8B%E5%8F%96%E5%86%B3%E4%BA%8E%E7%8E%AF%E5%A2%83%EF%BC%8C%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E6%8A%8A%E2%80%9C%E8%A6%81%E5%8D%95%E4%BE%8B%E2%80%9D%E5%92%8C%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91%E8%9E%8D%E5%90%88%E5%9C%A8%E4%B8%80%E4%B8%AA%E7%B1%BB%E4%B8%AD%E3%80%82
Solid原则:
IOCContainer:
浅析Unity IoC容器——以VContainer库为参考 - 哔哩哔哩
单例模式与IOCContainer:
https://zhuanlan.zhihu.com/p/475779641