希望给你 3-5 分钟的碎片化学习, 可能是坐地铁, 等公交, 积少成多, 水滴石穿, 谢谢关注.
从 UML 来理解依赖
1.1 什么是依赖
我们先看下图
可以简单理解, 一个 HomeController 类使用到了 DBContext 类, 而这种关系是有偶然性, 临时性, 弱关系的, 但是 DBContext 的变化会影响到 HomeController
1.2 显示依赖和隐式依赖
先看显示依赖代码:
显示依赖通过构造函数, 很清楚的描述了 HomeController 类都依赖了哪些对象, 这样就可以很好的管理这些依赖. 而隐式依赖的缺点刚好就是显示依赖的优点. 我们看下面的隐式依赖:
如果一个类有上千行代码, 到处都充斥着该类型的代码, 这些代码就像隐藏的病毒一样, 无处不在, 可以想象后续的变化和修改是多么的恐怖.
1.3 依赖倒置
依赖倒置的概念其实很简单, 一句话就讲完了: 我们要依赖抽象, 而不依赖具体实现. 什么是抽象? 比如接口, 抽象类就是.
依赖抽象的目的是什么? 封装变化! 因为所有实现接口的实现都可以互相替换.
如上图所示, 当数据库 DapperUserRepository 切换到 EfUserRepository, 对 HomeController 类可以无需任何修改, 就可以平滑切换过去. 反之, 则更改的面就会非常大.
再看下面的代码, OrderController 依赖接口 IUserRepository 就是依赖倒置的表现.
从单元测试来理解
也许你会说, 我的变化没有那么频繁, 不需要那么麻烦. 那么你是否考虑过, 有可能自己的代码需要进行单元测试? 如果存在这种可能, 那么依赖注入是你必须要做的事.
2.1 控制反转
我们再看下面这个代码的问题
虽然 OrderController 依赖的是接口 IUserRepository, 满足依赖倒置原则, 但是构造函数却依赖的是具体实现类 UserRepository, 这种做法属于硬编码, 仍然无法满足未来变化带来的修改, 怎么办? 接下来我们来讲控制反转这个相对难以理解的概念.
先说反转, 到底反转的是什么? 我们知道 OrderController 依赖的对象 UserRepository 是在构造函数内的生成的. 如何能够把该对象的生成交给外部去决定生成呢? 可以的! 这种转移对象生成的方式就是控制反转.
简而言之, 反转的是控制权, 即依赖对象生成的控制权. 是自己决定生成还是交由别人去决定生成.
所以上面的代码, 修改如下:
以上的代码才达到真正的控制反转, UserRepository 对象的生成完全交由外部进行控制, 交给变化去控制.
这样有什么好处呢? 交给外部生成的最大好处是想要生成什么对象可以自由控制, 这样还是为了将来对象生成的可替换, 比如数据库访问对象的变更; 单元测试的实现类替换等等.
2.2 单元测试
有了上面的控制反转, 我们的单元测试就方面很多了.
我们可以看到, 在数据库无法连接的时候, 我们可以使用 MemoryUserRepository 进行替换单元测试, 非常方便.
来源: https://www.cnblogs.com/alligator/p/9900449.html