2009-08-10 12 views
9

Tengo la siguiente situación en la que una clase de cliente ejecuta un comportamiento diferente según el tipo de mensaje que recibe. Me pregunto si existe una forma mejor de hacerlo ya que no me gustan las sentencias instanceof y if.Evitar instanciaof al verificar un tipo de mensaje

Una cosa que pensé hacer fue sacar los métodos de la clase del cliente y ponerlos en los mensajes. Pondría un método como process() en la interfaz de IMessage y luego pondría el comportamiento específico del mensaje en cada uno de los tipos de mensajes concretos. Esto simplificaría al cliente porque simplemente llamaría a message.process() en lugar de verificar tipos. Sin embargo, el único problema con esto es que el comportamiento contenido en los condicionales tiene que ver con operaciones en datos contenidos dentro de la clase Cliente. Por lo tanto, si implementé un método de proceso en las clases concretas de mensajes, tendría que pasarlo al cliente y no sé si esto realmente tiene sentido.

public class Client { 
    messageReceived(IMessage message) { 
     if(message instanceof concreteMessageA) { 
      concreteMessageA msg = (concreteMessageA)message; 
      //do concreteMessageA operations 
     } 
    } 
     if (message instanceof concreteMessageB) { 
      concreteMessageb msg = (concreteMessageB)message; 
      //do concreteMessageB operations 
     } 
} 
+0

Basado en el hecho de que está utilizando JMS, ¿qué es IMessage? JMS tiene tipos de mensajes predefinidos, por lo que no puede definir el suyo, a menos que tenga un intermediario que los convierta, lo cual es más que un dolor de cabeza a menos que intente abstraer el JMS. – Robin

Respuesta

7

La manera simple de evitar la instancia de prueba es despachar polimórficamente; p.ej.

public class Client { 
    void messageReceived(IMessage message) { 
     message.doOperations(this); 
    } 
} 

donde cada clase de mensaje define un método apropiado doOperations(Client client).

EDIT: segunda solución que mejor se adapta a los requisitos.

Una alternativa que sustituye a una secuencia de pruebas '' instanceof con una sentencia switch es:

clase
public class Client { 
    void messageReceived(IMessage message) { 
     switch (message.getMessageType()) { 
     case TYPE_A: 
      // process type A 
      break; 
     case TYPE_B: 
      ... 
     } 
    } 
} 

Cada iMessage necesita definir un método int getMessageType() para devolver el código apropiado. Los enums funcionan igual de bien, y son más elegantes, IMO.

+1

El segundo enfoque aún puede requerir un lanzamiento si los diferentes * procesos * bloques necesarios para acceder a las propiedades solo disponibles en los subtipos de mensajes –

+0

No me gusta la idea de que el cliente defina cómo debe procesarse el mensaje. ¿Qué sucede si necesita cambiar doOperations? ¿Cómo puede obtener este código actualizado para todos los clientes? Supongo que un patrón de visitante sería mejor en este caso. Para que el visitante del cliente sea aceptado por el mensaje y pueda escabullirse para hacer lo que quiera. – pjp

+0

@pgp: erm ... Creo que estás malinterpretando el problema. La forma en que se plantea el problema, la lógica del procesamiento de los mensajes >> es << específica para la clase Cliente. –

4

Una opción aquí es una cadena de manipuladores . Tiene una cadena de manipuladores, cada uno de los cuales puede manejar un mensaje (si corresponde) y luego consume, lo que significa que no se pasará más abajo en la cadena. En primer lugar se define la interfaz de Handler:

public interface Handler { 
    void handle(IMessage msg); 
} 

Y entonces la lógica de la cadena manejador se parece a:

List<Handler> handlers = //... 
for (Handler h : handlers) { 
    if (!e.isConsumed()) h.handle(e); 
} 

A continuación, cada controlador puede decidir para manejar/consumen un evento:

public class MessageAHandler implements Handler { 
    public void handle(IMessage msg) { 
     if (msg instanceof MessageA) { 
      //process message 
      //consume event 
      msg.consume(); 
     } 
    } 
} 

De Por supuesto, esto no elimina el instanceof s, pero sí significa que no tiene un gran bloque if-elseif-else-if-instanceof, que puede ser ilegible

+0

La legibilidad es una cuestión de opinión/gusto. La creación de nuevas clases de controlador para cada tipo de mensaje significa que debe buscar en otro lugar de nuevo (aparte de la clase Cliente y las clases IMessage) para encontrar la lógica. No lo llamaría una "gran victoria de legibilidad". –

+1

¡Ni yo tampoco! Todo depende de si odias los grandes bloques 'if-else-instanceof'. Suena como si el OP lo hiciera, así que lo sugerí. La cadena de controladores * es * útil, porque es más flexible que if-else. Un controlador no tiene que consumir un evento, por lo que puede ser procesado por más de un controlador –

1

¿Qué tipo de sistema de mensajes está utilizando?

Muchos tienen opciones para agregar un filtro a los controladores en función del encabezado o contenido del mensaje. Si esto es compatible, simplemente crea un controlador con un filtro basado en el tipo de mensaje, luego su código es bueno y limpio sin la necesidad de una instancia de tipo de comprobación (ya que el sistema de mensajería ya lo comprobó por usted).

Sé que puede hacer esto en JMS o en el servicio de eventos OSGi.

Dado que está utilizando JMS, básicamente puede hacer lo siguiente para registrar a sus oyentes. Esto creará un oyente para cada tipo de mensaje único.

String filterMsg1 = "JMSType='messageType1'"; 
    String filterMsg2 = "JMSType='messageType2'"; 

    // Create a receiver using this filter 
    Receiver receiverType1 = session.createReceiver(queue, filterMsg1); 
    Receiver receiverType2 = session.createReceiver(queue, filterMsg2); 

    receiverType1.setMessageHandler(messageType1Handler); 
    receiverType2.setMessageHandler(messageType2Handler); 

Ahora cada manejador recibirá el tipo de mensaje específico sólo (sin instanceof o si-entonces), asumiendo por supuesto que el emisor establece el tipo a través de llamadas a setJMSType() en el mensaje de salida.

Este método está integrado en el mensaje, pero también puede crear su propia propiedad de encabezado y filtrar también.

+0

Estoy usando JMS y tendré que analizar su sugerencia. – volker238

+0

@volker238 - Se llama selector y puede usarlo para filtrar según la información del encabezado del mensaje. – Robin

+0

@ volker238 - Agregué un fragmento de código basado en el hecho de que está utilizando JMS. Suponiendo que pueda establecer las propiedades del encabezado, el selector es absolutamente el camino a seguir, ya que permite que la implementación JMS haga todo el enrutamiento por usted. No necesita un código de manejo desagradable. – Robin

0
//Message.java 

abstract class Message{ 
    public abstract void doOperations(); 
} 

//MessageA.java 

class MessageA extends Message{ 
    public void doOperations(){ 
     //do concreteMessageA operations ; 
    } 
} 

    //MessageB.java 

class MessageB extends Message { 
    public void doOperations(){ 
     //do concreteMessageB operations 
    } 
} 

//MessageExample.java 

class MessageExample{ 
    public static void main(String[] args) { 
     doSmth(new MessageA()); 
    } 

    public static void doSmth(Message message) { 
     message.doOperations() ;  

    } 
} 
0

Una solución de Java 8 que utiliza el despacho doble. No elimina completamente instanceof, pero solo requiere una comprobación por mensaje en lugar de una cadena if-elseif.

public interface Message extends Consumer<Consumer<Message>> {}; 

public interface MessageA extends Message { 

    @Override 
    default void accept(Consumer<Message> consumer) { 
     if(consumer instanceof MessageAReceiver){ 
     ((MessageAReceiver)consumer).accept(this); 
     } else { 
     Message.super.accept(this); 
     } 
    } 
} 

public interface MessageAReceiver extends Consumer<Message>{ 
    void accept(MessageA message); 
} 
Cuestiones relacionadas