前言

简单整理一下Mediator。

正文

Mediator 名字是中介者的意思。

那么它和中介者模式有什么关系呢?前面整理设计模式的时候,并没有去介绍具体的中介者模式的代码实现。

如下:

https://www.cnblogs.com/aoximin/p/13600464.html

之所以没写代码就是因为它现在是一种思想,以前的简单的已经很难满足我们现在开发的需求了。

那么看下Mediator 是如何做的吧。

Mediator 这个服务,是如何让我们的命令查询职责分离的。

首先安装一下包:

Mediator 核心接口为:

  1. IMediator

  2. IRequest,IRequest

  3. IRequestHandler<in IRequest,TResponse>

一般从接口就能看到其设计思想,其余的实现,各有各的想法,那么就来看下IMediator吧。

  1. /// <summary>
  2. /// Defines a mediator to encapsulate request/response and publishing interaction patterns
  3. /// </summary>
  4. public interface IMediator : ISender, IPublisher
  5. {
  6. }

这个接口的作用是定义了一个中介者,这个中介者用来装入请求或者响应和实现一些交互模式。

那么看来分别就是ISender和IPublisher来实现了。

看下ISender:

  1. /// <summary>
  2. /// Send a request through the mediator pipeline to be handled by a single handler.
  3. /// </summary>
  4. public interface ISender
  5. {
  6. /// <summary>
  7. /// Asynchronously send a request to a single handler
  8. /// </summary>
  9. /// <typeparam name="TResponse">Response type</typeparam>
  10. /// <param name="request">Request object</param>
  11. /// <param name="cancellationToken">Optional cancellation token</param>
  12. /// <returns>A task that represents the send operation. The task result contains the handler response</returns>
  13. Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default);
  14. /// <summary>
  15. /// Asynchronously send an object request to a single handler via dynamic dispatch
  16. /// </summary>
  17. /// <param name="request">Request object</param>
  18. /// <param name="cancellationToken">Optional cancellation token</param>
  19. /// <returns>A task that represents the send operation. The task result contains the type erased handler response</returns>
  20. Task<object?> Send(object request, CancellationToken cancellationToken = default);
  21. }

这个接口用来通过由单个处理程序的中介者管道中发送请求。

IPublisher:

  1. /// <summary>
  2. /// Publish a notification or event through the mediator pipeline to be handled by multiple handlers.
  3. /// </summary>
  4. public interface IPublisher
  5. {
  6. /// <summary>
  7. /// Asynchronously send a notification to multiple handlers
  8. /// </summary>
  9. /// <param name="notification">Notification object</param>
  10. /// <param name="cancellationToken">Optional cancellation token</param>
  11. /// <returns>A task that represents the publish operation.</returns>
  12. Task Publish(object notification, CancellationToken cancellationToken = default);
  13. /// <summary>
  14. /// Asynchronously send a notification to multiple handlers
  15. /// </summary>
  16. /// <param name="notification">Notification object</param>
  17. /// <param name="cancellationToken">Optional cancellation token</param>
  18. /// <returns>A task that represents the publish operation.</returns>
  19. Task Publish<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
  20. where TNotification : INotification;
  21. }

这个接口用来通过多个处理程序的中介者管道中发送通知或者事件。

好了,现在我们得到的信息有"发布"、"事件"、"请求"、"管道" 这几个名词了。

那么实际上我们也能大致的猜出它是怎么实现的。

接下来查看IRequest:

  1. /// <summary>
  2. /// Marker interface to represent a request with a void response
  3. /// </summary>
  4. public interface IRequest : IRequest<Unit> { }
  5. /// <summary>
  6. /// Marker interface to represent a request with a response
  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接口:

  1. /// <summary>
  2. /// Defines a handler for a request
  3. /// </summary>
  4. /// <typeparam name="TRequest">The type of request being handled</typeparam>
  5. /// <typeparam name="TResponse">The type of response from the handler</typeparam>
  6. public interface IRequestHandler<in TRequest, TResponse>
  7. where TRequest : IRequest<TResponse>
  8. {
  9. /// <summary>
  10. /// Handles a request
  11. /// </summary>
  12. /// <param name="request">The request</param>
  13. /// <param name="cancellationToken">Cancellation token</param>
  14. /// <returns>Response from the request</returns>
  15. Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
  16. }

现在我们在来整理一下名词:"发布"、"事件"、"请求"、"管道" 、"处理请求"

那么大概猜测大概通过接口来处理请求,然后还可以发布事件处理的。处理请求有处理请求的管道,且这个处理是单个处理程序,而处理事件是多个处理程序。

接下来操作一遍:

  1. async static Task Main(string[] args)
  2. {
  3. var services = new ServiceCollection();
  4. services.AddMediatR(typeof(Program).Assembly);
  5. var serviceProvider = services.BuildServiceProvider();
  6. var mediator = serviceProvider.GetService<IMediator>();
  7. await mediator.Send(new SelfCommand{ CommandName ="zhangsan"});
  8. }
  9. internal class SelfCommand : IRequest<long>
  10. {
  11. public string CommandName { get; set; }
  12. }
  13. internal class SelfCommandHandler : IRequestHandler<SelfCommand, long>
  14. {
  15. public Task<long> Handle(SelfCommand request, CancellationToken cancellationToken)
  16. {
  17. Console.WriteLine($"处理 {nameof(SelfCommand)}请求:{request.CommandName}");
  18. return Task.FromResult(10L);
  19. }
  20. }

这里就有一个疑问了,为啥SelfCommandHandler会自动称为处理程序?我们并没有设置啊。

那么就来看一下send在干什么吧:

  1. public Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default)
  2. {
  3. if (request == null)
  4. {
  5. throw new ArgumentNullException(nameof(request));
  6. }
  7. var requestType = request.GetType();
  8. var handler = (RequestHandlerWrapper<TResponse>)_requestHandlers.GetOrAdd(requestType,
  9. t => (RequestHandlerBase)Activator.CreateInstance(typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(requestType, typeof(TResponse))));
  10. return handler.Handle(request, cancellationToken, _serviceFactory);
  11. }

上面可以看到生成了一个handle,然后调用了Handle 方法。

然后进RequestHandlerWrapperImpl 查看:

  1. internal class RequestHandlerWrapperImpl<TRequest, TResponse> : RequestHandlerWrapper<TResponse>
  2. where TRequest : IRequest<TResponse>
  3. {
  4. public override Task<object?> Handle(object request, CancellationToken cancellationToken,
  5. ServiceFactory serviceFactory)
  6. {
  7. return Handle((IRequest<TResponse>)request, cancellationToken, serviceFactory)
  8. .ContinueWith(t =>
  9. {
  10. if (t.IsFaulted)
  11. {
  12. ExceptionDispatchInfo.Capture(t.Exception.InnerException).Throw();
  13. }
  14. return (object?)t.Result;
  15. }, cancellationToken);
  16. }
  17. public override Task<TResponse> Handle(IRequest<TResponse> request, CancellationToken cancellationToken,
  18. ServiceFactory serviceFactory)
  19. {
  20. Task<TResponse> Handler() => GetHandler<IRequestHandler<TRequest, TResponse>>(serviceFactory).Handle((TRequest) request, cancellationToken);
  21. return serviceFactory
  22. .GetInstances<IPipelineBehavior<TRequest, TResponse>>()
  23. .Reverse()
  24. .Aggregate((RequestHandlerDelegate<TResponse>) Handler, (next, pipeline) => () => pipeline.Handle((TRequest)request, cancellationToken, next))();
  25. }
  26. }

这里埋一个坑,因为看了一下,有很多细节的地方比如说Activator.CreateInstance的机制、一些管道细节,还设计到注册IPipelineBehavior<TRequest, TResponse>的实现类的机制,整理到该系列的细节篇中,将会比较详细的介绍。

现在只需要看Handle的ContinueWith,如果失败的话,那么会返回一个异常,否则返回结果。

然后根据上面的,我们指定Request对应的RequstHandle 必须名字有如下规律:如SelfCommand,只需要SelfCommand加长一些即可,比如说SelfCommandHandler,比如说SelfCommandHandlerV2,还可以SelfCommand2都行。

但是呢,最好改好名字,后面加Handle。

同时,如果我们写两个SelfCommandHandler和SelfCommandHandlerV2,那么是否两个都会执行?不是的,只会执行一个。

  1. internal class SelfCommandHandler2 : IRequestHandler<SelfCommand, long>
  2. {
  3. public Task<long> Handle(SelfCommand request, CancellationToken cancellationToken)
  4. {
  5. Console.WriteLine($"处理 {nameof(SelfCommand)} V2请求:{request.CommandName}");
  6. return Task.FromResult(10L);
  7. }
  8. }
  9. internal class SelfCommandHandler : IRequestHandler<SelfCommand, long>
  10. {
  11. public Task<long> Handle(SelfCommand request, CancellationToken cancellationToken)
  12. {
  13. Console.WriteLine($"处理 {nameof(SelfCommand)}请求:{request.CommandName}");
  14. return Task.FromResult(10L);
  15. }
  16. }

比如说这样,那么会执行。

也就是说会执行SelfCommandHandler2。

那么请求就算介绍完了,那么看下事件。

调用事件这样调用即可:

  1. await mediator.Publish(new SelfEvent { EventName = "SelfEvent" });

具体类:

  1. internal class SelfEvent : INotification
  2. {
  3. public string EventName { get; set; }
  4. }
  5. internal class SelfEventHandler : INotificationHandler<SelfEvent>
  6. {
  7. public Task Handle(SelfEvent notification, CancellationToken cancellationToken)
  8. {
  9. Console.WriteLine($"SelfEventHandler 执行:{notification.EventName}");
  10. return Task.CompletedTask;
  11. }
  12. }
  13. internal class SelfEventHandlerV2 : INotificationHandler<SelfEvent>
  14. {
  15. public Task Handle(SelfEvent notification, CancellationToken cancellationToken)
  16. {
  17. Console.WriteLine($"SelfEventHandlerV2 执行:{notification.EventName}");
  18. return Task.CompletedTask;
  19. }
  20. }

效果:

那么和requst不同的是,注册几个就会调用几个。

好了现在回到中介者模式中来。

中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。

那么Mediator 这个模块呢,帮助我们解决了request和requesthandle之间的耦合,和 Event与EventHandle 之间的耦合。

一开始我认为是命令模式,后来一想,命令模式解决“行为请求者”与“行为实现者”的耦合。

命令模式如下:

https://www.cnblogs.com/aoximin/p/13616558.html

上面只是命令模式的一种形式哈。

下一节领域事件的处理。以上只是个人整理,如有错误,望请指点。

重新整理 .net core 实践篇—————Mediator实践[二十八]的更多相关文章

  1. 重新整理 .net core 实践篇—————领域事件[二十九]

    前文 前面整理了仓储层,工作单元模式,同时简单介绍了一下mediator. 那么就mediator在看下领域事件启到了什么作用吧. 正文 这里先注册一下MediatR服务: // 注册中间者:Medi ...

  2. 重新整理 .net core 实践篇—————异常中间件[二十]

    前言 简单介绍一下异常中间件的使用. 正文 if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } 这样写入中间件哈,那么在env环 ...

  3. 重新整理 .net core 实践篇————cookie 安全问题[三十八]

    前言 简单整理一下cookie的跨站攻击,这个其实现在不常见,因为很多公司都明确声明不再用cookie存储重要信息,不过对于老站点还是有的. 正文 攻击原理: 这种攻击要达到3个条件: 用户访问了我们 ...

  4. 重新整理 .net core 实践篇——— UseEndpoints中间件[四十八]

    前言 前文已经提及到了endponint 是怎么匹配到的,也就是说在UseRouting 之后的中间件都能获取到endpoint了,如果能够匹配到的话,那么UseEndpoints又做了什么呢?它是如 ...

  5. 重新整理 .net core 实践篇—————静态中间件[二十一]

    前言 简单整理一下静态中间件. 正文 我们使用静态文件调用: app.UseStaticFiles(); 那么这个默认会将我们根目录下的wwwroot作为静态目录. 这个就比较值得注意的,可能刚开始学 ...

  6. 重新整理 .net core 实践篇————缓存相关[四十二]

    前言 简单整理一下缓存. 正文 缓存是什么? 缓存是计算结果的"临时"存储和重复使用 缓存本质是用空间换取时间 缓存的场景: 计算结果,如:反射对象缓存 请求结果,如:DNS 缓存 ...

  7. 重新整理 .net core 实践篇—————配置系统之间谍[八](文件监控)

    前言 前文提及到了当我们的配置文件修改了,那么从 configurationRoot 在此读取会读取到新的数据,本文进行扩展,并从源码方面简单介绍一下,下面内容和前面几节息息相关. 正文 先看一下,如 ...

  8. 重新整理 .net core 实践篇————重定向攻击[三十九]

    前言 简单介绍一下重定向攻击. 正文 攻击思路: 看着上面挺复杂的,其实是一些很简单的步骤. 攻击者通过某些手段,让用户打开了一个好站点,打开的这个地址里面带有重定向信息,重定向信息就是自己伪造的站点 ...

  9. 重新整理 .net core 实践篇————配置应用[一]

    前言 本来想整理到<<重新整理.net core 计1400篇>>里面去,但是后来一想,整理 .net core 实践篇 是偏于实践,故而分开. 因为是重新整理,那么就从配置开 ...

随机推荐

  1. Windows进程间通讯(IPC)----WM_COPYDATA

    WM_COPYDATA通讯思路 通过向其他进程的窗口过程发送WM_COPYDATA消息可以实现进程间通讯. 只能通过SendMessage发送WM_COPYDATA消息,而不能通过PostMessag ...

  2. 看雪加密解密第一个traceme程序破解

    工具:ollydbg(吾爱破解2.10版) 工具设置:因为traceme是一个win32图形用户程序,所以其程序入口点在WinMain()函数处,设置ollydbg的调试设置的事件选项,选中在WinM ...

  3. linux远程下载文件 的两种方法之 ftp命令和scp命令

    ftp命令: 服务器有安装ftp Server,另外一台linux可以使用ftp的client程序来进行文件的拷贝读取和下载. 1. 连接ftp服务器  格式:ftp [hostname| ip-ad ...

  4. 如何更好理解Peterson算法?

    如何更好理解Peterson算法? 1 Peterson算法提出的背景 在我们讲述Peterson算法之间,我们先了解一下Peterson算法提出前的背景(即:在这个算法提出之前,前人们都做了哪些工作 ...

  5. 阿里云上安装 OpenStack 是什么体验

    阿里云上跑火车(安装 OpenStack Train 版本),猜猜最终花了多少钱? 前言 前面给大家提供了用虚拟机安装 OpenStack 的镜像,虽然已经很简便了,但还是略显笨重.一来镜像文件比较大 ...

  6. 联想ThinkServer服务器安装CentOS7 Redhat7系统 驱动R110i RAID卡

    1.下载对应版本的驱动(因为联想没有CentOS的驱动用redhat的驱动就可以). 2.进入BIOS里,在高级设置里找到SATA设置,把SATA模式改成RAID(重启后配置raid),sSATA模式 ...

  7. 寻找CPU使用率高的进程方法

    寻找CPU使用率高的进程方法 发布时间:  2017-07-13 浏览次数:  1362 下载次数:  0 问题描述 节点报CPU使用率高,甚至出现"ALM-12016 CPU使用率超过阈值 ...

  8. Debian 9.4 多网卡链路聚合bond配置

    Debian 9.4 多网卡链路聚合bond配置 安装ifenslave ifenslave 的作用是网卡的负载均衡 root@debian:~# apt-get install ifenslave ...

  9. SystemVerilog 编写FSM

    SystemVerilog 编写FSM 题目 SystemVerilog实现 仿真 SystemVerilog 编写FSM 好书: https://github.com/yllinux/blogPic ...

  10. OpenStack Rally 性能测试

    注意点:在测试nova,在配置文件里面如果不指定网络id,那么默认是外网的网络(该网络是共享的),如果想要指定网络,那么该网络必须是共享的状态,否则将会报错:无法发现网络.如果测试多于50台的虚拟机需 ...