象的控制权交由配置文件控制,然后根据配置文件中的信息(程序集+类型),通过反射来获取对象,而不是直接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
.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com