2009-09-25 15 views
26

Tengo el GOF sentado en mi escritorio aquí y sé que debe haber algún tipo de patrón de diseño que resuelva el problema que tengo, pero no puedo entenderlo .Patrón de diseño para manejar múltiples tipos de mensajes

Por razones de simplicidad, he cambiado el nombre de algunas de las interfaces que estoy usando.

Así que aquí está el problema, en un lado del cable, tengo varios servidores que envían diferentes tipos de mensajes. En el otro lado del cable, tengo un cliente que necesita poder manejar todos los diferentes tipos de mensajes.

Todos los mensajes implementan la misma interfaz común IMessage. Mi problema es que cuando el cliente recibe un nuevo IMessage, ¿cómo sabe qué tipo de IMessage recibió?

Supuse que podría hacer algo como lo siguiente, pero esto simplemente se siente horrible.

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

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

El segundo pensamiento, es añadir una propiedad a iMessage llama MessageTypeID, pero eso requeriría mí escribir algo como lo siguiente, que también se siente muy mal.

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

Sé que este problema general se ha abordado un millón de veces, por lo que no tiene que haber una manera mejor de resolver el problema de tener un método que toma una interfaz como parámetro, pero necesita diferentes de control de flujo basado en qué clase ha implementado esa interfaz.

+0

añadí una una respuesta similar a la que usted aceptó que funciona a partir de la sugerencia de @Brian Gideon, que creo que encontrará que funciona bien con Double Dispatching. – SwDevMan81

Respuesta

17

Se puede crear manejadores de mensajes separados para cada tipo de mensaje, e ingenuamente pasar el mensaje a cada controlador disponible hasta que encuentra uno que puede manejarlo Al igual que en la cadena de patrón de responsabilidad:

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

También podría implementar esto como un mapa, con el nombre de la clase o tipo de mensaje mensaje de Identificación como clave y la instancia de manejador apropiado como el valor.

Otros han sugerido tener el objeto de mensaje "manejar", pero eso no me parece correcto. Parece que sería mejor separar el manejo del mensaje del mensaje mismo.

Algunas otras cosas que me gustan de él:

  1. Puede inyectar los controladores de mensajes a través de la primavera o lo que usted quiera en lugar de crearlos en el constructor, por lo que este mismo comprobable.

  2. Puede introducir un comportamiento similar al de un tema en el que tenga varios controladores para un solo mensaje simplemente eliminando el "corte" del bucle ProcessMessage.

  3. Al separar el mensaje del manejador, puede tener diferentes controladores para el mismo mensaje a diferentes destinos (por ejemplo, varias clases de MessageProcessor que manejan los mismos mensajes de otra forma)

+0

+1 Creo que esta es una buena idea. –

+1

Todas las soluciones fueron geniales, pero esta se ajusta mejor a las necesidades de esta aplicación. un TradeMessage no sería procesado de la misma manera por todas las aplicaciones, por lo que no tenía sentido poner la lógica de procesamiento en el objeto TradeMessage. –

+1

'if (! Msg es SomeOtherMessage)' debe ser 'if (! (Msg es SomeOtherMessage))'. 'OrderMessageHandler' y' SomeOtherMessageHandler' deberían implementar 'IMessageHandler' para poder agregarlos a los manejadores' List . Además 'bool messageWasHandled' pierde un'; '. (¿Lo has probado realmente?) –

14

Un par de soluciones son aplicables para esto, primero es la mejor solución, la última es la mejor. Todos los ejemplos son pseudocódigo:

primera y mejor solución

Vicente Ramdhanie introdujo el patrón correcto real para resolver este problema, que se llama el strategy pattern.

Este patrón crea un "procesador" separado, en este caso para procesar los mensajes en consecuencia.

pero estoy bastante seguro de una buena explicación se da en su libro por el Gobierno de Francia :)

segundo

Como se ha comentado, el mensaje no puede ser capaz de procesar en sí, sigue siendo útil para crear una interfaz para el mensaje, o una clase base, para que pueda hacer una función de procesamiento general para un mensaje y sobrecargarlo para otros más específicos.

sobrecarga en cualquier caso es mejor que la creación de un método diferente para cada tipo de mensaje ...

public class Message {} 
public class TradeMessage extends Message {} 

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

    public function process(TradeMessage msg) { 
     //logic 
    } 
} 

tercera

Si su mensaje podría procesar en sí se podría escribir una interfaz, ya su método de proceso depende de qué mensaje recibió, parece más fácil colocarlo dentro de la clase de mensaje ...

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

que luego implementar esto en todas sus clases de mensajes y Proccess ellos:

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

en su lista usted puede almacenar cualquier clase que implementa la interfaz ...

+1

Supuse que su función ProcessXMessage() dependía de un código fuera de su objeto, por lo que su objeto no podía procesarse internamente. – JustLoren

+0

Según tengo entendido el problema, es posible que el mensaje no tenga ningún comportamiento asociado. Así que creo que un procesador de mensajes por separado con ese comportamiento * sobrecargado * sería la mejor solución. –

+0

@bruno conde: Al igual que la publicación de Vincent Ramdhanie - Estoy de acuerdo, esta es probablemente la mejor implementación general. – JustLoren

1

Añadir un ProcessMessage () a la interfaz de iMessage y deje que el mensaje concreto decida polimórficamente la forma correcta de procesarse.

Su código se convierte entonces en

newMessage.ProcessMessage(); 

Aquí es un buen artículo sobre using polymorphism instead of conditionals.

1

En un escenario similar, tengo un servidor que recibe muchos mensajes diferentes de varios clientes.

Todos los mensajes se envían en serie y comienzan con un identificador de tipo de mensaje. Luego tengo una declaración de cambio mirando el identificador. Los mensajes se deserializan (a objetos muy diferentes) y se procesan según corresponda.

Una cosa similar podría hacerse haciendo pasar objetos que implementan una interfaz que incluye una forma de indicar el tipo de mensaje.

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

He reemplazado el uso de esto con un diccionario de comandos que implementan una interfaz común y están indexados por tipo. Mucho menos código. Combata declaraciones de interruptor grandes con este patrón de comando –

+0

Hola, ¿cómo implementa la serialización? ¿Serializas cada mensaje por separado? ¿Puedes por favor dar ejemplo? – ransh

4

Una opción es que los mensajes lleguen con sus propios controladores. Es decir, crea una interfaz llamada IMessageProcessor que especifica un método processMessage (IMessage). A continuación, defina la clase concreta que implementa IMessageProcessor para cada tipo de mensaje.

Cada clase de IMessage definirá su propio procesador.

Cuando receieve un objeto de mensaje que va a hacer algo como esto:

message.processor.processMessage(); 
2

Es posible que desee tomar una mira a través de Enterprise Integration Patterns por Gregor Hohpe y Bobby Woolf. Tiene un buen catálogo de patrones para el procesamiento de mensajes.

3

Para mi pequeño marco de mensajes dentro de la aplicación Silverlight, estoy usando el patrón Mediator. Es una especie de bus/broker de mensajería, a la cual los objetos se suscriben para un tipo o tipo de mensaje específico. Entonces este objeto Mediador (intermediario/autobús) está decidiendo quién recibirá qué tipo de mensajes.
calle detrás como:

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

métodos de ejemplo que se llaman:

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

o publicación (emisión) de mensajes:

Publish(new ChatMessage()); 

mensaje base es la clase abstracta, que todos mis mensajes hereda y acaba de hacer referencia al remitente y a algún Guid único.

Tomé el punto de partida para construir mi marco de mensajería desde MVVM Light Toolkit, puedes echarle un vistazo a su código fuente, ¡no es complicado!

Si quiere, puedo poner código C# para esto en alguna parte?

+0

Me encantaría ver el código para esto si pudieras compartir. –

+0

@ByteBlast esta es una buena biblioteca que implementa el patrón Mediator: https://github.com/mhinze/ShortBus –

2

Un patrón de envío puede funcionar bien.

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

Hay todo tipo de variaciones en este tema. Todos ellos tendrán un objeto despachador que contiene muchos manejadores diferentes. Debería considerar un controlador predeterminado en caso de que el despachador no pueda encontrar un controlador específico. Hay mucha libertad en cómo elige enviar los mensajes a los controladores apropiados. Por casualidad, despaché según el tipo, pero podrías hacerlo arbitrariamente más complejo. Tal vez el despachador podría examinar el contenido del mensaje para descubrir el mejor controlador. Tal vez el mensaje lleva consigo una clave que identifica a un controlador preferido. No lo sé. Hay muchas posibilidades aquí.

+0

Me gusta esta idea. Pero hay dos cosas que no me encantan de la implementación: en primer lugar, si un controlador está registrado para el tipo ** A ** usando _ContainsKey (Type) _ no se podrán encontrar objetos de cualquier clase _derived_ de ** A **. En segundo lugar, un diccionario solo permite un manejador por tipo de mensaje. – NVRAM

+0

@NVRAM: Muy buenos puntos. –

+0

@NVRAM: creo que puede sortear el primer punto agregando los tipos derivados con el mismo controlador. Obviamente no es ideal, pero es una solución alternativa. El segundo punto es válido. – SwDevMan81

7

Según mi experiencia con el manejo de mensajes, generalmente es el caso de que diferentes consumidores de mensajes requieran manejar una variedad de tipos de mensajes. Encontré el patrón Double Dispatch para manejar esto muy bien. La idea básica es registrar un conjunto de manejadores que envían los mensajes recibidos al controlador para su procesamiento en función del tipo específico (utilizando la función de sobrecarga). Los consumidores solo se registran para los tipos específicos que desean recibir. Debajo hay un diagrama de clase.

Double Dispatch UML Class Diagram

El código es el siguiente:

iHandler

public interface IHandler 
{ 
} 

IMessageHandler

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

I-Mensaje

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

A continuación, sólo tienen un recipiente que gestiona los manipuladores y que está todo listo. Una simple para recorrer la lista de controladores cuando un mensaje recibido, y los manipuladores de recibir los mensajes en los que los quieren

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

Este diseño surgió de una pregunta que hice un tiempo atrás sobre Polymorphic Event Handling

+0

¿Qué quiere decir con _container_ exactamente? –

+0

@ByteBlast - Por contenedor me refiero a una clase que gestiona la colección de 'mListOfRegisteredHandlers'. Así que, básicamente, una clase que tiene funciones de controlador add/remove y tal vez tenga una función auxiliar llamada 'DispatchMessage (IMessage msg)' que recorrerá la lista de 'mListOfRegisteredHandlers' y llamará a' msg.Dispatch (handler) '(el último código fragmento en mi respuesta). – SwDevMan81

+0

¿Cómo llamarías este tipo de contenedor? Fuera de interés, gracias. –

Cuestiones relacionadas