为什么是 Mediator 的 INotification/IRequest 标记接口?

问题描述

虽然我是 MediatR 的忠实粉丝,但将其视为对我所有项目的依赖感觉很奇怪(即使是那些不“调度”并且只是发布 POCO 引用标记接口的项目)。

如果要编写一个类似的中介库来做其他事情但具有通用的发送/发布功能,那么当下面的代码与普通 POCO 一起工作时,标记接口有什么需要?

代码通过发送/发布函数在请求/通知之间进行了描述,那么这些标记接口是多余的吗?

如果我要实现自己的中介器,从 NotificationRequest删除标记接口有什么问题吗?

更多的是关于标记接口的设计原则,而不是想要改变 MediatR 本身(为了避免冒犯,我删除了 MediatR,我为记录重复的 lib 很棒,我正在使用多线程来适应我自己的目的同步后台基于流的调度因此存在设计问题)。

处理程序接口

public interface INotificationHandler<in TNotification> where TNotification : class
{
    Task Handle(TNotification notification,CancellationToken cancellationToken);
}

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

没有标记界面的普通 POCO

class TestNotification
{
    public int UserId { get; set; }
    public int Age { get; set; }
}

class TestRequest
{
    public int UserId { get; set; }
}

处理程序

class TestRequestHandler : IRequestHandler<TestRequest,int>
{
    public Task<int> Handle(TestRequest request,CancellationToken cancellationToken)
    {
        return Task.Fromresult(request.UserId);
    }
}

class TestNotificationHandler : INotificationHandler<TestNotification>
{
    public Task Handle(TestNotification notification,CancellationToken cancellationToken)
    {
        Console.WriteLine("hello");
        return Task.CompletedTask;
    }
}

主要

static void Main(string[] args)
{
    _ = new TestNotificationHandler()
                .Handle(new TestNotification(),CancellationToken.None);

    var result = new TestRequestHandler()
                        .Handle(new TestRequest() { UserId = 111 },CancellationToken.None)
                        .Result;

    Console.WriteLine($"result is {result}");
}

C# 小提琴

https://dotnetfiddle.net/HTkfh9

解决方法

1. 当下面的代码与普通 POCO 一起工作时,首先需要标记接口是什么?

我们来看看 MediatR

namespace MediatR
{
    /// <summary>
    /// Marker interface to represent a request with a void response
    /// </summary>
    public interface IRequest : IRequest<Unit> { }

    /// <summary>
    /// Marker interface to represent a request with a response
    /// </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 { }
}

namespace MediatR
{
    /// <summary>
    /// Marker interface to represent a notification
    /// </summary>
    public interface INotification { }
}

它们只是空接口,所以我认为 MediatR 将来可能会像这样为 IRequest 添加一些功能:

public interface IRequestType {
    int GetId();

    string GetName();
}
/// <summary>
/// Marker interface to represent a request with a void response
/// </summary>
public interface IRequest : IRequest<Unit> {
    IRequestType GetRequestType();
}

所以我认为您应该实现 IRequestINotification 以避免将来对源代码进行大的更新

2. 从 Notification 和 Request 中移除标记接口有什么问题吗?

目前,对于您的示例,没问题 但是当你使用 Publish 函数时就不行了

/// <summary>
/// Asynchronously send a notification to multiple handlers
/// </summary>
/// <param name="notification">Notification object</param>
/// <param name="cancellationToken">Optional cancellation token</param>
/// <returns>A task that represents the publish operation.</returns>
Task Publish<TNotification>(TNotification notification,CancellationToken cancellationToken = default)
    where TNotification : INotification;

顺便说一句,我认为 MediatR 需要更新才能像这样为 Mediator 创建一个构建器:

IMediator mediator = new Mediator.Builder()
            .AddRequestHandler(typeof(PingRequest),new PingRequestHandler())
            .Build();
,

您对 INotification 的看法是正确的。如果您要编写自己的 MediatR 实现,则基本上可以删除该标记接口。

但是,对于 IRequest,您需要请求定义中的响应类型,以便能够在调用中介后处理响应。

考虑一个没有定义响应类型的请求:

public class TestRequest
{
}

现在,如果您调用中介器,将无法确定响应类型:

IMediator mediator = GetMediator();

// There is no way to type the response here.
var response = mediator.Send(new TestRequest());

中介者能够输入响应的唯一方式是因为响应定义在请求定义中:

public interface IRequest<out TResponse> : IBaseRequest { }

IMediator Send 的定义如下:

Task<TResponse> Send<TResponse>(IRequest<TResponse> request,CancellationToken cancellationToken = default);

这使得知道返回类型成为可能。

所以,长话短说:

  • 你不需要INotification
  • 确实需要IRequest<TResponse>
,

我实际上不久前也遇到了同样的问题。我真的很想避免让我的所有库都引用 MediatR 包。我也没有看到拥有它们的意义,因为您可以通过将它包装在这样的通用对象中来解决它:

public class NotificationWrapper<TNotification> : INotification {
    public TNofication Data { get; }
    public NotificationWrapper(TNotification notification) {
        Data = notification ?? throw new ArgumentNullException(notification);
    }
}

然后使用 INotificationHandler<NotificationWrapper<SomeType>>

的实现处理它

所以,我最终复制了库并编写了一个没有这些接口的版本。事实证明,您不需要 INotificationIRequest<out TResponse>...但需要权衡。

INotification 遵循 Jesse 上面所说的。但是,对于 IRequest,您会注意到 Send 方法确实有一个用于响应的通用参数。但是,它使用 IRequest<out TResponse> 来推断参数。如果移除接口,您仍然可以使用 TResponse 来查找处理程序,您只需要明确定义来自调用者的响应类型。在我看来,这是一个很好的折衷方案,因为如果请求类型使用不同的响应类型多次实现 IRequest<out TResponse>,您无论如何都必须这样做。而且,如果接受您必须明确定义您期望的响应的条件,您可以删除 IRequest<out TResponse> 接口并像这样调用:

IMediator mediator = GetMediator();
mediator.Send<ExpectedResponse>(request);

我执行此操作的版本托管在此处,并且基于 MediatR 8.0.0: https://github.com/user040F019F/Mediation

诚然,我可能应该做一个 fork/PR,但当时我还不太习惯 github。另外,请忽略自述文件,这是无意义的。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...