概念
依赖倒置原则(DIP):一种软件架构设计的原则(抽象概念)
控制反转(IOC):IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)
依赖注入(DI):一种反转流、依赖和接口的方式(DIP的具体实现方式)
IOC容器:依赖注入的框架,用来映射依赖,管理对象创建和依赖周期
依赖倒置原则 DIP
在生活中,之前有看到这样的场景,有一个ATM机,只要有一张卡,就可以插入取钱操作,在这个场景下ATM相当于高层模块,而银行卡是下层模块,ATM不依赖某个银行卡,只需要定义好规格参数,就可以正常使用。软件也可以借用这个概念。依赖倒置原则,转换了依赖,高层不依赖与下层模块,下层模块依赖于高层模块。也就是高层模块定义接口,下层模块实现接口。
这种方式的优点
1 程序更柔韧 修改一部分代码而不影响其他模块
2 程序更健壮 修改代码不会让程序崩溃
3 程序更高效 每一个组件轻松耦合,且可复用,提升开发速度
控制反转 IOC
DIP是一种设计原则,他告诉你俩个模块如何相互依赖,但他没有告诉你如何实现
IOC是一种设计模式,他告诉你如何做,来解除相互依赖的耦合,控制反转,它为互相依赖的组件提供抽象,将依赖(底层模块)交给第三方管理(系统)控制,即依赖对象不在被依赖的模块的类中new来获取。
软件设计原则:原则为我们提供指南,它告诉我们什么是对的,什么是错的。它不会告诉我们如何解决问题。它仅仅给出一些准则,以便我们可以设计好的软件,避免不良的设计。一些常见的原则,比如DRY、OCP、DIP等。
软件设计模式:模式是在软件开发过程中总结得出的一些可重用的解决方案,它能解决一些实际的问题。一些常见的模式,比如工厂模式、单例模式等等
举个例子,加入我们今天订单需要发送短信
public class EmailNotification
{
public void SendEmail(Order order)
{
// 发送邮件的代码...
}
}
public class OrderProcess
{
private EmailNotification _notification;public OrderProcess()
{
_notification = new EmailNotification();
}public void ProcessOrder(Order order)
{
// 订单处理逻辑...// 发送通知
_notification.SendEmail(order);
}
}
此时,OrderProcess订单处理类,直接依赖于EmailNotification邮件通知类,我们想改变通知方式,就要更改OrderProcess类,这违反了开闭原则,并且这个设计不够灵活和可扩展
新增短信通知只能新增一个实现类,修改OrderProcess类中的发送消息的对象,如果还要新增更多的通知方式呢,显然这不是一个良好的设计,组件之间相互耦合,扩展性比较差,它也违背了DIP原则,高层模块OrderProcess不应该依赖于下层模块,两者都应该依赖于抽象,我们可以使用IOC来优化代码,可以通过依赖注入和服务定位来实现,接下来我们使用依赖注入DI,查看如何使用
依赖注入DI
控制反转(IoC)一种重要的方式,就是将依赖对象的创建和绑定转移到被依赖对象类的外部来实现。
依赖注入(DI),它提供一种机制,将需要依赖(低层模块)对象的引用传递给被依赖(高层模块)对象
为了应用依赖倒置原则,我们应该定义接口来进行通知:
public interface INotification
{
public void SentMsg(Order order);
}
public class EmailNotification : INotification
{
public void SentMsg(Order order)
{
// 发送邮件的代码...
}
}
public class SmsNotification : INotification
{
public void SentMsg(Order order)
{
// 发送短信的代码...
}
}
public class OrderProcessTwo
{
public INotification _notification;
/// <summary>
/// 构造方法
/// </summary>
/// <param name="notification"></param>
public OrderProcessTwo(INotification notification)
{
_notification = notification;
}public void ProcessOrder(Order order)
{
// 订单处理逻辑...// 发送通知
_notification.SentMsg(order);
}
}//示例
// 使用EmailNotification发送通知
INotification emailNotification = new EmailNotification();
OrderProcessor processorWithEmail = new OrderProcessor(emailNotification);
processorWithEmail.ProcessOrder(new Order());// 使用SmsNotification发送通知
INotification smsNotification = new SmsNotification();
OrderProcessor processorWithSms = new OrderProcessor(smsNotification);
processorWithSms.ProcessOrder(new Order());
此时我们想要更改消息发送方式,只需要在外部更改其他对象,就可以实现更换消息发送方式,而不需要修改OrderProcessor内部代码,此时已经体现了IOC的巧妙
此外还有属性注入和接口注入设计基本一致,可以自行了解
服务定位的实现方式
public class ServiceLocator
{
private static IDictionary<object, object> services = new Dictionary<object, object>();public static void AddService<T>(T service)
{
services[typeof(T)] = service;
}public static T GetService<T>()
{
return (T)services[typeof(T)];
}
}
public class OrderProcessThree
{
private INotification _notification;public OrderProcessThree()
{
// 从服务定位器中获取需要的通知服务
_notification = ServiceLocator.GetService<INotification>();
}public void ProcessOrder(Order order)
{
// 处理订单的逻辑
// ...
_notification.SentMsg(order);
}
}
依赖注入DI的示例实例化十分清晰,它通过其构造函数定义它所依赖的INotification
类型的服务
而服务定位SL实例化过程中并不直接指定其所需的服务,OrderProcessor
需要从ServiceLocator
中查询所需要的服务。这种如果没有适当配置,会出现问题,依赖也不像DI那样明显,推荐使用DI
介绍完这些那么在netcore中如何应用呢
net core基本用法
在 .NET Core 平台下,有一套自带的轻量级 Ioc 框架,如果是 ASP.NET Core 项目,更是在使用主机的时候自动集成了进去,我们在 startup 类中的 ConfigureServices 方法中的代码就是往容器中配置依赖注入关系,如果是控制台项目的话,还需要自己去集成。除此之外,.NET 平台下还有一些第三方依赖注入框架,如 Autofac、Unity、Castle Windsor 等。
推荐原生注入和autofac注入先介绍下原生注入
服务
依赖项注入术语中,服务通常是指向其他对象提供服务的对象,既可以作为其他类的依赖项,也可能依赖于其他服务。服务是 Ioc 容器管理的对象。
三种生命周期
AddSingleton 单例模式:程序启动到关闭,对象只有这一个
AddTransient 瞬时模式:服务在每次请求时被创建
AddScoped 区域模式:每一次使用到服务时都会创建一个新的实例,每一次对该依赖的获取都是一个新实例
语法
在program注入,net5可能会有startup分开写了而已,不要重复注入,会产生多个实例的副本,可能会出现意料之外的问题
builder.Services.AddTransient(typeof(IMyService), typeof(MyService));
builder.Services.AddScoped<IMyService, MyService>();
builder.Services.AddSingleton<IMyService, MyService>();
控制器中构造方法调用
private IMyService _service; public ValuesController(IMyService service) { this._service = service; }
你可以在program中移除或清空服务,可以使用 Replace 和 Remove
// 将容器中注册的IRabbit实现替换为 Rabbit1
builder.Services.Replace(ServiceDescriptor.Transient<IRabbit, Rabbit1>());
// 从容器中 IRabbit 注册的实现 Rabbit1
builder.Services.Remove(ServiceDescriptor.Transient<IRabbit, Rabbit1>());
// 移除 IRabbit服务的所有注册
builder.Services.RemoveAll<IRabbit>();
// 清空容器中的所有服务注册
builder.Services.Clear();
autofac使用
引入包文件 Autofac.Extras.DynamicProxy(Autofac的动态代理,它依赖Autofac,所以可以不用单独引入Autofac)、Autofac.Extensions.DependencyInjection(Autofac的扩展)
如何在自带对象中使用,可以使用工厂模式创建
var builder = WebApplication.CreateBuilder(args); builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); //创建服务提供工厂
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
{//autofac 相关代码
//.....
//推荐写在这里边
var dlls = builder.Configuration.GetSection("Dll").Get<List<string>>();
//注册 推荐这种方式 实现写在方法中 dlls对象是后续批量注入的功能
autofacBuilder.RegisterModule(new AutofacModule(dlls));
});或者这种方式
// 配置AutoFac
var containerBuilder = new ContainerBuilder();
//autofac注入代码..........// 构建AutoFac容器
var container = containerBuilder.Build();
新建一个AutofacModuleRegister模块,继承自Autofac.Module,在Load方法中注册后续依赖注入过程中需要使用的的类型
public class AutofacModuleRegister : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{//你的代码.......
builder.RegisterType<ArticleService>().As<IArticleService>();
}
}
autofac批量注入
服务程序集注入方式 —— 未解耦
这种方式需要添加对项目的引用
public class AutofacModule : Module
{
private List<string> _dlls = new List<string>();public AutofacModule(List<string> dlls)
{
_dlls = dlls;
}
protected override void Load(ContainerBuilder builder)
{//要记得!!!这个注入的是实现类层,不是接口层!不是 IServices
var assemblysServices = Assembly.Load("YouProject.Service");//指定已扫描程序集中的类型注册为提供所有其实现的接口。
autofacBuilder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces();}
}
程序集注入 —— 实现层级引用的解耦
public class AutofacModule : Module
{
private List<string> _dlls = new List<string>();public AutofacModule(List<string> dlls)
{
_dlls = dlls;
}
protected override void Load(ContainerBuilder builder)
{
var basePath = AppContext.BaseDirectory;
var Type = new List<Type>();
List<Assembly> servicesAssembly = new List<Assembly>();
foreach (var item in _dlls)
{
var dllFile = Path.Combine(basePath, item);
if (!File.Exists(dllFile))
{
throw new Exception($"{item}丢失,检查appsettings.json中Autofac配置;如果项目的bin目录下没有该文件,检查是否有引用项目。");
}
servicesAssembly.Add(Assembly.LoadFrom(dllFile));
}// 支持属性注入依赖重复,消费队列单独注入
builder.RegisterAssemblyTypes(servicesAssembly.ToArray())
.AsImplementedInterfaces()
.InstancePerDependency()
.EnableInterfaceInterceptors() // 引用Autofac.Extras.DynamicProxy;
.PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies)
.InterceptedBy(Type.ToArray()); //允许将拦截器服务的列表分配给注册
}
}
Aop日志记录
autofac中有类似中间件的功能不需要,可以实现一些对请求日志记录的功能
首先创建一个类,继承IInterceptor接口,写一些简单记录日志http请求参数
/// <summary>
///拦截器 LogAOP 继承IInterceptor接口
/// </summary>
public class RecordAop : IInterceptor
{
/// <summary>
/// 实例化IInterceptor唯一方法
/// </summary>
/// <param name="invocation">包含被拦截方法的信息</param>
public void Intercept(IInvocation invocation)
{
//记录被拦截方法信息的日志信息
var dataIntercept = $"当前执行方法:{invocation.Method.Name} \r\n" +
$"参数是: {JsonHelper.ObjectToJson(invocation.Arguments)} \r\n";
//在被拦截的方法执行完毕后 继续执行当前方法
invocation.Proceed();dataIntercept += ($"被拦截方法执行完毕,返回结果:{JsonHelper.ObjectToJson(invocation.ReturnValue)} \r\n");
#region 输出到当前项目日志
SerilogHelper.WriteLog("RecordAop", "Intercept", dataIntercept);#endregion
}
}
添加到autofac容器中,在AutofacModule类中新增
var Type = new List<Type>();
builder.RegisterType<RecordAop>();//可以直接替换其他拦截器!一定要把拦截器进行注册
Type.Add(typeof(RecordAop));
实现缓存功能
定义缓存类和接口
/// <summary>
/// 简单的缓存接口,只有查询和添加,以后会进行扩展
/// </summary>
public interface ICaching
{
public object Get(string cacheKey);public void Set(string cacheKey, object cacheValue);
}/// <summary>
/// 实例化缓存接口ICaching
/// </summary>
public class MemoryCaching : ICaching
{
//引用Microsoft.Extensions.Caching.Memory;这个和.net 还是不一样,没有了Httpruntime了
private IMemoryCache _cache;
//还是通过构造函数的方法,获取
public MemoryCaching(IMemoryCache cache)
{
_cache = cache;
}public object Get(string cacheKey)
{
return _cache.Get(cacheKey);
}public void Set(string cacheKey, object cacheValue)
{
_cache.Set(cacheKey, cacheValue, TimeSpan.FromMilliseconds(2000));
}
}
定义一个缓存拦截器
/// <summary>
/// 面向切面的缓存使用
/// </summary>
public class CacheAOP : IInterceptor
{
//通过注入的方式,把缓存操作接口通过构造函数注入
public readonly ICaching _cache;
public CacheAOP(ICaching cache)
{
_cache = cache;
}public void Intercept(IInvocation invocation)
{
//获取自定义缓存键
var cacheKey = CustomCacheKey(invocation);
//根据key获取相应的缓存值
var cacheValue = _cache.Get(cacheKey);
if (cacheValue != null)
{
//将当前获取到的缓存值,赋值给当前执行方法
invocation.ReturnValue = cacheValue;
return;
}
//去执行当前的方法
invocation.Proceed();
//存入缓存
if (!string.IsNullOrWhiteSpace(cacheKey))
{
_cache.Set(cacheKey, invocation.ReturnValue);
}
}//自定义缓存键
private string CustomCacheKey(IInvocation invocation)
{
var typeName = invocation.TargetType.Name;
var methodName = invocation.Method.Name;
var methodArguments = invocation.Arguments.Select(GetArgumentValue).Take(3).ToList();//获取参数列表,我最多需要三个即可string key = $"{typeName}:{methodName}:";
foreach (var param in methodArguments)
{
key += $"{param}:";
}return key.TrimEnd(':');
}
//object 转 string
private string GetArgumentValue(object arg)
{
if (arg is int || arg is long || arg is string)
return arg.ToString();if (arg is DateTime)
return ((DateTime)arg).ToString("yyyyMMddHHmmss");return "";
}
}
添加到autofac容器中,在AutofacModule类中新增
builder.RegisterType<MemoryCaching>().As<ICaching>();// 服务层注册
var Type = new List<Type>();
builder.RegisterType<CacheAOP>();
Type.Add(typeof(CacheAOP));
运行后可以查看效果,不要放到service层中,不然缓存aop会出错,你对service定义的缓存拦截器,你定义到service会出现循环引用
最后,可以运行测试下代码