繁体   English   中英

处理多种消息类型的设计模式

[英]Design pattern for handling multiple message types

我的办公桌上有 GOF,我知道一定有某种设计模式可以解决我遇到的问题,但是我想不通。

为了简单起见,我更改了我正在使用的一些接口的名称。

所以这就是问题所在,在网络的一侧,我有多个服务器发送不同类型的消息。 在网络的另一端,我有一个客户端需要能够处理所有不同类型的消息。

所有消息都实现相同的通用接口 IMessage。 我的问题是,当客户端收到一个新的 IMessage 时,它如何知道收到的是什么类型的 IMessage?

我想我可以做类似下面的事情,但这感觉很糟糕。

TradeMessage tMessage = newMessage as TradeMessage;
if (tMessage != null)
{
    ProcessTradeMessage(tMessage);
}

OrderMessage oMessage = newMessage as OrderMessage;
if (oMessage != null)
{
    ProcessOrderMessage(oMessage);
}

第二个想法是向 IMessage 添加一个名为 MessageTypeID 的属性,但这需要我编写如下内容,这也感觉很糟糕。

TradeMessage tMessage = new TradeMessage();
if (newMessage.MessageTypeID == tMessage.MessageTypeID)
{
    tMessage = newMessage as TradeMessage;
    ProcessTradeMessage(tMessage); 
}

OrderMessage oMessage = new OrderMessage();
if (newMessage.MessageTypeID == oMessage.MessageTypeID)
{
    oMessage = newMessage as OrderMessage;
    ProcessOrderMessage(oMessage);
}

我知道这个一般问题已经被解决了一百万次,所以必须有更好的方法来解决将接口作为参数的方法的问题,但需要基于 class 实现该接口的不同流量控制。

您可以为每种消息类型创建单独的消息处理程序,并将消息天真地传递给每个可用的处理程序,直到找到可以处理它的消息处理程序。 与责任链模式类似:

public interface IMessageHandler {
    bool HandleMessage( IMessage msg );
}

public class OrderMessageHandler {
    bool HandleMessage( IMessage msg ) {
       if ( !(msg is OrderMessage)) return false;

       // Handle the message and return true to indicate it was handled
       return true; 
    }
}

public class SomeOtherMessageHandler {
    bool HandleMessage( IMessage msg ) {
       if ( !(msg is SomeOtherMessage) ) return false;

       // Handle the message and return true to indicate it was handled
       return true;
    }
}

... etc ...

public class MessageProcessor {
    private List<IMessageHandler> handlers;

    public MessageProcessor() {
       handlers = new List<IMessageHandler>();
       handlers.add(new SomeOtherMessageHandler());
       handlers.add(new OrderMessageHandler());
    }

    public void ProcessMessage( IMessage msg ) {
       bool messageWasHandled
       foreach( IMessageHandler handler in handlers ) {
           if ( handler.HandleMessage(msg) ) {
               messageWasHandled = true;
               break;
           }
       }

       if ( !messageWasHandled ) {
          // Do some default processing, throw error, whatever.
       }
    }
}

您还可以将其实现为映射,将消息类名称或消息类型id作为键,并将相应的处理程序实例作为值。

其他人建议让消息对象本身“处理”,但这对我来说感觉不对。 似乎最好将消息的处理与消息本身分开。

我喜欢的其他一些事情:

  1. 你可以通过spring或者what-have-you注入消息处理程序,而不是在构造函数中创建它们,这使得它非常容易测试。

  2. 通过简单地从ProcessMessage循环中删除“break”,您可以引入类似于主题的行为,其中您可以为单个消息提供多个处理程序。

  3. 通过将消息与处理程序分离,您可以在不同的目标处为同一消息提供不同的处理程序(例如,以不同方式处理相同消息的多个MessageProcessor类)

一些解决方案适用于此,首先是最佳解决方案,最后是最不好的解决方案。 所有示例都是伪代码:

第1,最好的解决方案

Vincent Ramdhanie介绍了解决这个问题的实际正确模式,称为策略模式

此模式创建一个单独的“处理器”,在这种情况下,相应地处理消息。

但我很确定GOF的书中给出了一个很好的解释:)

第2

如评论所述,消息可能无法自行处理,为消息或基类创建接口仍然很有用,因此您可以为消息创建一般处理函数,并为更具体的消息重载消息。

在任何情况下,重载都比为每种类型的消息创建不同的方法更好......

public class Message {}
public class TradeMessage extends Message {}

public class MessageProcessor {
    public function process(Message msg) {
        //logic
    }

    public function process(TradeMessage msg) {
        //logic
    }
}

第3

如果您的消息可以处理自己,您可以编写一个接口,因为您的处理方法取决于您获得的消息,将它放在消息类中似乎更容易...

public interface IMessage
{
    public function process(){}
}

然后在所有消息类中实现它并处理它们:

list = List<IMessage>();
foreach (IMessage message in list) {
    message.process();
}

在您的列表中,您可以存储任何实现该接口的类...

根据我在消息处理方面的经验,通常情况是消息的不同消费者需要处理各种消息类型。 我找到了Double Dispatch模式来很好地处理这个问题。 基本思想是注册一组处理程序,这些处理程序将接收到的消息分派给处理程序,以便根据特定类型进行处理(使用函数重载)。 消费者只注册他们希望收到的特定类型。 下面是一个类图。

Double Dispatch UML类图

代码如下所示:

IHandler

public interface IHandler
{
}

IMessageHandler

public interface IMessageHandler<MessageType> : IHandler
{
   void ProcessMessage(MessageType message);
}

即时聊天

public interface IMessage
{
   void Dispatch(IHandler handler);
}

MessageBase

public class MessageBase<MessageType> : IMessage
   where MessageType : class, IMessage
{
   public void Dispatch(IHandler handler)
   {
      MessageType msg_as_msg_type = this as MessageType;
      if (msg_as_msg_type != null)
      {
         DynamicDispatch(handler, msg_as_msg_type);
      }
   }

   protected void DynamicDispatch(IHandler handler, MessageType self)
   {
      IMessageHandler<MessageType> handlerTarget = 
         handler as IMessageHandler<MessageType>;
      if (handlerTarget != null)
      {
         handlerTarget.ProcessMessage(self);
      }
   }
}

DerivedMessageHandlerOne

// Consumer of DerivedMessageOne and DerivedMessageTwo 
// (some task or process that wants to receive messages)
public class DerivedMessageHandlerOne : 
   IMessageHandler<DerivedMessageOne>, 
   IMessageHandler<DerivedMessageTwo>
   // Just add handlers here to process incoming messages
{     
   public DerivedMessageHandlerOne() { }

   #region IMessageHandler<MessaegType> Members

   // ************ handle both messages *************** //
   public void ProcessMessage(DerivedMessageOne message)
   {
     // Received Message one, do something with it
   }

   public void ProcessMessage(DerivedMessageTwo message)
   {
      // Received Message two, do something with it   
   }

   #endregion
}

DerivedMessageOne

public class DerivedMessageOne : MessageBase<DerivedMessageOne>
{
   public int MessageOneField;

   public DerivedMessageOne() { }
}

然后你就有了一个管理处理程序的容器,你就完成了。 收到消息时,处理程序列表的简单循环,以及处理程序接收他们想要的消息

// Receive some message and dispatch it to listeners
IMessage message_received = ...
foreach(IHandler handler in mListOfRegisteredHandlers)
{
   message_received.Dispatch(handler);
}

这个设计出自一个我回答多态事件处理的问题

一种选择是让消息带有自己的处理程序。 也就是说,创建一个名为IMessageProcessor的接口,它指定方法processMessage(IMessage)。 接下来定义为每种类型的消息实现IMessageProcessor的具体类。

然后,每个IMessage类将定义自己的处理器。

当您收到消息对象时,您将执行以下操作:

message.processor.processMessage();

对于我在Silverlight应用程序中的小消息传递框架,我正在使用Mediator模式。 它是某种消息传递总线/代理,对象订阅特定类型或类型的消息。 然后,这个Mediator对象(经纪人/总线)决定谁将收到什么样的消息。
喜欢:

SubscribeFor<ChatMessage>().If(x=>x.SomeProp==true).Deliver(MyMethod);

调用的示例方法:

void MyMethod(ChatMessage msg) , or
void MyMethod(BaseBessage msg)

或发布(广播)消息:

Publish(new ChatMessage());

BaseMessage是抽象类,我的所有消息都是继承的,只是引用了发送者和一些独特的Guid。

我从MVVM Light Toolkit构建我的消息传递框架的起点,你可以看看他们的源代码,它并不复杂!

如果你问,我可以把c#代码放在某个地方吗?

将ProcessMessage()方法添加到iMessage接口,让具体消息以多态方式决定自己处理的正确方法。

然后你的代码变成了

newMessage.ProcessMessage();

这是一篇关于使用多态而不是条件的好文章。

您可能想看看Gregor Hohpe和Bobby Woolf的企业集成模式 它有一个很好的消息处理模式目录。

调度模式可能运行良好。

public static class MessageDispatcher
{
  private static readonly IMessageHandler s_DefaultHandler =
      new DefaultMessageHandler();
  private static readonly Dictionary<Type, IMessageHandler> s_Handlers =
      new Dictionary<Type, IMessageHandler>();

  static MessageDispatcher()
  {
    // Register a bunch of handlers.
    s_Handlers.Add(typeof(OrderMessage), new OrderMessageHandler());
    s_Handlers.Add(typeof(TradeMessage), new TradeMessageHandler());
  }

  public void Dispatch(IMessage msg)
  {
    Type key = msg.GetType();
    if (s_Handlers.ContainsKey(key))
    {
      // We found a specific handler! :)
      s_Handlers[key].Process(msg);
    }
    else
    {
      // We will have to resort to the default handler. :(
      s_DefaultHandler.Process(msg);
    }
  }
}

public interface IMessageHandler
{
  void Process(IMessage msg);
}

public class OrderMessageHandler : IMessageHandler
{
}

public class TradeMessageHandler : IMessageHandler
{
}

这个主题有各种各样的变化。 它们都将包含一个包含许多不同处理程序的调度程序对象。 如果调度程序找不到特定的处理程序,您应该考虑使用默认处理程序。 您如何选择将消息分发给适当的处理程序有很多自由。 我碰巧根据类型进行调度,但你可以使它任意复杂化。 也许调度员可以检查消息的内容以发现最佳处理程序。 也许消息带有一个标识首选处理程序的密钥。 我不知道。 这里有很多可能性。

在类似的场景中,我有一台服务器,它接收来自多个客户端的大量不同消息。

所有消息都以串行方式发送,并以消息类型的标识符开头。 然后我有一个查看标识符的switch语句。 然后将消息反序列化(到非常不同的对象)并根据需要进行处理。

类似的事情可以通过传递实现接口的对象来完成,该接口包括指示消息类型的方式。

public void ProcessMessage(IMessage msg)
{
    switch(msg.GetMsgType())  // GetMsgType() is defined in IMessage
    {
        case MessageTypes.Order:
            ProcessOrder(msg as OrderMessage);  // Or some other processing of order message
            break;
        case MessageTypes.Trade:
            ProcessTrade(msg as TradeMessage); //  Or some other processing of trade message
        break;
        ...
    }
}

我知道这是一个较旧的主题,多年来有很多非常好的答案。

然而,在2018年,我会使用Jimmy Bogard的MediatR( https://github.com/jbogard/MediatR )等软件包。

它提供了消息发送和处理的解耦,例如请求/响应,命令/查询,单向,发布/订阅,异步,多态调度等。

我知道它太老了,但我今天不得不实施类似的东西,我只想对已接受的答案提一些有用的旁注,

首先,为了减少具体 Handle 实现中的代码重复(is X,is Y),我建议创建一个抽象处理程序 class,因此:

public class OrderMessageHandler : IMessageHandler {
    bool HandleMessage( IMessage msg ) {
       if ( !(msg is OrderMessage)) return false;

       // Handle the message and return true to indicate it was handled
       return true; 
    }
}
public class SomeOtherMessageHandler : IMessageHandler {
    bool HandleMessage( IMessage msg ) {
       if ( !(msg is SomeOtherMessage) ) return false;

       // Handle the message and return true to indicate it was handled
       return true;
    }
}

变成:

public abstract class MessageHandler<T> : IMessageHandler where T : IMessage
{
    bool HandleMessage(IMessage msg)
    {
        if (!(msg is T concreteMsg)) return false;
        Handle(concreteMsg);
        return true;
    }
    protected abstract void Handle(T msg);
}
public class OrderMessageHandler : MessageHandler<OrderMessage>
{
    protected override void Handle(OrderMessage msg)
    {
        // do something with the concrete OrderMessage type
    }
}
public class SomeOtherMessageHandler : MessageHandler<SomeOtherMessage>
{
    protected override void Handle(SomeOtherMessage msg)
    {
        // do something with the concrete SomeOtherMessage type
    }
}

是的,我会考虑使用Dictionary<Type, IMessageHandler>而不是foreach并强制从处理中返回 bool 来决定它是否被处理,所以我的最终答案是:

*(ConcreteType 不是必须的,它可以帮助您在不指定类型的情况下添加处理程序)

public interface IMessageHandler
{
    Type ConcreteType { get; }
    void HandleMessage(IMessage msg);
}

public abstract class MessageHandlerBase<TConcreteMessage> : IMessageHandler where TConcreteMessage : IMessage
{
    public Type ConcreteType => typeof(TConcreteMessage);
    public void HandleMessage(IMessage msg)
    {
        if (msg is not TConcreteMessage concreteMsg) return;
        Handle(concreteMsg);
    }
    protected abstract void Handle(TConcreteMessage msg);
}

public class OrderMessageHandler : MessageHandlerBase<OrderMessage>
{
    protected override void Handle(OrderMessage msg)
    {
        // do something with the concrete OrderMessage type
    }
}

public class SomeOtherMessageHandler : MessageHandlerBase<SomeOtherMessage>
{
    protected override void Handle(SomeOtherMessage msg)
    {
        // do something with the concrete SomeOtherMessage type
    }
}

public class MessageProcessor
{
    private readonly Dictionary<Type, IMessageHandler> _handlers = new();

    public MessageProcessor()
    {
    }
    public void AddHandler(IMessageHandler handler)
    {
        var concreteMessageType = handler.ConcreteType;
        if (_handlers.ContainsKey(concreteMessageType))
        {
            throw new Exception($"handler for type {concreteMessageType} already exists.");
            //if you want to support multiple handlers for same type it can be solved with dictionary of List<T>
        }
        _handlers[concreteMessageType] = handler;
    }

    public void ProcessMessage(IMessage msg)
    {
        if (_handlers.TryGetValue(msg.GetType(), out var handler))
        {
            handler.HandleMessage(msg);
        }
        else
        {
            // Do some default processing, throw error, whatever.
        }
    }
}

public class OrderMessage : IMessage
{
    public Guid Guid { get; set; }
    public int Number { get; set; }
}

public class SomeOtherMessage : IMessage
{
    public Guid Guid { get; set; }
    public string Text { get; set; }
}

希望它可以帮助将来的人:)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM