.NET-DI和IoC
.NET-DI和IoC
在 C# 开发中,依赖注入(Dependency Injection, DI)和控制反转(Inversion of Control, IoC)是两个非常重要的设计模式
它们帮助实现松耦合、高内聚的软件架构
DI的相关概念:
- 服务
- 注册服务
- 服务容器
- 查询服务
- 对象生命周期
- Transient:瞬态
- Scoped:范围
- Singleton:单例
服务容器
实例化ServiceCollection服务容器,并使用AddXXX方法将对象加入到容器中(可选择不同的生命周期)
using Microsoft.Extensions.DependencyInjection;
namespace 依赖注入
{
internal class Program
{
static void Main(string[] args)
{
// 创建服务容器
var serviceCollection = new ServiceCollection();
// 注册服务,将对象加入到IoC容器中
// 对象生命周期设置为瞬态
serviceCollection.AddTransient<ITestService, TestServiceImpl>();
// 构建服务提供者
using (ServiceProvider sp = serviceCollection.BuildServiceProvider())
{
// 解析服务
var testServiceImpl = sp.GetService<ITestService>();
}
}
}
interface ITestService
{
}
class TestServiceImpl : ITestService
{
}
}
生命周期的选择
当类中无状态(即无属性和字段)选择Singleton
如果类中有状态,且有scope控制,建议Scope,通常Scope控制下的代码都是运行在同一线程中的,并没有并发修改的问题
使用Transient的时候应该谨慎选择
服务提供者
与Java不同,C#的容器注入方式不是属性注入,而是构造器注入,所以加入到IoC容器中的类无法直接实例化出来
所以如果想在main中调用第一个类,则不能实例化,需要使用服务提供者调用GetXXX方法来获取到此对象
GetService
使用GetService方法获取容器中对象时,如果容器中没有对象则返回null
// 查询服务,通过ServiceProvide的方式获取到对象
using (ServiceProvider sp = serviceCollection.BuildServiceProvider())
{
// 如果容器中注册此对象则返回null
var testServiceImpl = sp.GetService<ITestService>();
}
GetRequiredService
使用GetRequiredService方法获取容器中对象时,如果容器中没有对象则抛出异常
// 查询服务,通过ServiceProvide的方式获取到对象
using (ServiceProvider sp = serviceCollection.BuildServiceProvider())
{
// 区别于GetService, 如果容器中没有注册此对象会直接抛出异常
var testServiceImpl = sp.GetRequiredService<ITestService>();
}
GetServices
不同于Java,同一接口可注册多个服务,也就是可以将多个接口的实现类对象放到IoC容器中
直接调用则会获取到最后一个放入容器的对象
也可以通过GetServices获取到改接口注册的全部服务
using Microsoft.Extensions.DependencyInjection;
namespace 依赖注入
{
internal class Program
{
static void Main(string[] args)
{
// 创建服务容器
var serviceCollection = new ServiceCollection();
// 注册服务,将对象加入到IoC容器中
// 对象生命周期设置为瞬态
serviceCollection.AddTransient<ITestService, TestServiceImpl>();
serviceCollection.AddTransient<ITestService, TestServiceImp2>();
// 解析服务,通过ServiceProvide的方式获取到对象
using (ServiceProvider sp = serviceCollection.BuildServiceProvider())
{
var enumerable = sp.GetServices<ITestService>();
foreach (var testService in enumerable)
{
Console.WriteLine(testService);
}
}
}
}
interface ITestService
{
}
class TestServiceImpl : ITestService
{
}
class TestServiceImp2 : ITestService
{
}
}
扩展方法
在 C# 中,扩展方法是一种非常有用的特性,它允许你在现有类型上添加新的方法,而无需修改该类型的源代码
这对于增强第三方库中的类型或你无法修改的类型特别有用
基本概念
-
静态类:扩展方法必须定义在一个静态类中。
-
静态方法:扩展方法本身必须是静态方法。
-
this 关键字:扩展方法的第一个参数必须使用
this
关键字,后面跟着要扩展的类型。
方法封装
为IServiceCollection接口添加扩展方法,将Add XXX方法进行二次封装
使用扩展方法对注册服务的方法进行封装
using Microsoft.Extensions.DependencyInjection;
namespace LogServices;
/**
* 此类用于编写有关于ConsoleLog的扩展方法
*/
public static class ConsoleLogExtensions
{
public static void AddConsoleLog(this IServiceCollection serviceCollection)
{
serviceCollection.AddScoped<ILogProvider,ConsoleLogProvider>();
}
}
serviceCollection.AddConsoleLog(); // 至此,serviceCollection对象可直接调用扩展方法AddConsoleLog,其作用与下面的方法语义相同
serviceCollection.AddScoped<ILogProvider, ConsoleLogProvider>();
配置读取
如果服务器成为了集群,那么为每台服务器来手动定义配置文件是十分费劲的,我们可以将配置文件中心化,单独做一台服务器专门用于其他服务器调取配置文件使用
但这种配置方式会存在“一改全变”的问题,为了解决这种问题,我们使用可覆盖的配置读取器(编程思想中的override)
当同时存在,服务器配置、环境变量配置、本地文件配置或更多方式是,可覆盖的配置读取器会自动的选择最后一个读取的配置运行