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

У меня есть 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); } 

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

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

 public interface IMessageHandler { bool HandleMessage( IMessage msg ); } public class OrderMessageHandler { bool HandleMessage( IMessage msg ) { if ( !msg is OrderMessage) return false; // Handle the message. } } public class SomeOtherMessageHandler { bool HandleMessage( IMessage msg ) { if ( !msg is SomeOtherMessage ) return false; // Handle the message. } } ... etc ... public class MessageProcessor { private List handlers; public MessageProcessor() { handlers = new List(); 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. } } } 

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

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

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

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

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

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

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

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

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

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

Но я уверен, что хорошее объяснение дано в вашей книге GOF 🙂

второй

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

перегрузка в любом случае лучше, чем создание другого метода для каждого типа сообщений …

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

третий

Если ваше сообщение могло обрабатывать себя, вы могли бы написать интерфейс, поскольку ваш метод процесса зависит от того, какое сообщение вы получили, кажется, проще разместить его внутри classа сообщений …

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

вы затем реализуете это во всех своих classах сообщений и выполняете их:

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

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

По моему опыту обработки сообщений, обычно это тот случай, когда разные потребители сообщений требуют обработки различных типов сообщений. Я нашел шаблон Double Dispatch , чтобы справиться с этим. Основная идея заключается в регистрации набора обработчиков, которые отправляют полученные сообщения обработчику для обработки на основе определенного типа (с использованием перегрузки функций). Потребители регистрируются только для определенных типов, которые они хотят получить. Ниже приведена диаграмма classов.

Диаграмма классов UML Double Dispatch

Код выглядит так:

IHandler

 public interface IHandler { } 

IMessageHandler

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

Шеззаде

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

MessageBase

 public class MessageBase : 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 handlerTarget = handler as IMessageHandler; 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, IMessageHandler // Just add handlers here to process incoming messages { public DerivedMessageHandlerOne() { } #region IMessageHandler 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 { 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). Затем определите конкретный class, который реализует IMessageProcessor для каждого типа сообщения.

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

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

 message.processor.processMessage(); 

Для моей маленькой среды обмена сообщениями в приложении Silverlight я использую шаблон Mediator. Это своего рода шина / брокер обмена сообщениями, к которым объекты подписываются для определенного типа или типов сообщений. Тогда этот объект посредника (брокер / автобус) решает, кто получит какие сообщения.
Сокет:

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

Образцы методов, которые называются:

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

или публикации (трансляции) сообщений:

 Publish(new ChatMessage()); 

BaseMessage – абстрактный class, который наследует все мои сообщения, и имеет только ссылку на отправителя и некоторый уникальный Guid.

Я взял отправную точку для создания моей системы обмена сообщениями из MVVM Light Toolkit , вы можете взглянуть на их исходный код, это не сложно!

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

Возможно, вы захотите ознакомиться с шаблонами интеграции предприятия Грегором Хохпе и Бобби Вуффом. Он имеет хороший каталог шаблонов для обработки сообщений.

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

 public static class MessageDispatcher { private static readonly IMessageHandler s_DefaultHandler = new DefaultMessageHandler(); private static readonly Dictionary s_Handlers = new Dictionary(); 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 { } 

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

Добавьте метод ProcessMessage () в интерфейс iMessage и позвольте конкретному сообщению полиморфно решить правильный способ обработки самих себя.

Затем ваш код становится

 newMessage.ProcessMessage(); 

Вот хорошая статья об использовании polymorphismа вместо условностей .

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

Все сообщения отправляются сериализованными и начинаются с идентификатора типа сообщения. Затем у меня есть оператор 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 году я бы использовал пакет, такой как MediatR от Jimmy Bogard ( https://github.com/jbogard/MediatR ).

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

  • Классы менеджеров Unity singleton
  • Это использование оператора «instanceof» считается плохим дизайном?
  • Каким образом реализация Майерсом Синглтона на самом деле является синглтон
  • чистый эквивалент эквивалентного эквивалента C ++? (Ответ: Идиома Адвокат-Клиент)
  • Thread Safe C # Singleton Pattern
  • Разница между запросом MVC и компонентом MVC
  • Длинный список операторов if в Java
  • Реализация Singleton с Enum (на Java)
  • Что такое статические заводские методы?
  • Определение и реализация статического polymorphismа
  • Неужели плохая практика заставит сеттера вернуть «это»?
  • Давайте будем гением компьютера.