web-dev-qa-db-fra.com

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 mec, je ne peux pas le comprendre.

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

Alors voici 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 deuxième pensée, est d’ajouter 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 horrible.

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 flux différent en fonction de la classe qui a implémentée cette interface. 

29
Jonathan Beerhalter

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

Vous pouvez également l'implémenter en tant que mappe, avec le nom de la classe de message ou l'id du 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'objet de message se "manipule" lui-même, mais cela ne me semble pas juste. 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).

20
Eric Petroelje

Quelques solutions s’appliquent à cela, la première est la meilleure solution, la dernière est la moins bonne. 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égie

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 :)

2nd

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<iMessage>();
foreach (iMessage message in list) {
    message.process();
}

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

15
NDM

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 doivent traiter 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. Ci-dessous, un diagramme de classes.

Double Dispatch UML Class Diagram

Le code ressemble à ceci:

IHandler

public interface IHandler
{
}

IMessageHandler

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

iMessage

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

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’avais posée il ya quelque temps à propos de Gestion des événements polymorphes

8
SwDevMan81

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 objet de message, vous ferez quelque chose comme ceci:

message.processor.processMessage();
5
Vincent Ramdhanie

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

SubscribeFor<ChatMessage>().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. Il suffit de faire référence à l'expéditeur et à un guide unique. 

J'ai pris le point de départ pour la construction de ma structure de messagerie depuis MVVM Light Toolkit , vous pouvez consulter leur code source, ce n’est pas compliqué!

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

3
Hrvoje Hudo

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

Il y a toutes sortes de variations sur ce thème. Ils auront tous un objet 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. Il se trouve que j’expédie par 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.

2
Brian Gideon

Vous voudrez peut-être jeter un coup d'œil sur 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.

2
Mike Two

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 utilisant le polymorphisme au lieu de conditionnels .

1
Ryan Michela

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 objets très différents) et traités comme il convient.

On pourrait faire la même chose en passant des objets 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;
        ...
    }
}
1
Matt Lacey

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 fournit un 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.

0
Thiago Silva