在面向对象编程 (OOP) 中,程序通常由许多类组成。开发人员在这些类之间分发业务逻辑和应用程序功能。我们拥有的类越多,这些类之间需要的通信就越多,有时这会增加应用程序的复杂性。程序变得更难阅读和维护,因为任何更改都可能影响其他几个依赖类中的代码。为了解决这个问题,我们通常使用一种称为中介者设计模式的设计模式。在这篇文章中,我将通过一些实际示例深入介绍中介模式。我还将向您展示如何自己或使用非常著名的 MediatR 库来实现 Mediator 模式。
什么是中介模式?
中介者模式被认为是一种行为模式,它定义了对象如何相互通信。它于 1994 年在著名的“设计模式:可重用面向对象软件的元素”一书中首次引入,但近年来变得越来越流行。
根据维基百科:
使用中介者模式,对象之间的通信被封装在中介者对象中。对象不再直接相互通信,而是通过中介进行通信。这减少了通信对象之间的依赖关系,从而减少了耦合。 —— 维基百科
中介者模式减少了一组交互对象之间的紧密耦合,从而使开发人员可以轻松地独立更改对象的功能。对象不直接调用其他对象,而是将它们的交互委托给中介对象。这个中介对象然后处理对象之间的通信和交互。
Mediator 模式的一个真实示例是机场的交通控制室。如果所有航班都必须相互交互才能找到下一个将要降落的航班,那将会造成很大的混乱。相反,航班仅将其状态发送给调解员(控制室)。
设置 ASP.NET Core 项目
假设您有一个在线电子商务应用程序,它向用户显示产品目录。每个产品都有添加到购物篮按钮,允许用户将不同的产品添加到他们的购物篮中。当用户单击“添加到购物篮”按钮时,您将选定的产品 ID 传递给某个需要执行以下三个任务的后端服务或控制器:
- 从某个后端服务或数据库中获取产品详细信息
- 将产品添加到购物篮
- 发送电子邮件通知客户
我们将创建一个演示应用程序来模拟上述场景,然后我们将看到如何在这个真实示例中使用中介者模式。创建一个新的 ASP.NET Core MVC 项目并在 Models 文件夹中定义以下 Product 类。
Product.cs
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
接下来,我们将创建三个服务来处理上述三个需求。当然,我不会用实际代码来实现这些服务,因为这篇文章的目的是向您展示如何实现中介模式以及如何处理不同对象之间的通信。
第一个服务 ProductService 将从某个后端数据库中获取产品。它只有一个方法 GetProduct(int id) 将产品 id 作为参数并返回与该 id 匹配的产品。
IProductService.cs
public interface IProductService
{
Product GetProduct(int id);
}
出于演示目的,让我们从 GetProduct 方法返回一个假产品。
ProductService.cs
public class ProductService : IProductService
{
public Product GetProduct(int id)
{
// In real world application you will fetch Product from database
return new Product(){ Id = id, Name = "Dell Laptop", Price = 800 };
}
}
下一个服务会将我们在上述方法中获取的产品添加到购物车中。使用将产品对象作为参数的 AddToBasket(Product product) 方法创建以下 ShoppingService。
IShoppingService.cs
public interface IShoppingService
{
void AddToBasket(Product product);
}
ShoppingService.cs
public class ShoppingService : IShoppingService
{
public void AddToBasket(Product product)
{
// Implement the code to add the product to a basket.
}
}
最后,我们需要名为 NotificationService 的第三个服务,该服务具有单个 SendNotification(Product product) 方法以通过电子邮件向客户发送产品详细信息。
INotificationService.cs
public interface INotificationService
{
void SendNotification(Product product);
}
NotificationService.cs
public class NotificationService : INotificationService
{
public void SendNotification(Product product)
{
// Implement the code to send an email notification
// to customer with the product details
}
}
在 Startup.cs 文件的 ConfigureServices 方法中注册所有三个服务,以便您可以使用依赖注入将它们注入到控制器和其他类中。如果您想了解有关依赖注入的更多信息,请阅读我的 ASP.NET Core 依赖注入分步指南。
Startup.cs
public class ShoppingController : Controller
{
private readonly IProductService _productService;
private readonly INotificationService _notificationService;
private readonly IShoppingService _shoppingService;
public ShoppingController(
IProductService productService,
INotificationService notificationService,
IShoppingService shoppingService)
{
_productService = productService;
_notificationService = notificationService;
_shoppingService = shoppingService;
}
}
接下来,创建以下以产品 ID 作为参数的 AddToBasket 操作方法。注意,如何在 action 方法中依次调用所有三个服务的方法来满足上述三个要求。
[HttpPost]
public IActionResult AddToBasket(int id)
{
// Fetch Product from Database
var product = _productService.GetProduct(id);
// Add Product to Basket
_shoppingService.AddToBasket(product);
// Send Notification to User
_notificationService.SendNotification(product);
return View();
}
我们在不使用中介者模式的情况下实现了所有业务需求,您可以很容易地看到控制器与太多服务进行交互。它依赖于所有三个服务,如果一种服务方法将改变,您必须更改上述控制器的代码。这是中介者模式解决的问题,因为它减少了与中介者对象的这种耦合和依赖性。接下来让我们实现 Mediator 模式,看看它是如何减少上述依赖的。
在 ASP.NET Core 中实现中介者模式
无论您是在现有应用程序中实现中介者模式,还是从头开始,步骤都几乎相同。您可能只需要更改这些步骤的实施顺序。有两种方法可以实现中介者模式。您可以编写我接下来将向您展示的实现,也可以使用一些第三方库,例如 MediatR。我将在这篇文章中向您展示这两种技术。在本节中,我们将推出我们自己的中介者模式实现。
使用单个方法 Handle(int id) 创建一个新接口 IShippingMediator。
IShippingMediator.cs
public interface IShoppingMediator
{
void Handle(int id);
}
接下来,创建一个将实现上述接口的类 ShoppingMediator。类的实现对您来说可能很熟悉。我们在类中注入相同的三个服务,并在 Handle 方法中调用这些服务的方法。
ShoppingMediator.cs
public class ShoppingMediator : IShoppingMediator
{
private readonly IProductService _productService;
private readonly INotificationService _notificationService;
private readonly IShoppingService _shoppingService;
public ShoppingMediator(
IProductService productService,
INotificationService notificationService,
IShoppingService shoppingService)
{
_productService = productService;
_notificationService = notificationService;
_shoppingService = shoppingService;
}
public void Handle(int id)
{
// Fetch Product from Database
var product = _productService.GetProduct(id);
// Add Product to Basket
_shoppingService.AddToBasket(product);
// Send Notification to User
_notificationService.SendNotification(product);
}
}
在 Startup.cs 文件中注册 ShoppingMediator 以确保您可以将中介注入您的控制器。
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddScoped<INotificationService, NotificationService>();
services.AddScoped<IShoppingService, ShoppingService>();
services.AddScoped<IProductService, ProductService>();
services.AddScoped<IShoppingMediator, ShoppingMediator>();
}
现在,由于我们已将所有代码从 ShoppingController 移至 ShoppingMediator,因此可以简化 ShoppingController 代码。我们只需要在控制器中注入 ShoppingMediator 类并从 AddToBasket 操作方法调用 Handle 方法。
ShoppingController.cs
public class ShoppingController : Controller
{
private readonly IShoppingMediator _shoppingMediator;
public ShoppingController(IShoppingMediator shoppingMediator)
{
_shoppingMediator = shoppingMediator;
}
[HttpPost]
public IActionResult AddToBasket(int id)
{
_shoppingMediator.Handle(id);
return View();
}
}
ShoppingController 不依赖于这三个服务。控制器只需要使用 ShoppingMediator 类,如果这三个服务中的任何一个中的代码发生变化,控制器代码将保持不变,因为它与这些服务没有紧密耦合。它甚至不需要知道这些服务是如何调用的,因为这个责任现在转移到了 Mediator 类。
使用 MediatR 实现中介者模式
MediatR 是最流行的库之一,它具有简单的、进程内的 Mediator 模式实现,没有依赖项。它支持请求、响应、命令、查询、通知和事件,并具有同步和异步方法来在对象之间分派消息。
MediatR 中有两种类型的请求——返回值的请求和不返回值的请求:
- IRequest - 请求返回一个值
- IRequest - 请求不返回值
每个请求类型都有自己的处理程序接口,以及一些辅助基类:
- IRequestHandler - 实现这个并返回任务
- RequestHandler - 继承它并返回 U
对于没有返回值的请求:
- IRequestHandler – 实现这个,你将返回 Task。
- AsyncRequestHandler - 继承它,你将返回任务。
- RequestHandler - 继承它,你将不会返回任何东西(无效)。
您可以通过 NuGet 包管理器下载并安装 MediatR 库。如果您使用的是 ASP.NET Core,那么您还可以安装 MediatR.Extensions.Microsoft.DependencyInjection 包,该包将允许您使用名为 AddMediatR(Assembly) 的扩展方法在 Startup.cs 文件中注册项目的所有处理程序。
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddScoped<INotificationService, NotificationService>();
services.AddScoped<IShoppingService, ShoppingService>();
services.AddScoped<IProductService, ProductService>();
services.AddMediatR(typeof(Startup));
}
使用上面显示的 AddMediatR 方法配置 MediatR 库后,我们现在可以使用以下两个步骤来使用 MediatR 库:
- 我们需要创建一个实现 IRequest 或 IRequest 的自定义请求类
- 我们需要创建一个客户请求处理程序通过实现上述接口之一,并需要在此客户请求处理程序中添加我们所有的登录信息。
让我们在上面实现的同一个购物车示例中使用 MediatR 库。首先,创建以下继承 IRequest 接口的 ShoppingBasketRequest 类。
ShoppingBasketRequest.cs
public class ShoppingBasketRequest: IRequest
{
public int ProductId { get; set; }
}
接下来,通过继承 RequestHandler 类创建以下 ShoppingBasketRequestHandler 类并将 ShoppingBasketRequest 对象作为 T 传递。我们还需要覆盖 Handle 方法,该方法将包含我们希望该方法为我们运行的所有代码。
public class ShoppingBasketRequestHandler : RequestHandler<ShoppingBasketRequest>
{
private readonly IProductService _productService;
private readonly INotificationService _notificationService;
private readonly IShoppingService _shoppingService;
public ShoppingBasketRequestHandler(
IProductService productService,
INotificationService notificationService,
IShoppingService shoppingService)
{
_productService = productService;
_notificationService = notificationService;
_shoppingService = shoppingService;
}
protected override void Handle(ShoppingBasketRequest request)
{
// Fetch Product from Database
var product = _productService.GetProduct(request.ProductId);
// Add Product To Basket
_shoppingService.AddToBasket(product);
// Send Notification to User
_notificationService.SendNotification(product);
}
}
上面的代码与我们在上面实现自己的中介模式实现时编写的代码非常相似。我们正在注入相同的三个服务并在 Handle 方法中调用相同的方法。
要在我们的 ShoppingController 中使用上述请求,我们需要在控制器的构造函数中注入 IMediator 接口。然后我们可以调用 Send 方法并将 ShoppingBasketRequest 对象作为参数传递。然后中介会自动调用上面的 Handle 方法。
ShoppingController.cs
public class ShoppingController : Controller
{
protected readonly IMediator _mediator;
public ShoppingController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public IActionResult AddToBasket(int id)
{
_mediator.Send(new ShoppingBasketRequest() { ProductId = id });
return View();
}
}
总结概括
我相信您现在对 Mediator 模式是什么以及如何自己或使用 MedaitR 库实现它有一个基本的了解。我尽力使用现实世界的购物车示例来解释所有内容,以便您更快地理解该概念并将其与您在项目中面临的类似问题联系起来。如果您想了解有关 MediatR 库的更多信息,则可以阅读以下 wiki 站点。