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

时间:2009-09-25 14:03:22

标签: c# design-patterns

我已经把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);
}

我知道这个普遍的问题已经被解决了一百万次,所以必须有一种更好的方法来解决一个方法将接口作为参数的问题,但是需要基于什么类实现的不同的流控制那个界面。

10 个答案:

答案 0 :(得分:20)

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

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或者有什么来注入消息处理程序,而不是在构造函数中创建它们,这使得它非常可测试。

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

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

答案 1 :(得分:15)

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

第一,最佳解决方案

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

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

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

<强>第二

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

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

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

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

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

<强>第三

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

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

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

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

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

答案 2 :(得分:8)

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

Double Dispatch UML Class Diagram

代码如下所示:

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() { }
}

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

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

这个设计来自我前一段时间回答Polymorphic Event Handling

的问题

答案 3 :(得分:4)

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

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

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

message.processor.processMessage();

答案 4 :(得分:3)

对于我在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#代码放在某个地方吗?

答案 5 :(得分:2)

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

您的代码将变为

newMessage.ProcessMessage();

这是一篇关于using polymorphism instead of conditionals的好文章。

答案 6 :(得分:2)

你可能想看看Gregor Hohpe和Bobby Woolf的Enterprise Integration Patterns。它有一个很好的消息处理模式目录。

答案 7 :(得分:2)

调度模式可能效果很好。

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
{
}

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

答案 8 :(得分:1)

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

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

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

答案 9 :(得分:0)

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

所有消息都以串行方式发送,并以消息类型的标识符开头。然后我有一个查看标识符的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;
        ...
    }
}