【问题标题】:Inheritance and extension of models in c# with multiple implementations of property within basec#中模型的继承和扩展,在base中具有多个属性实现
【发布时间】:2019-08-21 17:08:49
【问题描述】:

我有一个发布/订阅队列,它返回我称之为 QueueMessages 的内容。

QueueMessage 有一个类型和一个正文。类型始终是字符串,但主体因类型而异。我希望继承类设置消息类型。我想添加我自己的本地属性来存储处理数据表。

我希望能够为 body 定义一个通用对象并在继承类中覆盖,但它失败了,因为我正在更改返回类型。

界面:

interface IBaseQueueMessage
{
    Guid Id { get; set; }
    string MessageType { get; }
    object Message { get; set; }
    DateTime ConsumeDate { get; set; }
}

基类:

public abstract class BaseQueueMessage:IBaseQueueMessage
{
    public Guid Id { get; set; }
    public abstract string MessageType { get; }
    public abstract object Message { get; set; }
    public DateTime ConsumeDate => DateTime.Now;
}

继承类(总共有 7 或 8 个不同的类)

public sealed class Type1Message: BaseCallType
{
    public override string MessageType => "Type1Message";
    public override Type1Message Message { get; set; }
}

public class Type1Message
{
    public string aaa { get; set; }
    public int bbb { get; set; }
}

public sealed class Type2Message: BaseCallType
{
    public override string MessageType => "Type2Message";
    public override Type2Message Message { get; set; }
}

public class Type2Message
{
    public string aaa { get; set; }
    public string bbb { get; set; }
    public int ccc {get; set;}
    public bool ddd {get; set;}
}

上述失败,因为我试图返回一个特定的类而不是通用对象。我明白为什么它会失败,但我想知道这样做的正确方法是什么?我可以只为每个类定义单独的类,然后用接口和继承来见鬼,但以这种方式处理它感觉不对。我打算将 QueueMessage 直接映射到每种不同类型的继承类,因此我希望模型与我从队列中获取的完全匹配。

为可能遗漏了一些非常明显的东西提前道歉,我已经有一段时间没有进行任何编码了,这对我来说是一个相对较新的领域。

已编辑以添加有关实施问题的更多详细信息

很多这样的作品,谢谢。我仍然有问题的地方是 MessageHandlerWrapper。如果我调试,则该方法尝试在_handler = handler 中使用的构造函数中的处理程序对象始终为空。

在我的 .net 核心 startup.cs 中,我有:

        void RegisterHandler<TMessageType, THandler>()
            where TMessageType : class
            where THandler : IMessageHandler<TMessageType>
        {
            serviceCollection.AddSingleton<TMessageType>();
            serviceCollection.AddSingleton(
                serviceProvider => new MessageHandlerWrapper<TMessageType>(serviceProvider.GetService<THandler>(), serviceProvider)
            );
        }

...当我在上面提到的代码中调试时,依赖关系解决了该块末尾附近的...=&gt; new MessageHandlerWrapper&lt;...,所以我看不出为什么该服务不可用点。

有没有什么方法可以通过将服务解析到 MessageHandlerWrapper 中的具体处理程序来手动尝试调试它,以查看问题可能出在哪里?

为了完整起见,startup.cs 中的整个部分是:

//set up message handlers
        var msgFactory = new MessageHandlerFactory();

        //create local function to make it easier to add service references
        void RegisterHandler<TMessageType, THandler>()
            where TMessageType : class
            where THandler : IMessageHandler<TMessageType>
        {
            serviceCollection.AddSingleton<TMessageType>();
            serviceCollection.AddSingleton(
                serviceProvider => new MessageHandlerWrapper<TMessageType>(serviceProvider.GetService<THandler>(), serviceProvider)
            );
        }

        // Type1MessageHandler, etc is the implementation of IMessageHandler<Type1>
        RegisterHandler<Type1, Type1MessageHandler>();
        RegisterHandler<Type2, Type2MessageHandler>();
        RegisterHandler<Type3, Type3MessageHandler>();
        RegisterHandler<Type4, Type4MessageHandler>();

        // some string constants for message types would be better.
        serviceCollection.AddSingleton<IMessageHandlerFactory>(serviceProvider =>
        {
            msgFactory.RegisterHandler("Type1",
                serviceProvider.GetService<MessageHandlerWrapper<Type1>>);
            msgFactory.RegisterHandler("Type2",
                serviceProvider.GetService<MessageHandlerWrapper<Type2>>);
            msgFactory.RegisterHandler("Type3",
                serviceProvider.GetService<MessageHandlerWrapper<Type3>>);
            msgFactory.RegisterHandler("Type4",
                serviceProvider.GetService<MessageHandlerWrapper<Type4>>);
            return msgFactory;
        });

        serviceCollection.AddSingleton<IMessageHandler, MessageRouter>();

【问题讨论】:

  • 根据您的接口声明,派生类必须返回类型object。如果您愿意/能够修改接口以使其具有通用性,并且Message 属性可以具有类型T(即接口类型参数),那么每个实现类都可以指定自己的类型。不幸的是,您的问题并不清楚这里有什么可能。
  • 谢谢彼得。请按照您的建议如何更改界面中的消息类型?
  • 查看标记重复的答案。您可能需要也可能不需要该答案中显示的非通用基本接口类型,具体取决于您的具体情况。
  • 在某些时候你会需要消息处理程序。你是如何创造这些的?它们是从 IoC 容器中解析出来的吗?
  • 尚不清楚这是否与您的情况相关,但我正在对通过网络传输的许多不同类型的消息做类似的事情。标头始终具有相同的格式,但每种消息类型的有效负载完全不同。我的Message 类有一个Memory&lt;byte&gt; 和一个GetPayload&lt;T&gt; 方法,其中T 被限制为struct。消费者检查标头中的类型值并使用适当的类型参数调用GetPayload

标签: c# class oop inheritance


【解决方案1】:

这是一种方法。设计的目的是让您能够编写类型安全的通用消息处理程序类,但能够在您从 object 类型的消息开始时调用它们。从一个到另一个桥接可能会很痛苦,我认为这是您问题的核心。

这不会对消息使用继承。 (您提到您可以不继承。)我认为它不会在这种情况下增加任何价值,尽管这可以适应使用继承。

首先,这是一个通用的Message&lt;T&gt; 类。这不会替换您现有的消息类。只是为了有一个类型安全的泛型 handler,你需要一个泛型 message

public class Message<T>
{
    public Message(T content, Guid id, string messageType, DateTime consumeDate)
    {
        Content = content;
        Id = id;
        MessageType = messageType;
        ConsumeDate = consumeDate;
    }

    public T Content { get; }
    public Guid Id { get;  }
    public string MessageType { get; }
    public DateTime ConsumeDate { get;  }
}

接下来,这是一个消息处理程序的接口。这个例子的实现并不重要。重要的是无论T 是什么,你都可以编写一个类来处理它。

public interface IMessageHandler<T> 
{
    void HandleMessage(Message<T> message);
}

下一个接口和类旨在充当从非通用消息(使用object)到通用消息处理程序的“桥梁”:

public interface IMessageHandler
{
    void HandleMessage(IQueueMessage message);
}

public class MessageHandlerWrapper<TMessage> : IMessageHandler
{
    private readonly IMessageHandler<TMessage> _handler;

    public MessageHandlerWrapper(IMessageHandler<TMessage> handler)
    {
        _handler = handler;
    }

    // This is the critical part - it gets us from object to TMessage.
    public void HandleMessage(IQueueMessage message)
    {
        _handler.HandleMessage(
            new Message<TMessage>(
                (TMessage)message.Message,
                message.Id,
                message.MessageType,
                message.ConsumeDate));
    }
}

然后你需要一个根据消息类型字符串返回正确IMessageHandler 的工厂:

public interface IMessageHandlerFactory
{
    IMessageHandler GetHandler(string messageType);
}

将所有这些放在一起将您的非通用消息连接到您的通用消息处理程序:

public class MessageRouter : IMessageHandler
{
    private readonly IMessageHandlerFactory _messageHandlerFactory;

    public MessageRouter(IMessageHandlerFactory messageHandlerFactory)
    {
        _messageHandlerFactory = messageHandlerFactory;
    }

    public void HandleMessage(IQueueMessage message)
    {
        var handler = _messageHandlerFactory.GetHandler(message.MessageType);
        handler.HandleMessage(message);
    }
}

这个类也实现了IMessageHandler。它将获取消息,而不关心类型是什么,使用工厂获取更具体的处理程序,并将消息路由到该处理程序。

现在我们需要实现工厂。这是一个实现,它允许我们从 IServiceProvider 解析处理程序,而无需求助于服务定位器:

public class MessageHandlerFactory : IMessageHandlerFactory
{
    private readonly Dictionary<string, Func<IMessageHandler>> _messageHandlers
        = new Dictionary<string, Func<IMessageHandler>>(StringComparer.OrdinalIgnoreCase);

    public void RegisterHandler(string messageType, Func<IMessageHandler> getHandlerFunction)
    {
        _messageHandlers[messageType] = getHandlerFunction;
    }

    public IMessageHandler GetHandler(string messageType)
    {
        if (_messageHandlers.ContainsKey(messageType))
            return _messageHandlers[messageType]();
        throw new InvalidOperationException($"No handler is registered for message type {messageType}.");
        // Or you could return some default handler that does something else with 
        // unknown message types.
    }
}

现在我们可以注册实现如下:

public static class MessageHandlerServiceCollectionExtensions
{
    public static IServiceCollection AddMessageHandlers(this IServiceCollection services)
    {
        void RegisterHandler<TMessageType, THandler>() 
            where TMessageType : class
            where THandler : IMessageHandler<TMessageType>
        {
            services.AddSingleton<TMessageType>();
            services.AddSingleton(
                serviceProvider => new MessageHandlerWrapper<TMessageType>(serviceProvider.GetService<THandler>())
            );
        }

        // MessageTypeOneHandler is the implementation of IMessageHandler<MessageTypeOne>
        RegisterHandler<MessageTypeOne, MessageTypeOneHandler>();
        RegisterHandler<MessageTypeTwo, MessageTypeTwoHandler>();

        // some string constants for message types would be better.
        services.AddSingleton<IMessageHandlerFactory>(serviceProvider =>
        {
            var factory = new MessageHandlerFactory();
            factory.RegisterHandler("messagetypeone",
                serviceProvider.GetService<MessageHandlerWrapper<MessageTypeOne>>);
            factory.RegisterHandler("messagetypetwo",
                serviceProvider.GetService<MessageHandlerWrapper<MessageTypeTwo>>);
            return factory;
        });

        services.AddSingleton<IMessageHandler, MessageRouter>();
        return services;
    }
}

做了这么多,是不是有点复杂?它对我有用,但我仍然认为值得付出努力。为什么?

  • 消息处理程序是类型安全的,而不是允许object 在代码中传播。我们像疾病一样控制它。我们可以创建实现IMessageHandler&lt;TMessage&gt; 的简单、可测试、单一职责的类。
  • 一切都从容器中得到解决。这意味着消息处理程序都可以有自己独特的依赖关系,这没关系。
  • 没有反射。并不是说反思是邪恶的,但是一旦我们沿着这条路走下去,情况似乎会变得更糟。
  • 如果您需要为更多类型添加处理程序,您或下一个开发人员有一条清晰的前进道路。即使他们不知道如何注册处理程序并且他们不这样做,异常也会为他们提供一条清晰的前进道路,向他们展示他们需要做什么。如果它被注册它会得到解决。如果不是,工厂将抛出异常。

最后,所有这些都是可测试的,甚至是工厂。如果消息类型是enum,您可以只运行一个测试来确保每种类型都有一个处理程序。

[DataTestMethod]
[DataRow("MessageTypeOne")]
[DataRow("MessageTypeTwo")]
public void FactoryResolvesMessageHandlers(string messageType)
{
    var services = new ServiceCollection();
    services.AddMessageHandlers();
    var provider = services.BuildServiceProvider();
    var factory = provider.GetService<IMessageHandlerFactory>();
    var handler = factory.GetHandler(messageType);
    Assert.IsNotNull(handler);
}

【讨论】:

  • 另一个想法 - 考虑一下如果有任何方法可以避免将消息输入为object,那么这一切会变得多么简单。对我来说,这就是复杂性所在。但我不得不多次研究这种模式,其中一些字符串标识类型 - 也许消息是 JSON 或其他东西 - 除了编写代码来桥接别无选择从object 到一般的东西。
  • 非常感谢您,这需要我一段时间才能完成。你是对的,它是 json 并且不能改变,所以是必要的邪恶。这看起来很棒!
  • 在这种情况下,反序列化在MessageHandlerWrapper&lt;TMessage&gt; 中可能有意义。而不是object 作为消息内容,您可以将其保留为字符串。就在上面的示例将消息 object 转换为 TMessage 的地方,您可以改为反序列化 JSON。
  • 我正在解决这个问题,并且对上面的 cmets 中的 // MessageTypeOneHandler is the implementation of IMessageHandler&lt;MessageTypeOne&gt; 有什么疑问?这是什么目的,我很难理解,对不起。这个实现从何而来?我的意图是只使用 Newtonsoft 的 json 类将字符串反序列化为特定的消息类,例如MessageType1.
  • 这意味着一旦您确定了具体类型,例如MessageTypeOneType2Message,您将需要一个实现IMessageHandler&lt;Type2Message&gt; 的类。我没有包含它的实现,因为我不知道在反序列化后实际处理消息时下游会发生什么。 (我也正在开发一个单独的自我回答版本,因为这是一个常见问题,并且通过这样做,答案将更接近问题。)
猜你喜欢
  • 1970-01-01
  • 2012-12-17
  • 1970-01-01
  • 1970-01-01
  • 2013-06-16
  • 2011-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多