DI的理论基础
依赖
什么是依赖?简单说就是类和类之间的关系,一个类需要另一个类来协同工作的时候就产生了类的依赖
这种类之间的依赖就是耦合,我们不能消除耦合(无法避免),但是可以通过DI让这种耦合依赖清晰和便于管理
举例:经典的三层架构
UI:界面
BLL:业务逻辑处理
DAL:数据访问
数据访问DAL层的代码:
/// <summary>
/// 仓储
/// </summary>
public class TestRepository
{
public string GetName(long id)
{
return "张三";
}
}
业务层(BLL)代码:
/// <summary>
/// 逻辑处理
/// </summary>
public class TestService
{
private readonly TestRepository m_testRepository;
public TestService()
{
m_testRepository = new TestRepository();
}
public string GetName(long id)
{
var name = m_testRepository.GetName(id);
return name.Name;
}
}
其中TestService是需要依赖TestRepository的,这是一种紧耦合,一旦TestRepository有更改,必然导致TestService的代码同样需要更改,如果改动量比较大的话,这就很炸了
面向接口编程
面向是为了实现一个设计原则:要依赖抽象,而不是具体的实现。
比如上面的例子给DAL层加一个接口,比如ITestRepository,实现代码:
/// <summary>
/// 仓储interface
/// </summary>
public interface ITestRepository
{
string GetName(long id);
}
这个时候BLL层的TestService就可以依赖ITestRepository,比如:
/// <summary>
/// 逻辑处理
/// </summary>
public class TestService
{
private readonly ITestRepository m_testRepository;
public TestService()
{
m_testRepository = new TestRepository();
}
public string GetStuName(long id)
{
var name = m_testRepository.GetName(id);
return name.Name;
}
}
这样做的好处有两个,一个是低耦合,一个是职责清晰。
比如写仓储层的只需要实现ITestRepository的接口就行了,不用管谁调用我,写BLL层Service的只需要调用ITestRepository里面的接口,不用管接口是怎么实现的。再比如:
如果写仓储TestRepository的人写的很烂,不如重写,这个时候就可以重新写一个ITestRepository接口实现NewTestRespository,重写完成后Service层调用也只需要换掉对应实现:
public TestService()
{
m_testRepository = new NewTestRespository();
}
但其实这样子问题也很大,比如庞大的系统中用到了N多的Service,就要修改N多的地方,还是很麻烦。
原因就是上面讲的,这是一种依赖关系,Service要依赖Repository,有没有一种方法可以让这种控制关系反转过来呢?当Service需要使用Repository时,有没有办法让我需要的Repository自己注入到我这里来?
当然有,这就是我们将要实现的依赖注入。
使用依赖注入后你会发现,当重写完新的仓储NewTestRespository后,BLL业务逻辑层(TestService)是不需要改任何代码的,所有的Service都不需要一个一个去改,直接在注入的时候修改规则,不要注入以前老的直接注入新的仓储就可以了
面向接口后的架构:
名称 | 职责 | 举例 |
---|---|---|
界面层(UI) | 负责展示数据 | TestController |
业务逻辑抽象层(InterfaceBLL) | 业务逻辑运算抽象接口 | ITestService |
业务逻辑层(BLL) | 负责业务逻辑运算 | TestService |
数据访问抽象层(InterfaceDAL) | 数据访问抽象接口 | ITestRepository |
数据访问层(DAL) | 负责提供数据 | TestRepository |
什么是IoC
IoC,全称Inversion of Control,即“控制反转”,是一种设计原则,由Martin Fowler提出
什么是DI
DI,全称Dependency Injection,即依赖注入,是实现IoC的其中一种设计方法。
其特征是通过一些技巧,将依赖的对象注入到调用者当中。(比如把Repository注入到Service当中)
这里说的技巧目前主要指的就是容器,先把所有会产生依赖的对象统一添加到容器当中,比如TestRepository和TestService,把分配权限交给容器,当TestService内部需要使用TestRepository时,这时不应该让它自己new出来一个,而是通过容器,把TestRepository注入到TestService当中。
这就是名称“依赖注入”的由来。
DI和IoC有什么区别
DI和IoC是同一个东西吗?答案:不是一个东西。
区别也很简单:IoC是一种理念,DI是实现了IoC的其中一种方法,一种IOC的实现技术。
IoC是一种设计理念,很宽泛,你把程序里的一个写死的变量改成从配置文件里读取也是一种控制反转(由程序控制反转为由框架控制),你把这个配置改成用户UI界面的一个输入文本框由用户输入也是一种控制反转(由框架控制反转为由用户自己控制)。
所以IOC和DI不是一个东西,IOC是概念,DI是其中的一个手段
AutoFac
手把手写一个AutoFac的控制台应用程序来说明如何简单使用这个容器
程序启动流程:Main------>Service-------->Repository
首先建立对应的接口和类:
Repository:
/// <summary>
///仓储层接口
/// </summary>
public interface ITestRepository
{
/// <summary>
/// 通过ID获取名称
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
string GetNameById(string id);
}
public class TestRepository : ITestRepository
{
public string GetNameById(string id)
{
return "张三";
}
}
Service:
/// <summary>
/// 服务层接口
/// </summary>
public interface ITestService
{
/// <summary>
/// 通过ID获取名称
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
string GetNameById(string id);
}
public class TestService : ITestService
{
private ITestRepository m_testRepository;
//注入接口
public TestService(ITestRepository testRepository)
{
m_testRepository = testRepository;
}
public string GetNameById(string id)
{
return m_testRepository.GetNameById(id);
}
}
Autofac容器初始化及注册
public static class Container
{
public static Autofac.IContainer Instance;
public static void Init()
{
//新建容器构建器,用于注册组件和服务
var builder = new ContainerBuilder();
Register(builder);
//利用构建器创建容器
Instance = builder.Build();
}
private static void Register(ContainerBuilder builder)
{
builder.RegisterType<TestRepository>().As<ITestRepository>();
builder.RegisterType<TestService>().As<ITestService>();
}
}
-
public static IContainer Instance为单例容器
-
ContainerBuilder为AutoFac定义的容器构造器,通过它往容器内注册对象。
-
RegisterType是AutoFac封装的一种最基本的注册方法,传入的泛型(TestRepository)就是欲添加到容器的对象;As负责绑定注册对象的类型,一般是以其实现的接口类型暴露,从容器外部只能通过暴露的类型才能找到容器对象
Main函数
class Program
{
static void Main(string[] args)
{
Container.Init();
GetName();
Console.ReadKey();
}
private static void GetName()
{
//从容器中解析对象
ITestService service = Container.Instance.Resolve<ITestService>();
string name = service.GetNameById("123");
Console.WriteLine($"name is {name}");
}
}
这里Container.Instance.Resolve<ITestService>();实现有以下几步
1、容器通过ITestService去容器的内部找对应的类(TestService)找到以后会尝试实例化这个类,这个时候发现这个类的构造函数是有参数的
2、容器发现这个参数是ITestRepository,就去容器内部找这个类型所对应的类,然后再实例化自动注入TestService类中
3、至此,一个简单的依赖注入就完成了
我们可以通过断点在Instance这个单例容器中发现容器中已经注册的对象,就不上图了