CodeSnippet.Cn
代码片段
Csharp
架构设计
.NetCore
西班牙语
kubernetes
MySql
Redis
Algorithm
Ubuntu
Linux
Other
.NetMvc
VisualStudio
Git
pm
Python
WPF
java
Plug-In
分布式
CSS
微服务架构
JavaScript
DataStructure
Shared
MediatR与中介者模式
0
.NetCore
架构设计
小笨蛋
发布于:2022年05月04日
更新于:2022年05月04日
160
#custom-toc-container
> MediatR是一款基于中介者模式的思想而实现的.NET library,主要是为了解决进程内消息发送与消息处理过程之间的耦合问题。例如,一个Controller需要调用相应的Repository进行业务逻辑处理,普通情况下,可以直接在Controller注入Repository,而在使用MediatR的情况下,可以先注入MediatR,再通过MediatR发送给Repository,减少了耦合。在本文中,为了更好地理解,将先介绍中介者模式及其C#的实现,然后会对MediatR及其应用进行介绍。 ### 1. 中介者模式 中介者模式简单来说,就是用一个中介对象来封装一系列的对象交互,使各个对象不需要显式地互相引用,从而达到了减少耦合的目的,同时也能让它们之间交互独立地变化。 #### 1.1 中介者模式中的角色 总的来说,中介者模式中的角色有以下几种 - `Mediator`(中介者):通过定义一个接口与各同事对象进行通信。 - `ConcreteMediator`(具体中介者):是中介者的具体实现,需要知道所协调的同事对象,用来协调各同事对象之间的行为。 - `Colleague`(同事接口):定义了通过中介者与其他同事进行通信的接口 - `ConcreteColleague`(具体同事类):同事接口的实现类,当一个同事对象需要与其他同事进行通信时,将统一通过中介者来进行通信。上文中提到的`Controller`与`Repository`都属于`Colleague`。 #### 1.2 中介者模式的C#实现 为了实现中介者模式,我们来简单实现一个消息通知系统,系统中有两个角色:`Admin`和`User`,两者可以通过中介者进行互相通信。 程序中主要有三个类:`NotificationMediator(Mediator)`、`IPerson(Colleague)`、`Person(Concrete Colleague)`。 首先是接口`IPerson`,定义了`Send`和`Receive`两个方法,用来发送和接收消息。 ```csharp public interface IPerson { void Send(string message); void Receive(string message, string sender); } ``` 其次是`NotificationMediator`的实现,在这里使用了`EventHandler`,作为消息通知。 ```csharp public delegate void NotificationEventHandler(string message, string sender); public class NotificationMediator { public event NotificationEventHandler NotificationReceived; public void Send(string message, string sender) { if (NotificationReceived != null) { NotificationReceived(message, sender); } } } ``` 最后我们对`Person`类进行实现,在这里设定`Admin`和`User`都是`Person`类的实例对象,但区别在于`Role`这个属性,在构造方法中引入`mediator`,并把`Receive`方法加到`NotificationReceived`里。 ```csharp public class Person : IPerson { private NotificationMediator _mediator; public string Role { get; set; } public Person(NotificationMediator mediator, string role) { _mediator = mediator; Role = role; _mediator.NotificationReceived += new NotificationEventHandler(Receive); } public void Receive(string message, string role) { if (role != Role) { Console.WriteLine($"{Role} received '{message}' from {role}"); } } public void Send(string message) { Console.WriteLine($"{Role} sends: {message}"); _mediator.Send(message, Role); } } ``` 最后在`Program`里新建对象进行测试: ```csharp class Program { static void Main(string[] args) { var mediator = new NotificationMediator(); var admin = new Person(mediator, "Admin"); var user = new Person(mediator, "User"); admin.Send("Watch out!"); user.Send("I have Received"); } } ``` 得到以下结果: ![图片alt](/uploads/images/20220504/140202-bbfafa6e192e484fa745352e70fefc77.png ''代码片段:Www.CodeSnippet.Cn'') 可以看出,两个`Person`对象,通过`NotificationMediator`中介者,实现了消息的发送与接收。因为目前只是一个类的两个对象进行通信,可能不是十分明显,但如果想要将`Person`进一步细化,将各个`Role`作为单独的类,或是想要扩展更多的同事类,那么使用中介者的好处就非常明显了。 ### 2. MediatR 根据上面对中介者模式的描述,`MediatR`在项目中的作用就很明显了:降低各个类之间的耦合。下面介绍在实际.NET Core WebAPI 中实用`MediatR`。 首先新建WebAPI项目,并安装`MediatR`和`MediatR.Extensions.Microsoft.DependencyInjection`两个Nuget Packages。 ![](/uploads/images/20220504/140334-9e2e7748fc8b48faa1ea283e22affb51.jpg) 在Startup类中,需要将对`MediatR`进行配置,使用刚才安装的`DependencyInjection`包中的方法`AddMediatR`,将`MediatR`及当前的程序集注入进去,这样`MediatR`就可以通过扫描当前程序集来获取相应的类。 ```csharp public void ConfigureServices(IServiceCollection services) { services.AddMediatR(typeof(Startup)); services.AddControllers(); } ``` 如果你使用的是`Autofac`或其他`IoC`容器,请参阅MediatR Github Page。 `MediatR`有两种消息传递的方式: - `Request/Response message`,用于一个单独的`Handler`。 - `Notification message`,用于多个`Handler`。 #### 2.1 Request/Response 在`MediatR`中,有两种类型的`request`: - `IRequest
` :返回一个T类型的值。 - `IRequest` :不返回值。 对于每个`request`类型,都有相应的`handler`接口: - `IRequestHandler
` :实现该接口并返回 `Task
`。 - `RequestHandler
` :继承该类并返回 U。 - `IRequestHandler
` :实现该接口并返回 `Task
`。关于Unit这个类,会在下篇中介绍(如果有的话)。 - `AsyncRequestHandler
` :继承该类并返回 `Task`。 - `RequestHandler
` :继承该类不返回。 接下来,我们创建一个`Controller`及方法`CreateOrder`,接收`CreateOrderCommand`为参数。 ```csharp [ApiController] [Route("/api/order")] public class OrderController : ControllerBase { private readonly IMediator _mediator; public OrderController(IMediator mediator) { _mediator = mediator; } [HttpPost] public async Task
CreateOrder([FromBody]CreateOrderCommand command) { return await _mediator.Send(command); } } ``` `CreateOrderCommand`内只有一个`string`类型的属性`OrderName`,同时该类`implement IRequest`接口。 ```csharp public class CreateOrderCommand : IRequest
{ public string OrderName { get; set; } } ``` `CreateOrderCommandHandler`为对`CreateOrder`进行处理的类,实现`IRequestHandler`接口中的`Handle`方法。第一个泛型参数为`CreateOrderCommand`类,而第二个泛型参数则是`Handle`方法中返回的类型。 ```csharp public class CreateOrderCommandHandle : IRequestHandler
{ public Task
Handle(CreateOrderCommand request, CancellationToken cancellationToken) { var s = $"CreateOrderCommandHandler: Create Order ${request.OrderName}"; return Task.FromResult(s); } } ``` 所有的准备完毕,启动项目,使用Postman发送Post请求,可以看出得到以下结果: ![图片alt](/uploads/images/20220504/140542-d1c6ce43c839404f9760878dabacb9d9.png ''代码片段:Www.CodeSnippet.Cn'') 之前有说到,`IRequest`是用来对单个`Handler`进行处理的,那么如果同时有多个实现了同一种`IRequestHandler`的`Handler`会怎么样呢?我们在原方法上面,`duplicate`一个`Handler`: ```csharp public class CreateOrderCommandHandleSecond : IRequestHandler
{ public Task
Handle(CreateOrderCommand request, CancellationToken cancellationToken) { var s = $"CreateOrderCommandHandlerSecond: Create Order ${request.OrderName}"; return Task.FromResult(s); } } public class CreateOrderCommandHandle : IRequestHandler
{ public Task
Handle(CreateOrderCommand request, CancellationToken cancellationToken) { var s = $"CreateOrderCommandHandler: Create Order ${request.OrderName}"; return Task.FromResult(s); } } ``` 这次我们再进行测试,可以看到是由`Second`进行`handle`的,所以为了避免不必要的错误与误解,尽量仅注册需要的`Handler`。 ![图片alt](/uploads/images/20220504/140629-1063300d667f4d53b78be54a384189f3.png ''代码片段:Www.CodeSnippet.Cn'') #### 2.2 Notifications 根据`Request`中的基础,先建一个`implement INotification`的类: `public class QueryOrder : INotification {}` 新建三个实现`INotification
`的`Handler`: ```csharp public class QueryOrderHandler : INotificationHandler
{ public Task Handle(QueryOrder notification, CancellationToken cancellationToken) { Console.WriteLine($"Query 1st time"); return Task.CompletedTask; } } public class QueryOrderHandlerSecond : INotificationHandler
{ public Task Handle(QueryOrder notification, CancellationToken cancellationToken) { Console.WriteLine($"Query 2nd time"); return Task.CompletedTask; } } public class QueryOrderHandlerThird : INotificationHandler
{ public Task Handle(QueryOrder notification, CancellationToken cancellationToken) { Console.WriteLine($"Query 3rd time"); return Task.CompletedTask; } } ``` 在`Controller`中,写一个方法调用`mediator`实现`Query`操作,在`Notification`中,使用的是`Publish`方法而非`Send`: ```csharp [HttpGet] public async Task QueryOrder() { await _mediator.Publish(new QueryOrder()); } ``` 运行项目,发送Get请求,得到以下结果: ![图片alt](/uploads/images/20220504/140912-1298cd26764c442e9d076ef5cdd21743.png ''代码片段:Www.CodeSnippet.Cn'') #### 2.3 Request与Notification 通过以上实现与测试,可以总结出两者的一些不同: - Request 可以由使用者来决定是否需要选择有返回值的类型,而Notification 统一没有返回值。 - Request 最好用于没有其他相同泛型列表的Handler,而Notification可以用任意的Handler,并且在Handler中可以实现不同的逻辑。 - 两者在行为模式上有所区别,Request为Send,可以理解为单播;Notification为Publish,可以理解为广播。 ### 3. 总结 在本文中,首先介绍了中介模式及其C#实现,接着介绍了`MediatR`及如何在Web API项目中使用`Request/Notification` 发送消息。限于篇幅,本文只在Web API中使用了`MediatR`,但`MediatR`的应用场景却十分广泛。如果时间充裕,下一篇将会对MediatR的基础构造与实现方法进行一些梳理和讲解。 ### 参考 1. ^fErichGamma, 加马, 李英军. 设计模式:可复用面向对象软件的基础[M]. 机械工业出版社, 2000. 2. ^Wiki Mediator pattern https://en.wikipedia.org/wiki/Mediator_pattern 3. ^MediatR Github https://github.com/jbogard/MediatR/wiki
这里⇓感觉得写点什么,要不显得有点空,但还没想好写什么...
返回顶部
About
京ICP备13038605号
© 代码片段 2024