依赖注入:控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
依赖倒置原则(DIP):一种软件架构设计的原则(抽象概念)。
控制反转(IoC):一种反转流、依赖和接口的方式(DIP的具体实现方式)。
依赖注入(DI):IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)。
IoC容器:依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架)。
IOC和DI区别和联系
1、IOC控制反转的目标,IOC实现的环节
2、DI实现的手段,没有DI就没有IOC
3、正因为要实现IOC,所以才诞生了DI技术手段。
依赖倒置原则:
- 高层模块不应该依赖低层模块,两者应该依赖抽象
- 抽象不应该依赖细节
- 细节应该依赖抽象
依赖未倒置的场景如下:
![依赖未倒置](https://i-blog.csdnimg.cn/blog_migrate/d6c7b55deabef2ea4b2d2f5841f9ced0.png)
依赖倒置的场景如下:
![依赖倒置](https://i-blog.csdnimg.cn/blog_migrate/5fd4e0ea513265d2ee4805d9c64e94c5.png)
控制反转(IoC)
DIP是一种 软件设计原则,它仅仅告诉你两个模块之间应该如何依赖,但是它并没有告诉如何做。IoC则是一种 软件设计模式,它告诉你应该如何做,来解除相互依赖模块的耦合。控制反转(IoC),它为相互依赖的组件提供抽象,将依赖(低层模块)对象的获得交给第三方(系统)来控制,即依赖对象不在被依赖模块的类中直接通过new来获取。
做过简单得三层都遇到server层会依赖Dal层直接访问数据库,假设我们现在用得是sqlserver,需要定义一个sqlserverDal类,用来数据库操作。
public class SqlServerDal
{
public void Add()
{
Console.WriteLine("插入操作");
}
}
public class UserServer
{
private readonly SqlServerDal SqlServerDal = new SqlServerDal();
public void AddUser()
{
//插入用户数据
SqlServerDal.Add();
}
}
假如过几天系统要换成mysql,是不是需要定义一个mysql类,用来数据库操作
public class MysqlDal
{
public void Add()
{
Console.WriteLine("插入操作");
}
}
public class UserServer
{
private readonly MysqlDal mysqlDal = new MysqlDal();
public void AddUser()
{
//插入用户数据
mysqlDal.Add();
}
}
费了九牛二虎之力,程序终于跑起来了!试想一下,如果下次再换成postgres数据库,那我们是不是还得重新修改代码?
显然,这不是一个良好的设计,组件之间高度耦合,可扩展性较差,它违背了DIP原则。cotroller层不应该依赖于低层模块SqlServerDal,MysqlDal,两者应该依赖于抽象。那么我们是否可以通过IoC来优化代码呢?答案是肯定的。IoC有2种常见的实现方式:依赖注入和服务定位。其中,依赖注入使用最为广泛。下面我们将深入理解依赖注入(DI),并学会使用。
依赖注入(DI)
控制反转(IoC)一种重要的方式,就是将依赖对象的创建和绑定转移到被依赖对象类的外部来实现。
1、 构造函数注入
我们需要定义一个接口工厂用来存放操作得方法。IFactory
public interface IFactory
{
void Add();
}
实现类只需要继承接口,实现接口里得方法即可。
public class SqlServerDal:IFactory
{
public void Add()
{
Console.WriteLine("插入操作");
}
}
服务层构造函数传入实现类得接口即可。
public class UserServer
{
private readonly IFactory factory;
public UserServer(IFactory _factory)
{
factory= _factory;
}
public void AddUser()
{
factory.Add();
}
}
控制台直接调用服务层得类即可。
static void Main(string[] args)
{
SqlServerDal sqlServerDal = new SqlServerDal();
UserServer userServer = new UserServer(sqlServerDal);
userServer.AddUser();
}
假如要换成mysql数据库操作时,只需要mysqlDal继承IFactory就可以完成功能,再换postgres也是同样继承IFactory就可以完成功能。
2、属性注入
顾名思义,属性注入是通过属性来传递依赖。因此,我们首先需要在依赖类server中定义一个属性:
public class UserServer
{
private IFactory _factory;
public IFactory Factory
{
get { return _factory; }
set { value=_factory; }
}
public void AddUser()
{
_factory.Add();
}
}
控制台调用如下:
static void Main(string[] args)
{
SqlServerDal sqlServerDal = new SqlServerDal();
UserServer userServer = new UserServer();
userServer.Factory = sqlServerDal;
userServer.AddUser();
}
3、接口注入
相比构造函数注入和属性注入,接口注入显得有些复杂,使用也不常见。具体思路是先定义一个接口,包含一个设置依赖的方法。然后依赖类,继承并实现这个接口。
首先定义一个接口:
public interface IDenpendent
{
void setFactory(IFactory factory);
}
依赖实现这个接口:
public class UserServer : IDenpendent
{
private IFactory factory;//定义一个私有变量保存抽象
//实现接口
public void setFactory(IFactory _factory)
{
factory = _factory;
}
public void Add()
{
factory.Add();
}
}
控制台程序通过setFactory方法传递依赖:
class Program
{
static void Main(string[] args)
{
SqlServerDal serverDal = new SqlServerDal();//在外部创建依赖对象
UserServer userServer = new UserServer();
userServer.SetDependent(serverDal);//传递依赖
userServer.Add();
}
}
我们同样能得到上述的输出结果。
虽说上述三种注入形式避免了修改userServer类,但仍然避免不了修改控制台输出的方法。这样来看,减轻了userServer类的负担,但控制台输出端就必须判断使用的数据库,因此:上述三种注入形式的代码违背了设计模式的开闭原则,仍然有不小的瑕疵,如果解决这个问题呢?
IoC容器
对于大型项目来说,相互依赖的组件比较多。如果还用手动的方式,自己来创建和注入依赖的话,显然效率很低,而且往往还会出现不可控的场面。正因如此,IoC容器诞生了。IoC容器实际上是一个DI框架,它能简化我们的工作量。目前,比较流行的Ioc容器有以下几种:Ninject、Castle Windsor、Autofac、StructureMap、Unity、Spring.NET、LightInject 等
我批量依赖注入主要用到Autofac+Autofac.MVC5
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
//其他代码省略
AutofacConfig.Register();
}
}
using Autofac;
using Autofac.Integration.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Mvc;
namespace ******.Web.App_Start
{
public class AutofacConfig
{
/// <summary>
/// 负责调用autofac框架实现业务逻辑层和数据仓储层程序集中的类型对象的创建
/// 负责创建MVC控制器类的对象(调用控制器中的有参构造函数),接管DefaultControllerFactory的工作
/// </summary>
public static void Register()
{
//实例化一个autofac的创建容器
var builder = new ContainerBuilder();
//告诉Autofac框架,将来要创建的控制器类存放在哪个程序集 (Wchl.CRM.WebUI)
Assembly controllerAss = Assembly.Load("******.Web");
builder.RegisterControllers(controllerAss);
//告诉autofac框架注册数据仓储层所在程序集中的所有类的对象实例
Assembly respAss = Assembly.Load("*******.Repository");
//创建respAss中的所有类的instance以此类的实现接口存储
builder.RegisterTypes(respAss.GetTypes()).AsImplementedInterfaces();
//告诉autofac框架注册业务逻辑层所在程序集中的所有类的对象实例
Assembly serpAss = Assembly.Load("*******.Services");
//创建serAss中的所有类的instance以此类的实现接口存储
builder.RegisterTypes(serpAss.GetTypes()).AsImplementedInterfaces();
//创建一个Autofac的容器
var container = builder.Build();
//将MVC的控制器对象实例 交由autofac来创建
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}
}