一、前言
理解这些概念对于我来说有些非常困难。 但实际上它们非常简单,我们在日常编码中使用它。
今天,我想谈谈编码中依赖性的问题以及控制反转(IOC)和依赖注入(DI)想要说些什么。 本文面向渴望了解最重要原则,但在实现方面有点困惑的读者。
二、疑问点
1. 什么是控制反转(IOC)?。
2. 什么是依赖注入(DI)?。
3. 实现依赖注入的方法
4. 实施这一原则的优点
三、控制反转IOC
通过下面的例子讲解:在我们大学日常,我们有时会举办各种活动,有时甚至是无聊的讲座,考虑并回忆大学的日常生活,让我们尝试将大学和活动与控制反转(IOC)联系起来。
public class College { private TechEvents _events = null; public College() { _events = new TechEvents(); } public void GetEvents() { _events.LoadEventDetail(); } } public class TechEvents { public void LoadEventDetail() { Console.WriteLine("Event Details"); } }
上面 College 类的作用是创建 TechEvents 对象。
假设我有一个类为Colleage,另一个类为TechEvents。 正如您在上面所看到的,可能会出现许多问题:
1. 这两个类彼此紧密耦合。 我不能没有TechEvents的College,因为在College构造器中创建了一个TechEvents对象。
2. 如果我对TechEvents进行任何更改,我需要编译,或者你也可以说更新College类。
3. College 控制 Events 的创建。College 知道有组织的单一 Event。 如果有任何特定 event 像足球活动或Party活动被组织,则需要对 College 类进行更改,因为College直接引用Events。
现在我需要以某种方式解决这个问题,否则我们将无法在大学里举办任何其他活动。
解决这个问题的方法可能是将事件组织的控制权转移到其他地方。我们称之为控制反转(IOC),将控制权转换为其他实体,而不是直接在 College 组织 Event。什么反转控制原理说?
换句话说,主要类不应该具有聚合类的具体实现,而应该依赖于该类的抽象。 College类应该依赖于使用接口或抽象类的TechEvents类抽象。
/// <summary> /// 创建一个接口为了实现抽象 /// </summary> public interface IEvent { void LoadEventDetail(); } /// <summary> /// 所有类型事件活动类应该实现 IEvent /// </summary> public class TechEvent : IEvent { public void LoadEventDetail() { Console.WriteLine("Technology Event Details"); } } /// <summary> /// 所有类型事件活动类应该实现 IEvent /// </summary> public class FootballEvent : IEvent { public void LoadEventDetail() { Console.WriteLine("Football Event Details"); } } /// <summary> /// 所有类型事件活动类应该实现 IEvent /// </summary> public class PartyEvent : IEvent { public void LoadEventDetail() { Console.WriteLine("Party Event Details"); } } public class College { private IEvent _events = null; /// <summary> /// College 构造器提示说需要一个活动事件 /// </summary> /// <param name="ie"></param> public College(IEvent ie) { _events = ie; } public void GetEvents() { _events.LoadEventDetail(); } }
四、依赖注入(DI)
可以使用依赖注入(DI)来完成 IOC 。 它解释了如何将具体实现注入到使用抽象的类中,换句话说就是内部的接口。 依赖注入的主要思想是减少类之间的耦合,并将抽象和具体实现的绑定移出依赖类。
简单来说,DI就是一个对象如何去知道那些被抽象的其他依赖对象。实现依赖注入主要有4种方法。
1、构造函数注入
public class College { private IEvent _events = null; /// <summary> /// College 构造器提示说需要一个活动事件 /// </summary> /// <param name="ie"></param> public College(IEvent ie) { _events = ie; } public void GetEvents() { _events.LoadEventDetail(); } }
如上所示,事件对象由构造函数注入,使其保持抵耦合。 College类将完成他的工作,如果它想获取与事件相关的详细信息,它将根据他想要调用的事件在构造函数中调用它。
College coll = new College(new FootballEvent());
除了这个优势,另一个优点是,如果事件有任何变化或添加了更多事件,那么College不需要关心这一点。
2、方法注入
class College { private IEvent _events; public void GetEvent(IEvent myevent) { this._events = myevent; } }
如上所示,我使用GetEvents()方法调用College事件,其中事件类型作为抽象类型的参数传递。 这将帮助我在不影响College 的情况下添加或更改事件,换句话说,两者都是分离的。 这就是我可以调用该方法的方法。
College coll = new College(); coll.GetEvent(new FootballEvent());
3、属性注入
这是最常用的方法,我们通过创建接口类型的属性来注入具体类。
class College { private IEvent _events; public IEvent MyEvent { set { _events = value; } } }
如上所示,MyEvent属性的setter将获取一个具体对象并将其绑定到接口。 我的类与具体的对象低耦合。 现在对任何类型的Event类的任何更改都不会影响我的College类。
College coll = new College(); coll.MyEvent = new FootballEvent();
4、服务器定位注入
服务定位器可以像一个简单的运行时映射器。 这允许在运行时添加代码而无需重新编译应用程序,在某些情况下甚至无需重新启动它。
public class College { private IEvent _events = null; EventLocator el = new EventLocator(); public College(int index) { _events = el.LocateEvent(index); } } public class EventLocator { public IEvent LocateEvent(int index) { if (index == 1) { return new FootballEvent(); } else if(index == 2) { return new PartyEvent(); } else { return new TechEvent(); } } }
在上面的代码片段中,您可以看到 Events 和 College 之间有一个EventLocator类,它可以帮助我们在不知道具体类型的情况下找到服务。 我只是在构造函数中传递索引值,而构造函数又调用第三方来定位事件并将其返回给构造函数。 因此,对EventLocator的任何更改都不会影响College类。
College coll = new College(1); coll.GetEvents();
实时上述原则的优点:
1、它有助于类的解耦。
2、由于解耦,代码的可重用性增加。
3、改进了代码可维护性和测试。
五、总结
控制反转(IOC)讨论了谁将启动调用,其中依赖注入(DI)讨论一个对象如何通过抽象获取对其他对象的依赖。
这边文章主要引用国外网友的文章,若有觉得有不合理之处,可以查看原文 :https://www.c-sharpcorner.com/UploadFile/cda5ba/dependency-injection-di-and-inversion-of-control-ioc/