Modèle de conception pour la gestion de plusieurs types de message

J’ai le GOF assis sur mon bureau ici et je sais qu’il doit y avoir une sorte de modèle de conception qui résout mon problème, mais je ne peux pas le comprendre.

Pour simplifier, j’ai changé le nom de certaines des interfaces que j’utilise.

Voici donc le problème: d’un côté du réseau, j’ai plusieurs serveurs qui envoient différents types de messages. De l’autre côté du fil, j’ai un client qui doit être capable de gérer tous les types de messages.

Tous les messages implémentent la même interface commune IMessage. Mon problème est, quand le client reçoit un nouvel IMessage, comment sait-il quel type d’IMessage est reçu?

J’ai supposé que je pouvais faire quelque chose comme ce qui suit, mais cela me semble horrible.

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

La seconde pensée, est d’append une propriété à IMessage appelée MessageTypeID, mais cela nécessiterait que je rédige quelque chose comme ce qui suit, ce qui est aussi terrible.

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

Je sais que ce problème général a été abordé un million de fois. Il doit donc exister un moyen plus agréable de résoudre le problème de la méthode qui prend une interface en tant que paramètre, mais nécessite un contrôle de stream différent en fonction de la classe qui a implémenté cette interface.

Vous pouvez créer des gestionnaires de messages distincts pour chaque type de message et le transmettre naïvement à chaque gestionnaire disponible jusqu’à ce que vous en trouviez un qui puisse le gérer. Similaire au modèle de chaîne de responsabilité:

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

Vous pouvez également l’implémenter en tant que mappe, avec le nom de la classe de message ou l’ID de type de message en tant que clé et l’instance de gestionnaire appropriée en tant que valeur.

D’autres ont suggéré que l’object de message se “manipule” lui-même, mais cela ne me semble tout simplement pas correct. On dirait qu’il serait préférable de séparer le traitement du message du message lui-même.

Quelques autres choses que j’aime à ce sujet:

  1. Vous pouvez injecter les gestionnaires de messages via spring ou what-have-you plutôt que de les créer dans le constructeur, ce qui rend ce testable.

  2. Vous pouvez introduire un comportement de type sujet dans lequel vous avez plusieurs gestionnaires pour un même message en supprimant simplement le “saut” de la boucle ProcessMessage.

  3. En séparant le message du gestionnaire, vous pouvez avoir différents gestionnaires pour le même message à différentes destinations (par exemple, plusieurs classes MessageProcessor qui gèrent les mêmes messages différemment).

Quelques solutions s’appliquent à cela, la première est la meilleure solution, la dernière est la meilleure. Tous les exemples sont pseudocodes:

1ère et meilleure solution

Vincent Ramdhanie a présenté le modèle correct correct pour résoudre ce problème, appelé modèle stratégique .

Ce modèle crée un “processeur” séparé, dans ce cas, traite les messages en conséquence.

Mais je suis sûr que le GOF donne une bonne explication dans votre livre 🙂

2ème

Comme indiqué, le message peut ne pas être en mesure de se traiter lui-même, il est toujours utile de créer une interface pour le message ou une classe de base afin que vous puissiez créer une fonction de traitement générale pour un message et la surcharger pour des messages plus spécifiques.

Dans tous les cas, la surcharge est préférable à la création d’une méthode différente pour chaque type de message …

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

3ème

Si votre message pouvait se traiter vous-même, vous pourriez écrire une interface, puisque votre méthode de traitement dépend du message que vous avez reçu, il semble plus facile de l’insérer dans la classe de message …

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

vous l’implémentez ensuite dans toutes vos classes de message et vous les traitez:

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

dans votre liste, vous pouvez stocker n’importe quelle classe qui implémente cette interface …

D’après mon expérience en matière de traitement des messages, il est généralement évident que différents consommateurs de messages exigent le traitement de divers types de messages. J’ai trouvé le modèle Double Dispatch pour gérer cela bien. L’idée de base est d’enregistrer un ensemble de gestionnaires qui envoient les messages reçus au gestionnaire en vue de leur traitement en fonction du type spécifique (à l’aide de la surcharge de fonction). Les consommateurs ne s’inscrivent que pour les types spécifiques qu’ils souhaitent recevoir. Vous trouverez ci-dessous un diagramme de classes.

Diagramme de classes UML à double répartition

Le code ressemble à ceci:

IHandler

 public interface IHandler { } 

IMessageHandler

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

IMessage

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

Ensuite, vous avez juste un conteneur qui gère les gestionnaires et vous êtes tous ensemble. Une simple boucle for dans la liste des gestionnaires lorsqu’un message est reçu, et les gestionnaires reçoivent les messages où ils le souhaitent

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

Cette conception est issue d’une question que j’ai posée il y a quelque temps à propos de la gestion d’événements polymorphes

Une option consiste à avoir les messages avec leurs propres gestionnaires. En d’autres termes, créez une interface appelée IMessageProcessor qui spécifie une méthode processMessage (IMessage). Définissez ensuite une classe concrète qui implémente IMessageProcessor pour chaque type de message.

Chaque classe IMessage définira ensuite son propre processeur.

Lorsque vous recevez un object de message, vous ferez quelque chose comme ceci:

 message.processor.processMessage(); 

Pour mon petit cadre de messagerie dans l’application Silverlight, j’utilise un modèle Mediator. C’est un type de bus / courtier de messagerie auquel les objects s’abonnent pour un type ou des types de message spécifiques. Ensuite, cet object médiateur (courtier / bus) décide qui recevra quel type de message.
Quelque chose comme:

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

Exemples de méthodes appelées:

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

ou publication (diffusion) de messages:

 Publish(new ChatMessage()); 

BaseMessage est une classe abstraite, dont tous mes messages héritent et ne font référence qu’à l’expéditeur et à un guide unique.

J’ai pris le sharepoint départ pour la construction de ma structure de messagerie à partir de MVVM Light Toolkit , vous pouvez jeter un coup d’œil à leur code source, ce n’est pas compliqué!

Si vous le souhaitez, je peux mettre un code c # pour cela quelque part?

Vous voudrez peut-être jeter un coup d’œil sur les modèles d’intégration d’entreprise de Gregor Hohpe et Bobby Woolf. Il a un bon catalogue de modèles pour le traitement des messages.

Un modèle de répartition pourrait bien fonctionner.

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

Il y a toutes sortes de variations sur ce thème. Ils auront tous un object répartiteur qui contient de nombreux gestionnaires différents. Vous devriez envisager un gestionnaire par défaut au cas où le répartiteur ne pourrait pas trouver un gestionnaire spécifique. Il y a beaucoup de liberté dans la manière dont vous choisissez d’envoyer les messages aux gestionnaires appropriés. J’arrive juste à envoyer en fonction du type, mais vous pouvez le rendre arbitrairement plus complexe. Peut-être que le répartiteur pourrait examiner le contenu du message pour découvrir le meilleur gestionnaire. Peut-être que le message contient une clé qui identifie un gestionnaire préféré. Je ne sais pas. Il y a beaucoup de possibilités ici.

Ajoutez une méthode ProcessMessage () à l’interface iMessage et laissez le message concret décider de manière polymorphe de la manière appropriée de se traiter.

Votre code devient alors

 newMessage.ProcessMessage(); 

Voici un bon article sur l’ utilisation du polymorphism au lieu des conditions .

Dans un scénario similaire, j’ai un serveur qui reçoit beaucoup de messages différents de plusieurs clients.

Tous les messages sont envoyés en série et commencent par un identifiant de type de message. J’ai ensuite une déclaration switch en regardant l’identifiant. Les messages sont ensuite désérialisés (sur des objects très différents) et traités comme il convient.

On pourrait faire la même chose en passant des objects qui implémentent une interface qui inclut un moyen d’indiquer le type de message.

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

Je sais que c’est un fil plus ancien, avec plusieurs très bonnes réponses au fil des ans.

Cependant, en 2018, j’utiliserais un logiciel tel que MediatR de Jimmy Bogard ( https://github.com/jbogard/MediatR ).

Il permet le découplage de l’envoi et du traitement des messages avec des modèles tels que demande / réponse, commande / requête, unidirectionnel, pub / sub, asynchrone, envoi polymorphe, etc.