簡體   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