MediatR 知多少 - 简书

MediatR 知多少 - 简书
原文:MediatR 知多少 - 简书

webp

引言

首先不用查字典了,词典查无此词。猜测是作者笔误将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实现,其目的是消息发送和消息处理的解耦。它支持以单播和多播形式使用同步或异步的模式来发布消息,创建和侦听事件。

中介者模式

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


webp
中介者模式类图

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

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


webp

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

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

优点:中介者模式的优点就是减少类间的依赖,把原有的一对多的依赖变成了一对一的依赖,同事类只依赖中介者,减少了依赖,当然同时也降低了类间的耦合
缺点:中介者模式的缺点就是中介者会膨胀得很大,而且逻辑复杂,原本N个对象直接的相互依赖关系转换为中介者和同事类的依赖关系,同事类越多,中介者的逻辑就越复杂。

Hello MeidatR

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

单播消息传输

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

//构建 消息请求
public class Ping : IRequest<string> { }
//构建 消息处理
public class PingHandler : IRequestHandler<Ping, string> {public Task<string> Handle(Ping request, CancellationToken cancellationToken) {return Task.FromResult("Pong");}
}
//发送 请求
var response = await mediator.Send(new Ping());
Debug.WriteLine(response); // "Pong"

多播消息传输

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

//构建 通知消息
public class Ping : INotification { }
//构建 消息处理器1
public class Pong1 : INotificationHandler<Ping> {public Task Handle(Ping notification, CancellationToken cancellationToken) {Debug.WriteLine("Pong 1");return Task.CompletedTask;}
}
//构建 消息处理器2
public class Pong2 : INotificationHandler<Ping> {public Task Handle(Ping notification, CancellationToken cancellationToken) {Debug.WriteLine("Pong 2");return Task.CompletedTask;}
}//发布消息
await mediator.Publish(new Ping());

源码解析

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

类图

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

  1. IRequest Vs IRequestHandler
  2. INotification Vs INoticifaitonHandler
  3. IMediator Vs Mediator
  4. Unit
  5. IPipelineBehavior

IRequest Vs IRequestHandler

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

  • IRequest<T> - 有返回值
  • IRequest - 无返回值

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

/// <summary>
/// 代表无需返回值的请求
/// </summary>
public interface IRequest : IRequest<Unit> { }/// <summary>
/// 代表有返回值的请求
/// </summary>
/// <typeparam name="TResponse">Response type</typeparam>
public interface IRequest<out TResponse> : IBaseRequest { }/// <summary>
/// Allows for generic type constraints of objects implementing IRequest or IRequest{TResponse}
/// </summary>
public interface IBaseRequest { }

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

public interface IRequestHandler<in TRequest, TResponse>where TRequest : IRequest<TResponse>
{Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
}public interface IRequestHandler<in TRequest> : IRequestHandler<TRequest, Unit>where TRequest : IRequest<Unit>
{
}

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

public abstract class AsyncRequestHandler<TRequest> : IRequestHandler<TRequest>where TRequest : IRequest
{async Task<Unit> IRequestHandler<TRequest, Unit>.Handle(TRequest request, CancellationToken cancellationToken){await Handle(request, cancellationToken).ConfigureAwait(false);return Unit.Value;}protected abstract Task Handle(TRequest request, CancellationToken cancellationToken);
}

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

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

public struct Unit : IEquatable<Unit>, IComparable<Unit>, IComparable
{public static readonly Unit Value = new Unit();public static readonly Task<Unit> Task = System.Threading.Tasks.Task.FromResult(Value);// some other code
}

IMediator Vs Mediator

MediatR 类图

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

//Mediator.cs
//保存request和requesthandler的映射关系,1对1。
private static readonly ConcurrentDictionary<Type, object> _requestHandlers = new ConcurrentDictionary<Type, object>();
//保存notification与notificationhandler的映射关系,
private static readonly ConcurrentDictionary<Type, NotificationHandlerWrapper> _notificationHandlers = new ConcurrentDictionary<Type, NotificationHandlerWrapper>();

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

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

ASP.NET Core注册IMediatr

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

IPipelineBehavior

处理管道

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

  • IRequestPreProcessor<in TRequest> 请求处理前接口
  • IRequestPostProcessor<in TRequest, in TResponse> 请求处理后接口

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

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

internal class RequestHandlerWrapperImpl<TRequest, TResponse> : RequestHandlerWrapper<TResponse>where TRequest : IRequest<TResponse>
{public override Task<TResponse> Handle(IRequest<TResponse> request, CancellationToken cancellationToken,ServiceFactory serviceFactory){Task<TResponse> Handler() => GetHandler<IRequestHandler<TRequest, TResponse>>(serviceFactory).Handle((TRequest) request, cancellationToken);return serviceFactory.GetInstances<IPipelineBehavior<TRequest, TResponse>>().Reverse().Aggregate((RequestHandlerDelegate<TResponse>) Handler, (next, pipeline) => () => pipeline.Handle((TRequest)request, cancellationToken, next))();}
}

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

  1. C# 7.0的新特性 - 局部函数
  2. C# 6.0的新特性 - 表达式形式的成员函数
  3. Linq高阶函数 - Aggregate
  4. 匿名委托
  5. 构造委托函数链

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

public delegate int SumDelegate();//定义委托
public static void Main()
{//局部函数(在函数内部定义函数)//表达式形式的成员函数, 相当于 int Sum() { return 1 + 2;}int Sum() => 1 + 2;var sumDelegate = (SumDelegate)Sum;//转换为委托Console.WriteLine(sumDelegate());//委托调用,输出:3
}

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

public delegate int SumDelegate();SumDelegate delegater1 = delegate(){ return 1+2; }
//也相当于
SumDelegate delegater2 => 1+2;

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

函数定义:

public static TResult Aggregate<TSource,TAccumulate,TResult>
(this IEnumerable<TSource> source, 
TAccumulate seed, 
Func<TAccumulate,TSource,TAccumulate> func, 
Func<TAccumulate,TResult> resultSelector);

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

var nums = Enumerable.Range(2, 3);//[2,3,4]
// 计算1到5的累加之和,再将结果乘以2
var sum = nums.Aggregate(1, (total, next) => total + next, result => result * 2);// 相当于 (((1+2)+3)+4)*2=20
Console.WriteLine(sum);//20

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

  1. seed : 1
  2. Func<TAccumulate,TSource,TAccumulate> func : (total, next) => total + next
  3. Func<TAccumulate,TResult> resultSelector : result => result * 2

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

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

请求处理管道代码解读
构建流程解析

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

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

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

应用场景

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

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

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

public DashboardController(ICustomerRepository customerRepository,IOrderService orderService,ICustomerHistoryRepository historyRepository,IOrderRepository orderRepository,IProductRespoitory productRespoitory,IRelatedProductsRepository relatedProductsRepository,ISupportService supportService,ILog logger)  

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

public DashboardController(IMediatR mediatr)  

总结

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

参考资料:
CQRS/MediatR implementation patterns
MediatR when and why I should use it? vs 2017 webapi
ABP CQRS 实现案例:基于 MediatR 实现

posted on 2019-06-11 09:49 NET未来之路 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/lonelyxmas/p/11001982.html

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

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

相关文章

oracle数据库扩展目录,Oracle 扩充磁盘空间

Oracle 扩充磁盘空间环境&#xff1a;OS:Oracle Linux Server release 6.3DB:Oracle 11.2.0.4.0问题&#xff1a;根目录磁盘空间不足&#xff0c;还剩6.5G解决办法&#xff1a;扩容磁盘空间&#xff1b;由于安装操作系统时没有使用LVM&#xff0c;不能直接扩容根目录&#xff0…

使用ANT打包Android应用

大家好&#xff0c;今天来分享一下如何使用ANT打包Android应用。 通常我们习惯用eclipse来开发Android程序&#xff0c;它会自动帮我们打包当前的应用程序。如果在Navigator视图下&#xff0c;我们可以看到以下几个文件&#xff1a; 在上图中&#xff0c;com包放置的是我们的cl…

迷你飞信一出,LibFetion该挂了把

飞信确实是个好东西&#xff01; 发短信不要钱 支持群发&#xff0c;这样发个通知什么的&#xff0c;可以轻松搞定 但是以前的飞信客户端太庞大了 好几十M呢 而且光大还多了很多没有用的功能 比如&#xff1a;什么飞信空间啊&#xff0c;网上营业厅&#xff0c;彩信&#xff0c…

Java设计模式(1)工厂模式(Factory模式)

工厂模式定义&#xff1a;提供创建对象的接口。 为何使用工厂模式 工厂模式是我们最常用的模式了&#xff0c;著名的Jive论坛&#xff0c;就大量使用了工厂模式&#xff0c;工厂模式在Java程序系统可以说是随处可见。为什么工厂模式是如此常用&#xff1f;因为工厂模式就相当于…

cmd使用另一个Oracle的sid,(转发备用)Oracle SID在本机上已经存在,请指定一个不同的SID”的解决办法...

(转发备用)Oracle SID在本机上已经存在,请指定一个不同的SID”的解决办法(2014-04-30 10:57:17)1、 开始&#xff0d;&#xff1e;设置&#xff0d;&#xff1e;控制面板&#xff0d;&#xff1e;管理工具&#xff0d;&#xff1e;服务 停止所有Oracle服务。2、 开始&#xff0…

$Poj1952\ $洛谷$1687\ Buy\ Low,Buy\ Lower$ 线性$DP+$方案计数

Luogu Description 求一个长度为n的序列a的最长下降子序列的长度,以及这个长度的子序列种数,注意相同的几个子序列只能算作一个子序列. n<5000,a[i]不超过long范围 Sol 求最长下降子序列的长度: 1.f[i]表示以a[i]结尾的最长下降子序列长度 2.f[i]表示以i结尾的最长下降子序列…

JavaScript中的各种宽高以及位置总结

在javascript中操作dom节点让其运动的时候&#xff0c;常常会涉及到各种宽高以及位置坐标等概念&#xff0c;如果不能很好地理解这些属性所代表的意义&#xff0c;就不能理解js的运动原理&#xff0c;同时&#xff0c;由于这些属性概念较多&#xff0c;加上浏览器之间 实现方式…

MySQL 8.0索引合并

简介参考https://dev.mysql.com/doc/refman/8.0/en/index-merge-optimization.html#index-merge-intersection。索引合并是通过多个range类型的扫描并且合并它们的结果集来检索行的。仅合并来自单个表的索引扫描&#xff0c;而不是跨多个表的索引扫描。合并会产生底层扫描的三种…

linux开通80端口命令,Linux iptables开启80端口

Linux下安装好apache的时候访问IP 发现无法访问!以为安装失败了&#xff0c;于是测试apache 监听的端口80#netstat -lnt |grep 80tcp 0 0 :::80 :::* LISTEN安装成功了&#xff01;想到用本机telnet IP 80不通- - 但…

服务端配置实现AJAX跨域请求

2019独角兽企业重金招聘Python工程师标准>>> 一直以为AJAX跨域是无法逾越的鸿沟&#xff0c;最近发现原来在服务端可以通过发送header信息来允许AJAX跨域请求。 PHP代码示例&#xff1a; header(Access-Control-Allow-Origin:*); header(Access-Control-Allow-Hea…

【翻译】怎样使用css制作迷人的button

英文原文&#xff1a;How to make sexy buttons with css 作者&#xff1a;Alex 这是一个教程&#xff0c;作者Alex一步步地叫我们怎样使用css来制作一个迷人的按钮&#xff0c;看后觉得挺好&#xff0c;很想大家都看看&#xff0c;翻译的可能稍有不妥&#xff0c;望谅解…… 这…

Linux内核分析(三)----初识linux内存管理子系统

原文:Linux内核分析&#xff08;三&#xff09;----初识linux内存管理子系统Linux内核分析&#xff08;三&#xff09; 昨天我们对内核模块进行了简单的分析&#xff0c;今天为了让我们今后的分析没有太多障碍&#xff0c;我们今天先简单的分析一下linux的内存管理子系统&#…

linux常用高级命令,Linux常用高级文件操作命令

Linux常用高级文件操作命令 cat 查看文本#cat 文件名 打印文件内容到当前屏幕上#cat -n 文件名 显示行号#cat gt;Linux常用高级文件操作命令cat 查看文本#cat 文件名 打印文件内容到当前屏幕上#cat -n 文件名 显示行号#cat > a.txt 可以用这种方法修改或…

java高并发实战Netty+协程(Fiber)|系列1|事件驱动模式和零拷贝

今天开始写一些高并发实战系列。 本系列主要讲两大主流框架&#xff1a; Netty和Quasar(java纤程库) 先介绍netty吧&#xff0c;netty是业界比较成熟的高性能异步NIO框架。 简单来说&#xff0c;它就是对NIO2的封装&#xff0c;但提供了更好用&#xff0c;bug更少的API。 为什么…

桌面级linux推荐,七大顶级桌面比较!Linux平台自由选择

七大顶级Linux桌面&#xff1a;Unity对于开源Linux平台来说&#xff0c;如何选择就是首要解决的问题。通常Linux发行版都有默认的桌面成为你的首选&#xff0c;但目前可供选择的桌面环境种类繁多。特别是Ubuntu系统一个平台就提供9种正式备选版本&#xff0c;且各自拥有不同的桌…

最大子列和问题

问题陈述&#xff1a; 给定N个整数的序列{A1, A2, ... , AN}&#xff0c;求函数ƒ(i, j) max{0, Ai Ai1 ... Aj}(1<i<j<N)的最大值。 问题分析&#xff1a; 求给定数列的最大子列和。 算法设计&#xff1a; 算法1&#xff1a;计算每个子列的和 时间复杂度: T(N) …

蓝牙模块音频BLE数据数传串口AT指令的使用方法

一、简介 蓝牙模块&#xff0c;使用其实是非常简单的&#xff0c;因为它把比较麻烦的蓝牙射频以及外围的电路&#xff0c;都集成在一起&#xff0c;对于用户来说&#xff0c;就是一个黑盒子&#xff0c;只用关注应用&#xff0c;大可不必关心他实现的细节&#xff0c;所以深受广…

SharePoint Online 创建门户网站系列之首页布局

前 言 SharePoint Online中创建首页布局&#xff0c;一般都是首先将美工提供的效果图&#xff0c;切图成为Html Css Script的形式&#xff0c;然后&#xff0c;将所有资源文件传到SharePoint Online的资源库&#xff0c;在Designer中创建页面&#xff0c;添加Html页面&#x…

Android 3.0细节曝光:Google程序更耀眼

来源&#xff1a;腾讯科技 发布者&#xff1a;腾讯科技 Phandroid 网站今天披露了Android 3.0&#xff08;Gingerbread&#xff09;的一些细节。新系统仍在开发之中&#xff0c;不过有些 Google 员工已经在自己的 Nexus One 上安装了 Android 3.0&#xff0c;下面是一张模糊到…

解决无法将java项目部署到tomcat中去

project facets java转成web项目 用Eclipse开发项目的时候&#xff0c;把一个Web项目导入到Eclipse里会变成了一个Java工程&#xff0c;将无法在Tomcat中进行部署运行。 方法&#xff1a; 1.找到.project文件&#xff0c;找到里面的<natures>标签&#xff0c;查看是否有下…