Discussion (IOC) with inversion of control dependency injection (DI)

Introduction: Baidu reference literature and https://www.cnblogs.com/liuqifeng/p/11077592.html and http://www.cnblogs.com/leoo2sk/archive/2009/06/17/1504693.html (recommended ) two articles,

First, the concept of dependency injection and Inversion of Control

       Commonly used IoC Container (container)

    

 

Dependency injection (Dependency Injection), such a process is: Since a customer interface to depend only on a service class, not depending on the particular service class, the client class defines only one injection point. The program is running, the client does not directly instantiate class service specific instance of the class, but class customers run context or specialized service component is responsible for instantiating the class, and then inject it into the customer class, to ensure the normal operation of the customer class.

Dependency injection is controlled reversing of a specific implementation, then what is Inversion of Control : It is one design principles of object-oriented programming, its role is mainly used to reduce the degree of coupling between the computer code,

  The most common method is called dependency injection (Dependency Injection, referred to as DI ), there is a way called " dependent lookup " (Dependency Lookup). By inversion of control, when the object was created by an outside entity for all objects within a regulatory system, on which it depends object passed to it. It can be said, it is injected into the subject dependency.

 Appreciated dependency injection: is the dependent portion (or code is not frequently change controllable coupling portion) becomes a member of the abstract (class, an abstract class or interface), and an example of the specific required to rely on a flexible injection, to achieve inversion of control effect, thereby achieving decouple code.

 

C # common dependency injection

  1, by the constructor dependency injection

  2, by a dependency injection property accessors

  3, through interface dependency injection

  4, the reflection characteristics can be realized dependency injection

Setter injection refers to the class of customers, the type of the interface is provided a service class data members, and provided a method as Set injection point, the Set method accepts a concrete service class instances as parameters and assign it a service class interface type the data members.

Injection configuration (Constructor Injection) refers to the class of customers, the type of the interface is provided a service class data members, and configured to function as injection points, this constructor takes a particular instance of the service class as a parameter and assign it to the service class interface type of data members.

 

Dependent acquisition (Dependency Locate) provide a means to obtain points, customers still rely on the interface class service classes in the system. When a customer needs class service class, the initiative to get the specified service class from the acquisition point, the specific type of service class configuration acquired by the decision point.

Can be seen, this method is passive to active, so that the active client class for service classes when needed, and the polymorphism of the package inside the Acquisition Point. There are many points you can get to achieve, and perhaps most obvious is to create a Simple Factory as an acquisition point, the client class pass a specified string to obtain the appropriate service class instance. If the service class is dependent on a series of classes, typically less dependent acquisition mode using Abstract Factory to build Acquisition Point, and then, transferring the service class to a polymorphism polymorphism plant, and a plant dependent on the type of external configuration, such as XML file.

However, regardless of the use of Simple Factory or Abstract Factory, are unavoidable determine service class or type of plant, there must be a presence does not comply with the OCP if ... else or switch ... case structure of such a system in place, and this defect is Simple Factory abstract Factory and relies get itself can not be eliminated, and in some languages ​​support reflected in (such as C #), by introducing the reflection mechanism to solve this problem

IoC Container

Speaking of dependency injection, then we can not fail to mention IoC Container (IoC container), then in the end what is the IoC container? We take a look at its appearance background.

We know that software development saying the famous assertion: Do not reinvent the wheel! Because software development emphasizes reuse, so, for frequent application needs, always someone design a variety of common framework and class libraries developed to alleviate people's burden. For example, data persistence is very frequent demand, so various ORM framework came into being; Again, the demand for MVC spawned a number of other Struts to implement MVC framework.

With the object-oriented analysis and design development and maturation, OOA & D is more and more widely used in various projects, however, we know that it is impossible to do with OO polymorphism, the polymorphism can not do with dependency injection Therefore, dependency injection into a very frequent demand, and if all done by hand, not only the burden is too heavy, but also error-prone. Invention reflection mechanism is coupled, so it was natural to start the design and development of various special frame dependency injection. These specialized functions for implementing dependency injection assembly or framework is IoC Container.

From this point of view, IoC Container emergence has its historical inevitability. At present, perhaps the most famous IoC framework on Spring IoC component of the Java platform, and on the .NET platform also Spring.NET, and Unity.

IoC Container Classification

We've already discussed three types of dependency injection, however, want to classify IoC Container by the way is very difficult, because now IoC Container design are perfect, almost all support dependency injection. However, according to the characteristics and usage of different frameworks, or can speak IoC Container into two broad categories.

    • Heavyweight IoC Container
      called heavyweight IoC Container, refers to the general external profile (usually XML) as the dependent source, and the system hosting instances of each class IoC Container. This IoC Container, usually undertake all of the entire system is almost polymorphisms dependency injection work, and undertake the work of all instances of the service class, and examples of these rely on an external profile, such IoC Container, like by a file that defines the entire system multi-state structure, large field of view, you want to manage this well IoC Container, it requires a certain architecture design capability and rich experience.

      Spring and Spring.NET are examples of heavyweight IoC Container. In general, this is more than IoC Container stability and lack of activity, for low living polymorphism dependency injection.
    • Lightweight IoC Container

      Another IoC Container, generally do not rely on external profile, and the main parameters used to pass or Construtor Setter injection, which is called lightweight IoC Container IoC Container. This framework is flexible, easy to use, but often unstable, but also on the program parameters are a string of points, therefore, unsuitable for large scale replacement requires relatively stable and low living polymorphism, and for high activity polymorphic sex, have a good effect.

      Unity is a typical lightweight IoC Container.

 The following quote https://www.cnblogs.com/RayWang/p/11128554.html  article using IOC container AuotoFac

.Net Framework framework which mainly how to introduce AutoFac as a container and how to use AuotoFac -based, .Net Core Framework In addition to studying the introduction of two ways AutoFac, but also the use of reflective skills of its own DI framework of a preliminary package to achieve the same effect dependency injection.
Project structure below:

project name Types of frame
Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc Core container Class Library .NET Core 2.2
Ray.EssayNotes.AutoFac.Infrastructure.Ioc Framework container Class Library .NET Framework 4.5
Ray.EssayNotes.AutoFac.Model Physical layer Class Library .NET Framework 4.5
Ray.EssayNotes.AutoFac.Repository Storage layer Class Library .NET Framework 4.5
Ray.EssayNotes.AutoFac.Service Yewuluojiceng Class Library .NET Framework 4.5
Ray.EssayNotes.AutoFac.ConsoleApp Console main program Console project .NET Framework 4.5
Ray.EssayNotes.AutoFac.CoreApi Core WebApi main program Core Api project .NET Core 2.2
Ray.EssayNotes.AutoFac.NetFrameworkApi Framework WebApi main program Framework WebApi project .NET Framework 4.5
Ray.EssayNotes.AutoFac.NetFrameworkMvc Framework MVC main program Framework MVC project .NET Framework 4.5

GitHub Source Address: https://github.com/WangRui321/Ray.EssayNotes.AutoFac




DI theoretical foundation #

Reliance #

依赖,简单说就是,当一个类需要另一个类协作来完成工作的时候就产生了依赖。

这也是耦合的一种形式,但是是不可避免的。

我们能做的不是消灭依赖,而是让依赖关系更清晰、更易于控制。

举个例子,比如标准的三层架构模式

名称 职责 举例
界面层(UI) 负责展示数据 StudentController
业务逻辑层(BLL) 负责业务逻辑运算 StudentService
数据访问层(DAL) 负责提供数据 StudentRepository

数据访问层(DAL)代码:

Copy
    /// <summary>
    /// 学生仓储 /// </summary> public class StudentRepository { public string GetName(long id) { return "学生张三";//造个假数据返回 } }

业务层(BLL)代码:

Copy
    /// <summary>
    /// 学生逻辑处理 /// </summary> public class StudentService { private readonly StudentRepository _studentRepository; public StudentService() { _studentRepository = new StudentRepository(); } public string GetStuName(long id) { var stu = _studentRepository.Get(id); return stu.Name; } }

其中,StudentService的实现,就必须要依赖于StudentRepository。

而且这是一种紧耦合,一旦StudentRepository有更改,必然导致StudentService的代码同样也需要更改,如果改动量特别大话,这将是程序员们不愿意看到的。

面向接口#

面向是为了实现一个设计原则:要依赖于抽象,而不是具体的实现

还拿上面的例子说明,现在我们添加一个DAL的接口层,IStudentRepository,抽象出所需方法:

Copy
    /// <summary>
    /// 学生仓储interface /// </summary> public interface IStudentRepository { string GetName(long id); }

然后让StudentRepository去实现这个接口:

Copy
    /// <summary>
    /// 学生仓储 /// </summary> public class StudentRepository : IStudentRepository { public string GetName(long id) { return "学生张三";//造个假数据返回 } }

现在我们在StudentService里只依赖于IStudentRepository,以后的增删改查都通过IStudentRepository这个抽象来做:

Copy
    /// <summary>
    /// 学生逻辑处理 /// </summary> public class StudentService { private readonly IStudentRepository _studentRepository; public StudentService() { _studentRepository = new StudentRepository(); } public string GetStuName(long id) { var stu = _studentRepository.Get(id); return stu.Name; } }

这样做的好处有两个,一个是低耦合,一个是职责清晰。

如果对此还有怀疑的话,我们可以想象一个情景,就是负责写StudentService的是程序员A,负责写StudentRepository的是另一个程序员B,那么:

  • 针对程序员A
Copy
我只需要关注业务逻辑层面,
如果我需要从仓储层拿数据库的数据,
比如我需要根据Id获取学生实体,
那么我只需要去IStudentRepository找Get(long id)函数就可以了,
至于实现它的仓储怎么实现这个方法我完全不用管,
你怎么从数据库拿数据不是我该关心的事情。
  • 针对程序员B
Copy
我的工作就是实现IStudentRepository接口的所有方法就行了,
简单而明确,
至于谁来调用我,我不用管。
IStudentRepository里有根据Id获取学生姓名的方法,
我实现了就行,
至于业务逻辑层拿这个名字干啥,
那不是我要关心的事情。

这样看的话是不是彼此的职责就清晰多了,更进一步再举个极端的例子:

比如程序员B是个实习生,整天划水摸鱼,技术停留在上个世纪,结果他写的仓储层读取数据库全部用的手写sql语句的方式,极难维护,后来被领导发现领了盒饭,公司安排了另一个程序员C来重写仓储层,C这时不需要动其他代码,只需要新建一个仓储StudentNewRepository,然后实现之前的IStudentRepository,C使用Dapper或者EF,写完新的仓储层之后,剩下的只需要在StudentService里改一个地方就行了:

Copy
        public StudentService()
        {
            _studentRepository = new StudentNewRepository(); }

是不是职责清晰多了。

其实对于这个小例子来说,面向接口的优势还不太明显,但是在系统层面优势就会被放大。

比如上面换仓储的例子,虽然职责是清晰了,但是项目里有几个Service就需要改几个地方,还是很麻烦。

原因就是上面讲的,这是一种依赖关系,Service要依赖Repository,有没有一种方法可以让这种控制关系反转过来呢?当Service需要使用Repository,有没有办法让我需要的Repository自己注入到我这里来?

当然有,这就是我们将要实现的依赖注入。

使用依赖注入后你会发现,当C写完新的仓储后,业务逻辑层(StudentService)是不需要改任何代码的,所有的Service都不需要一个一个去改,直接在注入的时候修改规则,不要注入以前老的直接注入新的仓储就可以了。

面向接口后的架构:

名称 职责 举例
界面层(UI) 负责展示数据 StudentController
业务逻辑抽象层(InterfaceBLL) 业务逻辑运算抽象接口 IStudentService
业务逻辑层(BLL) 负责业务逻辑运算 StudentService
数据访问抽象层(InterfaceDAL) 数据访问抽象接口 IStudentRepository
数据访问层(DAL) 负责提供数据 StudentRepository

什么是IoC#

IoC,全称Inversion of Control,即“控制反转”,是一种设计原则,最早由Martin Fowler提出,因为其理论提出时间和成熟时间相对较晚,所以并没有被包含在GoF的《设计模式》中。

什么是DI#

DI,全称Dependency Injection,即依赖注入,是实现IoC的其中一种设计方法。

其特征是通过一些技巧,将依赖的对象注入到调用者当中。(比如把Repository注入到Service当中)

这里说的技巧目前主要指的就是引入容器,先把所有会产生依赖的对象统一添加到容器当中,比如StudentRepository和StudentService,把分配权限交给容器,当StudentService内部需要使用StudentRepository时,这时不应该让它自己new出来一个,而是通过容器,把StudentRepository注入到StudentService当中。

这就是名称“依赖注入”的由来。

DI和IoC有什么区别#

这是个老生常谈的问题了,而且这两个名字经常在各种大牛和伪大牛的吹逼现场频繁出现 ,听的新手云里雾里,莫名感到神圣不可侵犯。那么DI和IoC是同一个东西吗?如果不是,它们又有什么区别呢?

回答很简单:不是一个东西

区别也很简单,一句话概括就是:IoC是一种很宽泛的理念,DI是实现了IoC的其中一种方法

说到这里我已经感觉到屏幕后的你性感地添了一下嘴唇,囤积好口水,准备开始喷我了。

先别慌,我有证据,我们先来看下微软怎么说:

ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies.

地址:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2

翻译过来就是“ASP.NET Core支持依赖注入(DI)的软件设计模式,该模式是一种在类和它依赖的对象之间实现了控制反转(IoC)的技术”。

如果有人觉得辣鸡微软不够权威,那我们去看下IoC以及DI这两个概念的发明人——Martin Fowler怎么说:

几位轻量级容器的作者曾骄傲地对我说:这些容器非常有用,因为它们实现了控制反转。这样的说辞让我深感迷惑:控制反转是框架所共有的特征,如果仅仅因为使用了控制反转就认为这些轻量级容器与众不同,就好象在说我的轿车是与众不同的,因为它有四个轮子。
因此,我想我们需要给这个模式起一个更能说明其特点的名字——”控制反转”这个名字太泛了,常常让人有些迷惑。经与多位IoC 爱好者讨论之后,我们决定将这个模式叫做”依赖注入”(Dependency Injection)。

地址:http://insights.thoughtworkers.org/injection/

Martin Fowler说的比较委婉,其实说白了就是建议我们,不要乱用IoC装逼,IoC是一种设计理念,很宽泛,你把程序里的一个写死的变量改成从配置文件里读取也是一种控制反转(由程序控制反转为由框架控制),你把这个配置改成用户UI界面的一个输入文本框由用户输入也是一种控制反转(由框架控制反转为由用户自己控制

所以,如果确定讨论的模式是DI,那么就表述为DI,还是尽量少用IoC这种宽泛的表达。

AutoFac#

AutoFac是一个开源的轻量级的DI容器,
也是.net下最受大家欢迎的实现依赖注入的工具之一,
通过AutoFac我们可以很方便的实现一些DI的骚操作。

实战控制台程序依赖注入#

目标很简单,就是控制台程序启动后,将学生姓名打印出来。
程序启动流程是,控制台主程序调用Service层,Service层调用Repository层获取数据(示例项目的仓储层没有连接数据库,只是直接造个假数据返回)。
没有依赖注入的情况下,肯定是主程序会new一个StudentService,StudentService里会new一个StudentRepository,现在引入依赖注入后,就不应该这么new出来了,而是通过容器注入,也就是容器会把StudentRepository自动注入到StudentService当中。

架构#

实体层#

学生实体类StudentEntity:

Copy
namespace Ray.EssayNotes.AutoFac.Model
{
    /// <summary>学生实体</summary> public class StudentEntity { /// <summary>唯一标识</summary> public long Id { get; set; } /// <summary>姓名</summary> public string Name { get; set; } /// <summary>成绩</summary> public int Grade { get; set; } } }

仓储层#

IStudentRepository接口:

Copy
using Ray.EssayNotes.AutoFac.Model;

namespace Ray.EssayNotes.AutoFac.Repository.IRepository
{
    /// <summary>学生仓储interface</summary> public interface IStudentRepository { string GetName(long id); } }

StudentRepository仓储类:

Copy
using Ray.EssayNotes.AutoFac.Model;
using Ray.EssayNotes.AutoFac.Repository.IRepository;

namespace Ray.EssayNotes.AutoFac.Repository.Repository
{
    /// <summary> /// 学生仓储 /// </summary> public class StudentRepository : IStudentRepository { public string GetName(long id) { return "学生张三";//造个假数据返回 } } }

Service层#

IStudentService接口

Copy
namespace Ray.EssayNotes.AutoFac.Service.IService
{
    /// <summary> /// 学生逻辑处理interface /// </summary> public interface IStudentService { string GetStuName(long id); } }

StudentService类:

Copy
using Ray.EssayNotes.AutoFac.Repository.IRepository;
using Ray.EssayNotes.AutoFac.Repository.Repository;
using Ray.EssayNotes.AutoFac.Service.IService;

namespace Ray.EssayNotes.AutoFac.Service.Service { /// <summary> /// 学生逻辑处理 /// </summary> public class StudentService : IStudentService { private readonly IStudentRepository _studentRepository; /// <summary> /// 构造注入 /// </summary> /// <param name="studentRepository"></param> public StudentService(IStudentRepository studentRepository) { _studentRepository = studentRepository; } public string GetStuName(long id) { var stu = _studentRepository.Get(id); return stu.Name; } } } 

其中构造函数是一个有参的函数,参数是学生仓储,这个后面依赖注入时会用。

AutoFac容器#

需要先通过Nuget导入Autofac包:

Copy
using System;
using System.Reflection;
//
using Autofac;
using Autofac.Core; // using Ray.EssayNotes.AutoFac.Repository.IRepository; using Ray.EssayNotes.AutoFac.Repository.Repository; using Ray.EssayNotes.AutoFac.Service.IService; using Ray.EssayNotes.AutoFac.Service.Service; namespace Ray.EssayNotes.AutoFac.Infrastructure.Ioc { /// <summary> /// 控制台程序容器 /// </summary> public static class Container { /// <summary> /// 容器 /// </summary> public static IContainer Instance; /// <summary> /// 初始化容器 /// </summary> /// <returns></returns> public static void Init() { //新建容器构建器,用于注册组件和服务 var builder = new ContainerBuilder(); //自定义注册 MyBuild(builder); //利用构建器创建容器 Instance = builder.Build(); } /// <summary> /// 自定义注册 /// </summary> /// <param name="builder"></param> public static void MyBuild(ContainerBuilder builder) { builder.RegisterType<StudentRepository>().As<IStudentRepository>(); builder.RegisterType<StudentService>().As<IStudentService>(); } } } 

其中:

  • public static IContainer Instance
    为单例容器
  • Init()方法
    用于初始化容器,即往容器中添加对象,我们把这个添加的过程称为注册(Register)。
    ContainerBuilder为AutoFac定义的容器构造器,我们通过使用它往容器内注册对象。
  • MyBuild(ContainerBuilder builder)方法
    我们具体注册的实现函数。RegisterType是AutoFac封装的一种最基本的注册方法,传入的泛型(StudentService)就是我们欲添加到容器的对象;As函数负责绑定注册对象的暴露类型,一般是以其实现的接口类型暴露,这个暴露类型是我们后面去容器内查找对象时使用的搜索标识,我们从容器外部只有通过暴露类型才能找到容器内的对象。

主程序#

需要先Nuget导入AutoFac程序包:

Copy
using System;
//
using Autofac;
//
using Ray.EssayNotes.AutoFac.Infrastructure.Ioc; using Ray.EssayNotes.AutoFac.Service.IService; namespace Ray.EssayNotes.AutoFac.ConsoleApp { class Program { static void Main(string[] args) { Container.Init();//初始化容器,将需要用到的组件添加到容器中 PrintStudentName(10001); Console.ReadKey(); } /// <summary> /// 输出学生姓名 /// </summary> /// <param name="id"></param> public static void PrintStudentName(long id) { //从容器中解析出对象 IStudentService stuService = Container.Instance.Resolve<IStudentService>(); string name = stuService.GetStuName(id); Console.WriteLine(name); } } }

进入Main函数,先调用容器的初始化函数,该函数执行成功后,StudentRepository和StudentService就被注册到容器中了。
然后调用打印学生姓名的函数,其中Resolve()方法是AutoFac封装的容器的解析方法,传入的泛型就是之前注册时的暴露类型,下面可以详细看下这一步到底发生了哪些事情:

  • 容器根据暴露类型解析对象

也就是容器会根据暴露类型IStudentService去容器内部找到其对应类(即StudentService),找到后会试图实例化一个对象出来。

  • 实例化StudentService

AutoFac容器在解析StudentService的时候,会调用StudentService的构造函数进行实例化。

  • 构造注入

AutoFac容器发现StudentService的构造函数需要一个IStudnetRepository类型的参数,于是会自动去容器内寻找,根据这个暴露类型找到对应的StudnetRepository后,自动将其注入到了StudentService当中

经过这几步,一个简单的基于依赖注入的程序就完成了。

结果#

我们将控制台程序设置为启动项目,点击运行,如图调用成功:

如果把调试断点加在容器初始化函数里,可以很清晰的看到哪些对象被注册到了容器里:

补充#

使用控制台程序本来是为了突出容器的概念,但是容易造成一些误解,DI的最终形态可以参考源码里的Api项目和MVC项目,本来想循序渐进,先第一章控制台引入容器的概念,然后第二章讲批量注册、注入泛型、生命周期域管理,第三章讲Api和MVC项目,最后两章讲下.net core的DI,但是这里还是先说下吧:

  • 误解1:每次添加Service和Repository都要去注册,不是更麻烦?

其实是不需要一个一个注册的,运用批量注册后容器内部的代码是这样的,可以直接批量注册所有的:

Copy
    /// <summary>
    /// .net framework MVC程序容器 /// </summary> public static class MvcContainer { public static IContainer Instance; /// <summary> /// 初始化容器 /// </summary> /// <param name="func"></param> /// <returns></returns> public static void Init(Func<ContainerBuilder, ContainerBuilder> func = null) { //新建容器构建器,用于注册组件和服务 var builder = new ContainerBuilder(); //注册组件 MyBuild(builder); func?.Invoke(builder); //利用构建器创建容器 Instance = builder.Build(); //将AutoFac设置为系统DI解析器 System.Web.Mvc.DependencyResolver.SetResolver(new AutofacDependencyResolver(Instance)); } public static void MyBuild(ContainerBuilder builder) { Assembly[] assemblies = Helpers.ReflectionHelper.GetAllAssembliesWeb(); //批量注册所有仓储 && Service builder.RegisterAssemblyTypes(assemblies)//程序集内所有具象类(concrete classes) .Where(cc => cc.Name.EndsWith("Repository") |//筛选 cc.Name.EndsWith("Service")) .PublicOnly()//只要public访问权限的 .Where(cc => cc.IsClass)//只要class型(主要为了排除值和interface类型) .AsImplementedInterfaces();//自动以其实现的所有接口类型暴露(包括IDisposable接口) //注册泛型仓储 builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>)); //注册Controller Assembly mvcAssembly = assemblies.FirstOrDefault(x => x.FullName.Contains(".NetFrameworkMvc")); builder.RegisterControllers(mvcAssembly); } }

误解2:每次使用都要解析下,还不如直接new
好吧,其实也是不需要自己去解析的,最终形态的Controller入口是这样的,直接在构造函数里写就行了:

Copy
    public class StudentController : Controller
    {
        private readonly IStudentService _studentService; public StudentController(IStudentService studentService) { _studentService = studentService; } /// <summary> /// 获取学生姓名 /// </summary> /// <param name="id"></param> /// <returns></returns> public string GetStuNameById(long id) { return _studentService.GetStuName(id); } }

就是直接在构造函数里注入就可以了。

  • 误解3:依赖注入是不是过度设计?

首先DI是一个设计模式(design pattern),其本身完全不存在过不过度的问题,这完全取决于用的人和怎么用。
另外,在.NET Core中,DI被提到了一个很重要的地位,如果想要了解.NET Core,理解DI是必不可少的。

Guess you like

Origin www.cnblogs.com/hudean/p/11677372.html