MediatR 知多少

640?wx_fmt=png

引言

首先不用查字典了,词典查无此词。猜测是作者笔误将Mediator写成MediatR了。废话少说,转入正题。

先来简单了解下这个开源项目MediatR(作者Jimmy Bogard,也是开源项目AutoMapper的创建者,在此表示膜拜):

Simple mediator implementation in .NET. In-process messaging with no dependencies. Supports request/response, commands, queries, notifications and events, synchronous and async with intelligent dispatching via C# generic variance.

.NET中的简单中介者模式实现,一种进程内消息传递机制(无其他外部依赖)。 支持以同步或异步的形式进行请求/响应,命令,查询,通知和事件的消息传递,并通过C#泛型支持消息的智能调度。

如上所述,其核心是一个中介者模式的.NET实现,其目的是消息发送和消息处理的解耦。它支持以单播和多播形式使用同步或异步的模式来发布消息,创建和侦听事件。

中介者模式

既然是对中介者模式的一种实现,那么我们就有必要简要介绍下中介者这个设计模式,以便后续展开。640?wx_fmt=png

中介者模式:用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使耦合松散,而且可以独立地改变它们之间的交互。

看上面的官方定义可能还是有点绕,那么下面这张图应该能帮助你对中介者模式有个直观了解。640?wx_fmt=png

使用中介模式,对象之间的交互将封装在中介对象中。对象不再直接相互交互(解耦),而是通过中介进行交互。这减少了对象之间的依赖性,从而减少了耦合。

那其优缺点也在图中很容易看出:

优点:中介者模式的优点就是减少类间的依赖,把原有的一对多的依赖变成了一对一的依赖,同事类只依赖中介者,减少了依赖,当然同时也降低了类间的耦合

缺点:中介者模式的缺点就是中介者会膨胀得很大,而且逻辑复杂,原本N个对象直接的相互依赖关系转换为中介者和同事类的依赖关系,同事类越多,中介者的逻辑就越复杂。

Hello MeidatR

在开始之前,我们先来了解下其基本用法。

单播消息传输

单播消息传输,也就是一对一的消息传递,一个消息对应一个消息处理。其通过 IRequest来抽象单播消息,用 IRequestHandler进行消息处理。

  1. //构建 消息请求

  2. public class Ping : IRequest<string> { }

  3. //构建 消息处理

  4. public class PingHandler : IRequestHandler<Ping, string> {

  5.    public Task<string> Handle(Ping request, CancellationToken cancellationToken) {

  6.        return Task.FromResult("Pong");

  7.    }

  8. }

  9. //发送 请求

  10. var response = await mediator.Send(new Ping());

  11. Debug.WriteLine(response); // "Pong"

多播消息传输

多播消息传输,也就是一对多的消息传递,一个消息对应多个消息处理。其通过 INotification来抽象多播消息,对应的消息处理类型为 INotificationHandler

  1. //构建 通知消息

  2. public class Ping : INotification { }

  3. //构建 消息处理器1

  4. public class Pong1 : INotificationHandler<Ping> {

  5.    public Task Handle(Ping notification, CancellationToken cancellationToken) {

  6.        Debug.WriteLine("Pong 1");

  7.        return Task.CompletedTask;

  8.    }

  9. }

  10. //构建 消息处理器2

  11. public class Pong2 : INotificationHandler<Ping> {

  12.    public Task Handle(Ping notification, CancellationToken cancellationToken) {

  13.        Debug.WriteLine("Pong 2");

  14.        return Task.CompletedTask;

  15.    }

  16. }


  17. //发布消息

  18. await mediator.Publish(new Ping());

源码解析

对MediatR有了基本认识后,我们来看看源码,研究下其如何实现的。

640?wx_fmt=png

从代码图中我们可以看到其核心的对象主要包括:

  1. IRequest Vs IRequestHandler

  2. INotification Vs INoticifaitonHandler

  3. IMediator Vs Mediator

  4. Unit

  5. IPipelineBehavior

IRequest Vs IRequestHandler

其中 IRequestINotification分别对应单播和多播消息的抽象。 对于单播消息可以决定是否需要返回值选用不同的接口:

  • IRequest - 有返回值

  • IRequest - 无返回值

这里就不得不提到其中巧妙的设计,通过引入结构类型 Unit来代表无返回的情况。

  1. /// <summary>

  2. /// 代表无需返回值的请求

  3. /// </summary>

  4. public interface IRequest : IRequest<Unit> { }


  5. /// <summary>

  6. /// 代表有返回值的请求

  7. /// </summary>

  8. /// <typeparam name="TResponse">Response type</typeparam>

  9. public interface IRequest<out TResponse> : IBaseRequest { }


  10. /// <summary>

  11. /// Allows for generic type constraints of objects implementing IRequest or IRequest{TResponse}

  12. /// </summary>

  13. public interface IBaseRequest { }

同样对于 IRequestHandler也是通过结构类型 Unit来处理不需要返回值的情况。

  1. public interface IRequestHandler<in TRequest, TResponse>

  2.    where TRequest : IRequest<TResponse>

  3. {

  4.    Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken);

  5. }


  6. public interface IRequestHandler<in TRequest> : IRequestHandler<TRequest, Unit>

  7.    where TRequest : IRequest<Unit>

  8. {

  9. }

从上面我们可以看出定义了一个方法名为 Handle返回值为 Task的包装类型,而因此赋予了其具有以同步和异步的方式进行消息处理的能力。我们再看一下其以异步方式进行消息处理(无返回值)的默认实现 AsyncRequestHandler

  1. public abstract class AsyncRequestHandler<TRequest> : IRequestHandler<TRequest>

  2.    where TRequest : IRequest

  3. {

  4.    async Task<Unit> IRequestHandler<TRequest, Unit>.Handle(TRequest request, CancellationToken cancellationToken)

  5.    {

  6.        await Handle(request, cancellationToken).ConfigureAwait(false);

  7.        return Unit.Value;

  8.    }


  9.    protected abstract Task Handle(TRequest request, CancellationToken cancellationToken);

  10. }

从上面的代码来看,我们很容易看出这是装饰模式的实现方式,是不是很巧妙的解决了无需返回值的场景。

最后我们来看下结构类型 Unit的定义:

  1. public struct Unit : IEquatable<Unit>, IComparable<Unit>, IComparable

  2. {

  3.    public static readonly Unit Value = new Unit();


  4.    public static readonly Task<Unit> Task = System.Threading.Tasks.Task.FromResult(Value);

  5.    // some other code

  6. }

IMediator Vs Mediator

640?wx_fmt=png

IMediator主要定义了两个方法 SendPublish,分别用于发送消息和发布通知。其默认实现Mediator中定义了两个集合,分别用来保存请求与请求处理的映射关系。

  1. //Mediator.cs

  2. //保存request和requesthandler的映射关系,1对1。

  3. private static readonly ConcurrentDictionary<Type, object> _requestHandlers = new ConcurrentDictionary<Type, object>();

  4. //保存notification与notificationhandler的映射关系,

  5. private static readonly ConcurrentDictionary<Type, NotificationHandlerWrapper> _notificationHandlers = new ConcurrentDictionary<Type, NotificationHandlerWrapper>();

这里面其又引入了两个包装类: RequestHandlerWrapperNotificationHandlerWrapper。这两个包装类的作用就是用来传递 ServiceFactory委托进行依赖解析。

所以说 Mediator借助 publicdelegateobjectServiceFactory(TypeserviceType);完成对Ioc容器的一层抽象。这样就可以对接任意你喜欢用的Ioc容器,比如:Autofac、Windsor或ASP.NET Core默认的Ioc容器,只需要在注册 IMediator时指定 ServiceFactory类型的委托即可,比如ASP.NET Core中的做法:640?wx_fmt=png

在使用ASP.NET Core提供的原生Ioc容器有些问题:Service registration crashes when registering generic handlers

IPipelineBehavior

640?wx_fmt=png

MeidatR支持按需配置请求管道进行消息处理。即支持在请求处理前和请求处理后添加额外行为。仅需实现以下两个接口,并注册到Ioc容器即可。

  • IRequestPreProcessor 请求处理前接口

  • IRequestPostProcessor 请求处理后接口

其中 IPipelineBehavior的默认实现: RequestPreProcessorBehaviorRequestPostProcessorBehavior分别用来处理所有实现 IRequestPreProcessorIRequestPostProcessor接口定义的管道行为。

而处理管道是如何构建的呢?我们来看下 RequestHandlerWrapperImpl的具体实现:

  1. internal class RequestHandlerWrapperImpl<TRequest, TResponse> : RequestHandlerWrapper<TResponse>

  2.    where TRequest : IRequest<TResponse>

  3. {

  4.    public override Task<TResponse> Handle(IRequest<TResponse> request, CancellationToken cancellationToken,

  5.        ServiceFactory serviceFactory)

  6.    {

  7.        Task<TResponse> Handler() => GetHandler<IRequestHandler<TRequest, TResponse>>(serviceFactory).Handle((TRequest) request, cancellationToken);


  8.        return serviceFactory

  9.            .GetInstances<IPipelineBehavior<TRequest, TResponse>>()

  10.            .Reverse()

  11.            .Aggregate((RequestHandlerDelegate<TResponse>) Handler, (next, pipeline) => () => pipeline.Handle((TRequest)request, cancellationToken, next))();

  12.    }

  13. }

就这样一个简单的函数,涉及的知识点还真不少,说实话我花了不少时间来理清这个逻辑。 那都涉及到哪些知识点呢?我们一个一个的来理一理。

  1. C# 7.0的新特性 - 局部函数

  2. C# 6.0的新特性 - 表达式形式的成员函数

  3. Linq高阶函数 - Aggregate

  4. 匿名委托

  5. 构造委托函数链

关于第1、2个知识点,请看下面这段代码:

  1. public delegate int SumDelegate();//定义委托

  2. public static void Main()

  3. {

  4.    //局部函数(在函数内部定义函数)

  5.    //表达式形式的成员函数, 相当于 int Sum() { return 1 + 2;}

  6.    int Sum() => 1 + 2;


  7.    var sumDelegate = (SumDelegate)Sum;//转换为委托

  8.    Console.WriteLine(sumDelegate());//委托调用,输出:3

  9. }

再看第4个知识点,匿名委托:

  1. public delegate int SumDelegate();


  2. SumDelegate delegater1 = delegate(){ return 1+2; }

  3. //也相当于

  4. SumDelegate delegater2 => 1+2;

下面再来介绍一下 Aggregate这个Linq高阶函数。 Aggregate是对一个集合序列进行累加操作,通过指定初始值,累加函数,以及结果处理函数完成计算。

函数定义:

  1. public static TResult Aggregate<TSource,TAccumulate,TResult>

  2. (this IEnumerable<TSource> source,

  3. TAccumulate seed,

  4. Func<TAccumulate,TSource,TAccumulate> func,

  5. Func<TAccumulate,TResult> resultSelector);

根据函数定义我们来写个简单的demo:

  1. var nums = Enumerable.Range(2, 3);//[2,3,4]

  2. // 计算1到5的累加之和,再将结果乘以2

  3. var sum = nums.Aggregate(1, (total, next) => total + next, result => result * 2);// 相当于 (((1+2)+3)+4)*2=20

  4. Console.WriteLine(sum);//20

和函数参数进行一一对应:

  1. seed : 1

  2. Func func : (total, next) => total + next

  3. Func resultSelector : result => result * 2

基于上面的认识,我们再来回过头梳理一下 RequestHandlerWrapperImpl。 其主要是借助委托: publicdelegateTask<TResponse>RequestHandlerDelegate<TResponse>();来构造委托函数链来构建处理管道。

Aggregate函数了解后,我们就不难理解处理管道的构建了。请看下图中的代码解读:

640?wx_fmt=png

640?wx_fmt=png

那如何保证先执行 IRequestPreProcessor再执行 IRequestPostProcessor呢? 就是在注册到Ioc容器时必须保证顺序,先注册 IRequestPreProcessor再注册 IRequestPostProcessor。(这一点很重要!!!)

看到这里有没有想到ASP.NET Core中请求管道中中间件的构建呢?是不是很像俄罗斯套娃?先由内而外构建管道,再由外而内执行!

至此,MediatR的实现思路算是理清了。

应用场景

如文章开头提到:MediatR是一种进程内消息传递机制。 支持以同步或异步的形式进行请求/响应,命令,查询,通知和事件的消息传递,并通过C#泛型支持消息的智能调度。

那么我们就应该明白,其核心是消息的解耦。因为我们几乎都是在与消息打交道,那因此它的应用场景就很广泛,比如我们可以基于MediatR实现CQRS、EventBus等。

另外,还有一种应用场景:我们知道借助依赖注入的好处是,就是解除依赖,但我们又不得不思考一个问题,随着业务逻辑复杂度的增加,构造函数可能要注入更多的服务,当注入的依赖太多时,其会导致构造函数膨胀。比如:

  1. public DashboardController(

  2.    ICustomerRepository customerRepository,

  3.    IOrderService orderService,

  4.    ICustomerHistoryRepository historyRepository,

  5.    IOrderRepository orderRepository,

  6.    IProductRespoitory productRespoitory,

  7.    IRelatedProductsRepository relatedProductsRepository,

  8.    ISupportService supportService,

  9.    ILog logger

  10.    )  

如果借助 MediatR进行改造,也许仅需注入 IMediatR就可以了。

  1. public DashboardController(IMediatR mediatr)  

总结

看到这里,也许你应该明白MediatR实质上并不是严格意义上的中介者模式实现,我更倾向于其是基于Ioc容器的一层抽象,根据请求定位相应的请求处理器进行消息处理,也就是服务定位。 那到这里似乎也恍然大悟MediatR这个笔误可能是有意为之了。序员,你怎么看?

参考资料:

CQRS/MediatR implementation patterns

MediatR when and why I should use it? 

ABP CQRS 实现案例:基于 MediatR 实现

相关文章:

  • [译]ASP.NET&nbsp;Core中使用MediatR实现命令和中介者模式

  • 【翻译】asp.net&nbsp;core中使用MediatR

  • MEDIATR&nbsp;一个低调的中介者类库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/317725.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络分析(带权并查集)

网络分析 题意&#xff1a; 有n个节点&#xff0c;一开始彼此独立&#xff0c;有两个操作&#xff0c;第一个操作时是连接两个节点&#xff0c;第二个操作是对一个节点x&#xff0c;&#xff08;在进行第二个操作时&#xff0c;与该点相连的点也会x&#xff09; 问每个节点的…

良心发现,时隔一年再回首莫比乌斯反演(公式性质证明+题目练习)

文章目录莫比乌斯反演引入公式性质模板公式证明莫比乌斯函数前缀和题目练习完全平方数[HAOI2011]ProblembYY的GCD[SDOI2014]数表[国家集训队]Crash的数字表格/JZPTAB[SDOI2015]约数个数和寒假疫情期间跟着lmm学了一遍&#xff0c;完全是懵逼到底状态&#xff0c;以至于后面考到…

P7293-[USACO21JAN]Sum of Distances P【统计,bfs】

正题 题目链接:https://www.luogu.com.cn/problem/P7293 题目大意 有kkk张联通无向图&#xff0c;有kkk个人从每张图的点111出发&#xff0c;定义所有人的位置合为一个状态&#xff0c;求初始状态到达所有能到达状态的最短时间的和。 输出答案对 109710^971097 取模。 ∑n≤…

模板:次短路

所谓次短路&#xff0c;就是又次又短的路 &#xff08;逃&#xff09; 解析 使用两遍swap是更新sec的好方法 一定要判断是严格次短才更新sec的答案&#xff01; 代码 #include<bits/stdc.h> using namespace std; #define ll long long #define il inline const int …

平面切分

平面切分 问题描述 题解&#xff1a; 我对这种题极其非常不擅长。。。 另外吐槽为什么acwing的数据卡的这么死&#xff0c;蓝桥杯官网数据那么水 其实题目很简单&#xff0c;如果只有一个直线&#xff0c;那么就是两部分&#xff0c;如果是两个直线&#xff0c;这两个直线不相…

新起点!新征程!微软技术俱乐部(苏州)成立大会暨微软技术交流会

2019年1月19日&#xff0c;苏州微软将举办苏州史上最盛大的开发者聚会&#xff0c;微软技术俱乐部成立大会暨微软技术交流会。超越苹果&#xff0c;登顶世界市值第一&#xff01;云与AI两大技术支柱支撑起的微软帝国&#xff0c;正向万亿美元俱乐部挺进&#xff01;微软的改变我…

P7990-[USACO21DEC]Closest Cow Wins S【堆,贪心】

正题 题目链接:https://www.luogu.com.cn/problem/P7990 题目大意 数轴上有kkk个点是草地&#xff0c;每个草地有不同收益&#xff0c;mmm个点是地方的点&#xff0c;现在你要放置nnn个我方的点&#xff0c;不能和敌方的点重合。 如果一个草地离最近的我方的点距离严格小于最…

模板:整除分块

所谓整除分块&#xff0c;就是按照整除的结果分块 &#xff08;逃&#xff09; 解析 一个关键的结论&#xff1a; 对于 i≤ni\le ni≤n&#xff0c;满足 ⌊nx⌋⌊ni⌋\lfloor\dfrac{n}{x}\rfloor\lfloor\dfrac{n}{i}\rfloor⌊xn​⌋⌊in​⌋ 的最大的 xxx 为 ⌊n⌊ni⌋⌋\lfl…

[Cnoi2019]须臾幻境(LCT维护最大生成树+主席树/分块)

文章目录titlesolutioncodetitle solution 一棵nnn个点的树有n−1n-1n−1条边。 一般的&#xff0c;对于森林而言则有点数-边数树的个数 那么我们将无向图随便生成一个生成树&#xff0c;形成森林。 询问的连通块的个数就变为求森林的树的个数&#xff0c;即森林的点数 n …

跨平台、跨语言应用开发工具,Elements 介绍

目录1&#xff0c;Elements 介绍2&#xff0c;Elements 版本3&#xff0c;Elements 能干嘛4&#xff0c;Elements IDES5&#xff0c;Elements 工具1&#xff0c;Elements 介绍RemObjects Elements&#xff0c;是多平台移动项目开发工具&#xff0c;是一款可以帮助开发人员在 不…

蓝桥杯--字串排序

题目&#xff1a; 小蓝的幸运数字是 &#xff0c;他想找到一个只包含小写英文字母的字符串&#xff0c;对这个串中的字符进行冒泡排序&#xff0c;正好需要 V次交换。请帮助小蓝找一个这样的字符串。如果可能找到多个&#xff0c;请告诉小蓝最短的那个。如果最短的仍然有多个&…

P4688-[Ynoi2016]掉进兔子洞【莫队,bitset】

正题 题目链接:https://www.luogu.com.cn/problem/P4688 题目大意 给出一个长度为nnn的序列aaa。 然后mmm次询问给出三个区间&#xff0c;求这三个区间构成的可重集删去交集后剩下的数字个数和。 1≤n,m≤105,1≤ai≤1091\leq n,m\leq 10^5,1\leq a_i\leq 10^91≤n,m≤105,1…

洛谷P3791:普通数学题(整除分块、前缀和)

解析 似乎位运算和易或并没有太多性质上的联系… 所以换个角度分析 考虑按照二进制进行类似数位dp 暴力枚举 i 和 j 的前k,p位与n、m相同&#xff0c;下一位比n、m小。 然后后面的东西就可以随便填 每个异或的结果都有2^(两个数都可以随便填的位数)的方案 然后乘上一个约数个…

后缀自动机(SAM)构造实现过程演示+习题集锦

文章目录后缀自动机算法实现过程模板习题洛谷后缀自动机模板题品酒大会[HEOI2015]最短不公共子串字符串蒟蒻写这篇blogblogblog主要是存一下&#xff0c;后缀自动机的详细搭建过程&#xff0c;方便以后复习 具体的某些证明&#xff0c;为什么这么做&#xff0c;正确性劈里啪啦一…

使用logdashboard进行可视化的日志追踪

本文源码在Github可以找到下载LogDashboard如果你还不了解LogDashboard请看这里。 LogDashboard 1.1版本支持请求追踪,虽然目前版本还没有发布。不过这个功能可以先睹为快效果图下载项目首先我们可以在 https://github.com/liangshiw/LogDashboard/tree/master/samples/Request…

ARC132D-Between Two Binary Strings【贪心】

正题 题目链接:https://atcoder.jp/contests/arc132/tasks/arc132_d 题目大意 给出两个恰好有nnn个111和mmm个000的字符串s,ts,ts,t&#xff0c;定义两个字符串距离为通过交换两个相邻的字符把一个变成另一个的最小步数。 对于字符串kkk如果dis(s,k)dis(k,t)dis(s,t)dis(s,k…

CodeCraft-21 and Codeforces Round #711 (Div. 2)

CodeCraft-21 and Codeforces Round #711 (Div. 2) 题号题目考点AGCD Sum签到&#xff0c;模拟BBox Fitting贪心CPlanar Reflections规律暴力&#xff08;直接乱搞就对了&#xff09;DBananas in a Microwave思维题&#xff0c;贪心ETwo Houses交互题&#xff08;未补&#xf…

模板:插头DP

所谓插头dp&#xff0c;就是使用插头的dp &#xff08;逃&#xff09; 解析 好难呀qwq 终于算是学明白了吧 推荐博客 字太多了不想打了 自己过去看吧 感觉写的非常清晰了 代码 #include<bits/stdc.h> using namespace std; #define ll long long #define il inline …

【C#与Redis】--C# 操作 Redis

一、简介 C# 中通过 StackExchange.Redis 库可以方便地操作 Redis&#xff0c;实现高性能的数据缓存和存储。StackExchange.Redis 提供了强大的 API&#xff0c;允许开发者轻松连接、读取和写入 Redis 数据。通过使用 ConnectionMultiplexer 类&#xff0c;可以建立与 Redis 服…

ML.NET 0.9特性简介

ML.NET 0.9已于上周发布&#xff0c;距离上次0.8版本的发布只有一个多月&#xff0c;此次增加的新特性主要包括特征贡献计算&#xff0c;模型可解释性增强&#xff0c;ONNX转换对GPU的支持&#xff0c;Visual Studio ML.NET项目模板预览&#xff0c;以及API改进。特征贡献计算特…