Подтвердить что ты не робот

Шаблон проектирования для обработки нескольких типов сообщений

У меня есть GOF, сидящий на моем столе здесь, и я знаю, что должен быть какой-то шаблон дизайна, который решает проблему, которую я имею, но человек, которого я не могу понять.

Для упрощения я изменил имя некоторых интерфейсов, которые я использую.

Итак, вот проблема, с одной стороны провода, у меня есть несколько серверов, которые отправляют разные типы сообщений. С другой стороны провода у меня есть клиент, который должен иметь возможность обрабатывать все типы сообщений.

Все сообщения реализуют один и тот же общий интерфейс 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);
}

Я знаю, что эта общая проблема решена в миллион раз, поэтому должен быть более эффективный способ решения проблемы использования метода, который использует интерфейс как параметр, но нуждается в различном управлении потоком, основанном на том, что класс реализовал этот интерфейс.

4b9b3361

Ответ 1

Вы можете создать отдельные обработчики сообщений для каждого типа сообщений и наивно передавать сообщение каждому доступному обработчику, пока не найдете тот, который сможет его обработать. Аналогично схеме цепочки ответственности:

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

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

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

Вы также можете реализовать это в виде карты с именем класса сообщения или идентификатором типа сообщения в качестве ключа и соответствующим экземпляром обработчика в качестве значения.

Другие предложили, чтобы объект сообщения сам "обрабатывал", но мне это не кажется правильным. Похоже, что было бы лучше отделить обработку сообщения от самого сообщения.

Некоторые другие вещи, которые мне нравятся:

  1. Вы можете вставлять обработчики сообщений с помощью Spring или что-то еще, вместо того, чтобы создавать их в конструкторе, что делает это очень тестируемым.

  2. Вы можете ввести похожее на тему поведение, когда у вас есть несколько обработчиков для одного сообщения, просто удалив разрыв в цикле ProcessMessage.

  3. Отделив сообщение от обработчика, вы можете иметь разные обработчики для одного и того же сообщения в разных местах назначения (например, несколько классов MessageProcessor, которые по-разному обрабатывают одни и те же сообщения)

Ответ 2

Для этого применимо несколько решений: первое - лучшее решение, последнее - наименее наилучшее. Все примеры - псевдокод:

1-е и лучшее решение

Винсент Рамдхани представил правильный правильный шаблон для решения этой проблемы, который называется шаблон стратегии.

Этот шаблон создает отдельный "процессор", в этом случае для обработки сообщений соответственно.

Но я уверен, что в вашей книге есть хорошее объяснение в 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();
}

в вашем списке вы можете сохранить любой класс, который реализует этот интерфейс...

Ответ 3

Из моего опыта обработки сообщений, обычно это тот случай, когда разные потребители сообщений требуют обработки различных типов сообщений. Я нашел шаблон 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() { }
}

Тогда у вас просто есть контейнер, который управляет обработчиками, и вы все настроены. Простой цикл через список обработчиков при получении сообщения, а обработчики получают сообщения, в которых они хотят.

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

Эта конструкция возникла из вопроса, который я задал немного назад о Полиморфном управлении событиями

Ответ 4

Один из вариантов - иметь сообщения с собственными обработчиками. То есть, создайте интерфейс IMessageProcessor, который определяет метод processMessage (IMessage). Затем определите конкретный класс, который реализует IMessageProcessor для каждого типа сообщений.

Каждый класс IMessage будет определять свой собственный процессор.

Когда вы получаете объект сообщения, вы сделаете что-то вроде этого:

message.processor.processMessage();

Ответ 5

Для моей небольшой платформы обмена сообщениями в приложении Silverlight я использую шаблон 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, вы можете взглянуть на их исходный код, это не сложно

Если вы хотите, я могу поставить код С# для этого где-нибудь?

Ответ 8

Схема диспетчеризации может работать хорошо.

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

Есть разные варианты этой темы. Все они будут иметь объект диспетчера, который содержит много разных обработчиков. Вы должны рассмотреть обработчик по умолчанию, если диспетчер не может найти конкретного обработчика. Существует большая свобода в том, как вы выбираете отправку сообщений соответствующим обработчикам. Мне просто приходится отправлять по типу, но вы можете сделать это произвольно более сложным. Возможно, диспетчер мог проверить содержимое сообщения, чтобы обнаружить лучшего обработчика. Возможно, сообщение содержит ключ, который идентифицирует предпочтительный обработчик. Я не знаю. Здесь есть много возможностей.

Ответ 9

В аналогичном сценарии у меня есть сервер, который получает много разных сообщений от нескольких клиентов.

Все сообщения отправляются сериализованными и начинаются с идентификатора типа сообщения. Затем у меня есть оператор 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;
        ...
    }
}

Ответ 10

Я знаю, что это старая ветка с несколькими очень хорошими ответами за эти годы.

Однако в 2018 году я бы использовал такой пакет, как Jimmy Bogard MediatR (https://github.com/jbogard/MediatR).

Он обеспечивает разделение отправки и обработки сообщений с такими шаблонами, как запрос/ответ, команда/запрос, односторонний режим, публикация/подписка, асинхронность, полиморфная диспетчеризация и т.д.