理解:
依赖倒置原则 (DIP) 主程序要依赖于抽象接口, 不要依赖于具体实现. 高层模块不应该依赖底层模块, 两个都应该以来抽象. 抽象不应该依赖细节, 细节应该依赖抽象.(具体看我上一篇贴子)
依赖倒置原则是六大设计原则中的一种, 它的大致意思是所有模块都应该依赖于抽象, 而不是直接依赖于另一个模块. 依赖倒置原则仅仅只是一个原则而已, 它只是告诉了你程序应该要变成什么样子(模块之间依赖抽象), 而并没有具体告诉你应该怎么做. 就像是在学校, 老师告诉你教室要干净, 不要有垃圾, 而具体打扫垃圾的动作老师却并没有告诉你, 你可以选择用扫把打扫, 也可以选择用手捡, 但是最终教室要干净(当然, 你也可以不遵守).
控制反转 (IoC) 就是遵循了依赖倒置原则的一个思想.
什么是控制? 控制就是对对象进行创建, 操作, 销毁.
什么是 "反转"(叫 "转移" 更为贴切)?"反转" 的意思就是将 "控制" 的操作交由外部来处理, 自己只管用, 只管要, 其他的都不管.
为什么说控制反转遵循了依赖倒置原则? 虽然 A 模块需要 B 模块, 但是 A 模块中并不是声明了 B 模块对象的引用, 而是声明了对 IB(B 模块抽象)的引用, A 模块真正需要的是实现了 IB 抽象的子类, 所以 A 模块并不依赖于 B 模块, 而是依赖于 IB 抽象.
控制反转的大意为: 如果模块 A 需要模块 B, 模块 A 中并不是直接控制创建模块 B, 而是从外部控制如何创建. 例如我们将创建何种对象的控制权交由配置文件控制, 然后根据配置文件中的信息(程序集 + 类型), 通过反射来获取对象, 而不是直接 new 对象, 这也是控制反转的一种体现.
IoC 容器会连接程序中的所有模块, 模块将所需对象的控制权都交由 IoC 容器控制, IoC 容器会根据用户配置的信息将各个模块所需要的对象事先创建完成, 然后 IoC 容器可以通过依赖注入 (DI) 的方式为模块注入所需对象(还有依赖查找(DL)), 依赖注入就是一种具体实现的手段.
依赖倒置原则, 控制反转和依赖注入并不是为程序带来新的功能, 而是使得程序中模块的耦合性降低, 提高每个模块的复用性.
举个栗子:
就拿生活中最常见的自助取款机来说一下, 首先我们要拥有一张银行卡, 例如建设银行的银行卡 CCBCard 类(设计的一些属性可能不太合理, 不过重要的是了解思想)
- // 建行银行卡
- public class CCBCard
- {
- // 银行卡中的钱
- public decimal Money { get; set; }
- // 银行卡名字
- public String Name { get; set; }
- public CCBCard(decimal money,String name)
- {
- this.Money = money;
- this.Name = name;
- }
- }
然后我们来创建一个 ATM 自动取款机类, 该取款机拥有取钱和存钱的功能
ATM 机 1.0
- // 自动取款机
- public class ATM
- {
- // 建行银行卡
- public CCBCard Card = new CCBCard(1000,"建行卡");
- // 取钱
- public void SubMoney(decimal money)
- {
- // 判断余额是否足够
- if (Card.Money>= money)
- {
- // 取钱
- this.Card.Money -= money;
- Console.WriteLine($"取钱成功, 余额{Card.Money}");
- }
- else {
- Console.WriteLine("余额不足");
- }
- }
- // 存钱
- public void AddMoney(decimal money)
- {
- // 存钱
- this.Card.Money += money;
- }
- }
因为这个例子是生活中非常常见的, 所以大家肯定一眼就看出了不妥
此时的 ATM 机可是说是个非常 "睿智" 的 ATM 机了, 我去取钱, 而 ATM 机却自带了一张建行银行卡, 与其说是个 ATM 机, 倒不如说只能对一个 CCBCard 建行卡进行存取的机器. 此时 ATM 类完全依赖于 CCBCard 对象, 而且 CCBCard 对象也是由 ATM 类创建的, 如果 CCBCard 修改了, 或者要换其他的卡, ATM 机还要做出修改.
所以接下来我们应该将这不好的两点改掉:
1,ATM 机只能读取单一的 CCBCard 卡(ATM 控制 CCBCard 对象的创建)
2,ATM 只能读取 CCBCard 类型的卡(ATM 完全依赖于 CCBCard 对象)
先来解决第一点, 接下来我们改进一下, 为 ATM 机增加构造函数, 通过构造函数传递 CCBCard 对象, 使得 ATM 机可以操作其他建行卡:
ATM 机 2.1:
增加构造函数, 两个方法不用变
- // 建行银行卡
- public CCBCard Card;
- // 构造函数传入 CCBCard 对象
- public ATM(CCBCard card)
- {
- this.Card = card;
- }
使用:
- // 银行卡
- CCBCard card = new CCBCard(1000,"建行卡");
- //ATM
- ATM atm = new ATM(card);
- // 取钱
- atm.SubMoney(100);
然后来解决第二个问题, 只能存取建行卡:
此时就需要用到依赖倒置原则, 让 ATM 类依赖于 CCBCard 抽象, 而不是具体的实现. 如果我们想存取其他银行卡里面的钱, 必须为所有的银行卡抽象出一个接口, 然后让所有银行卡子类都实现这个接口.
- // 银行卡接口
- public interface ICard
- {
- string Name { get; set; }
- decimal Money { get; set; }
- }
建行卡实现该接口:
- // 建行银行卡
- public class CCBCard:ICard
- {
- // 银行卡中的钱
- public decimal Money { get; set; }
- // 银行卡名字
- public String Name { get; set; }
- public CCBCard(decimal money,String name)
- {
- this.Money = money;
- this.Name = name;
- }
- }
使得 ATM 机依赖于 ICard 接口(修改了 Card 属性和构造函数), 而且 ATM 机并不控制 ICard 的子类, 而是将控制权交由调用者. 这一切才合情合理啊, 无论用户插入什么卡, 该 ATM 机都能进行存取. 这就是控制反转, 而通过构造函数传入的 ICard 对象则是通过依赖注入的方式注入对象.
- // 建行银行卡
- public ICard Card;
- // 构造函数传入 ICard 对象(依赖注入)
- public ATM(ICard card)
- {
- this.Card = card;
- }
依赖注入还有其他两种: 通过接口和属性注入
- // 属性注入
- public ICard Card { get; set; }
- // 接口注入
- // 该方法实现了接口
- public void Inject(ICard card)
- {
- this.Card = card;
- }
而接口注入就是通过方法注入, 此种方式会增加不必要的接口, 现在基本不使用, 大多为构造函数注入.
新添加一个 ICBC(工商银行)卡进行测试
- // 工商银行卡
- public class ICBCCard : ICard
- {
- public string Name { get; set; }
- public decimal Money { get; set; }
- }
测试:
- // 建设银行卡
- CCBCard bcard = new CCBCard(1000, "CCB");
- // 工商银行卡
- ICBCCard ccard = new ICBCCard()
- {
- Money = 1000,
- Name = "ICBC"
- };
- //ATM
- ATM atm1 = new ATM(bcard);
- ATM atm2 = new ATM(ccard);
- // 取钱
- atm1.SubMoney(100);
IoC 容器就是专门为 ATM 机这种模块服务的, 它就像是一个大齿轮一样, 连接所有小齿轮(模块), 它运转, 整个程序便运转, 如果有些模块可能需要用到其他模块中的对象, 它们并不会直接依赖, 而是全都由 IoC 容器控制.
虽然互相需要, 但是互不依赖, IoC 容器会事先将 ICard 子类创建好, 然后通过依赖注入注入到 ATM 机中(Unity,Spring.NET 等框架都是封装完善的 IoC 容器),ATM 机只管接收, 只管索取. ATM 机是不对子类进行创建的, 控制权在用户手里, 由用户控制 ATM 机操作何种银行卡, 就像你去取钱一样, 你插入什么卡自助取款机都可以取钱, 这看起来是多么平常的一件事? 很多看起来高大上的思想, 都是从需求演变过来的, 然后由前人一点点探索研究总结出来.
至此 ATM 机已经完成了, 可能因为 ATM 机太常见了, 所以我所说的一切你都可以想到(换卡, 换不同银行的银行卡), 就像是在说废话, 如果你都理解了, 那么根据 ATM 机, 你应该仔细的思索一下, 你所设计的类和模块满不满足像 ATM 机一样的 "功能"?
来源: https://www.cnblogs.com/ckka/p/11448065.html