Mediator 模式及其 .NET 实现
参考资料:
中间人模式简介
在软件工程层面,中间人模式定义了封装不同对象之间的交互方式。在应用系统中,随着业务体量的逐渐增大,不同对象之间的通信变得越来越复杂,这使得应用程序难以阅读及维护。中间人模式将对象之间的「通信(或交互)」封装至单独的「中间人(Mediator)」对象中,对象之间通过「中间人」进行通信,而不再直接交互。这减少了拟交互对象之间的依赖,进而降低了系统内部各组件之间的耦合。
定义
中间人模式的要点,在于封装拟交互对象群间关系,拟交互类型通过「中间人」将消息发送至其他拟交互类型,并同时从其他拟交互类型接收消息。
案例
在 MVC 项目中,应用层的 Controller
通常以 DI 的方式依赖其他组件,例如,我们经常看到如下的 Controller
:
1 | public DashboardController( |
一开始,这种实现并无不妥,但随着业务规则越来越复杂,它很快便会成长为:
1 | public DashboardController( |
也许有人看到这段代码会觉得 Controller
承担了太多职责,于是开始思考应该将这些职责转移到更适合的下层组件当中去。然而,无论这些依赖存在于哪个组件都显得太臃肿了,因为它显式依赖了这些对象(无论是具体类型还是抽象类型)。
为了解决这个问题,我们引入中间人模式,让所有依赖其他对象进行交互的类型转而引用中间人类型,使得耦合度大大降低:
1 | public DashboardController( |
签名看上去清爽多了,但实际的通信和交互应该如何实现呢?让我们看看如何将一个简单的命令通过 MediatR 应用在真实的项目中。
MediatR 类库
MediatR 是 .NET 生态中实现「中间人」模式的轻量级类库,支持 Request
/Notification
两种通信方式,在编码实现上支持同步/异步编程模型,我们以一个 ASP.NET Core 项目为例来演示如何实现这一过程,关键步骤为:
- 通过 Nuget 添加对
MediatR
的项目引用 - 创建
Request/Notification
对象 - 创建处理
Request/Notification
的Handler
对象
在应用层中注册 IMediator 和 ServiceFactory 委托
按照 MediatR Wiki, IMediator
使用 ServiceFactory
委托创建所需的 Handler
类型实例、Pipiline
行为和 pre/post
处理器。它们的服务类型及实现类型需要在应用层中进行注册,添加 Autofac.Extensions.DependencyInjection
库的引用:
1 | public IServiceProvider ConfigureServices(IServiceCollection services) |
上述代码的关键两行为
builder.RegisterAssemblyTypes(typeof(IMediator).Assembly).AsImplementedInterfaces()
: 将IMediator
接口的默认实现以单例模型注册至服务容器builder.Register<ServiceFactory>(ctx =>
: 定义ServiceFactory
委托的实现,该委托旨在提供在运行时解析类型的实现
创建实现 IRequest/INotification 接口的类型
接下来,创建一个实现了 IRequest
接口的类型,该类型代表某个希望完成的工作:
1 | public class AddEmergencyContactCommand : IRequest |
该类型仅包含一些 payload
属性,理论上可以在 Request
对象中定义任何需要发送至 Mediator
的载荷数据——基元类型或复杂类型都支持。
创建处理对应 Request/Notification 的 Handler 的类型
Handler
类型继承自 RequestHandler<TRequest>
类型,并重写了 Handle
方法。Mediator
将确保 Handle
方法传入期望的 Request
对象。
1 | public class AddEmergencyContactHandler : RequestHandler<AddEmergencyContactCommand> |
Handler
类型可通过 DI 访问其他类型的引用以确保完成命令操作。Command
的发起者(本例中为 Controller
)将不再关心执行该命令要依赖哪些对象,仅需知道要发起何种命令:
1 | [ ] |
Controller
的 Post
方法中,组装了 AddEmergencyContactCommand
对象,并通过调用 Mediator.Send()
方法发送命令。Mediator
对象将确保调用栈的下一步指向对应的 AddEmergencyContactHandler
类型并将 AddEmergencyContactCommand
作为参数传入其 Handle
方法。类型的解析委托正是前文在 Startup.ConfigureServices
方法中定义的 ServiceFactory
。
使用一对多的处理方式 - Notification
Request
模拟了一对一的靶向操作,而 Notification
则模拟了一对多的发布/订阅操作。与 Request
类似,首先需要定义通知消息:
1 | public class ContactAddedNotification : INotification |
接下来,定义两个消费该通知消息的 Handler
类型:
1 | public class ContactAddedPong1 : INotificationHandler<ContactAddedNotification> |
基于前文的 Request
的案例,发布通知,修改代码如下:
1 | public class AddEmergencyContactHandler : AsyncRequestHandler<AddEmergencyContactCommand> |
IMediator
使用 Publish
方法来发布通知。上述代码同时将原来继承的 RequestHandler
替换成了 AsyncRequestHandler
以支持异步编程模型。
需要指出的是,
Mediator
只是在编码级别做了类似「消息队列」的工作,实际的调用序列依然是同步的,Mediator
关注的重点在于「解耦」,而非「异步」。
本 Sample 的源代码可至 Github 下载。