2009-08-10 14 views
9

Ich habe die folgende Situation, in der eine Client-Klasse unterschiedliche Verhalten auf der Grundlage der Art der Nachricht führt, die es empfängt. Ich frage mich, ob es eine bessere Möglichkeit gibt, dies zu tun, da ich die instanceof- und die if-Anweisungen nicht mag.Vermeidung von instanceof beim Überprüfen eines Nachrichtentyps

Eine Sache, die ich mir vorgenommen habe, war, die Methoden aus der Clientklasse herauszuziehen und sie in die Nachrichten einzufügen. Ich würde eine Methode wie process() in die IMessage-Schnittstelle einfügen und dann das nachrichtenspezifische Verhalten in jeden der konkreten Nachrichtentypen einfügen. Dies würde den Client einfach machen, da er nur message.process() aufrufen würde, anstatt Typen zu prüfen. Das einzige Problem dabei ist jedoch, dass das in den Bedingungen enthaltene Verhalten mit Operationen auf Daten in der Klasse Client zu tun hat. Wenn ich also eine Prozessmethode in den konkreten Nachrichtenklassen implementieren würde, müsste ich sie dem Client übergeben und ich weiß nicht, ob das auch wirklich Sinn macht.

+0

Basierend auf der Tatsache, dass Sie JMS verwenden, was ist IMessage? JMS verfügt über vordefinierte Nachrichtentypen, sodass Sie keine eigenen Nachrichtentypen definieren können, es sei denn, Sie verfügen über einen Intermediär, der sie konvertiert. Dies ist jedoch nur ein kleiner Fehler, es sei denn, Sie versuchen, JMS zu abstrahieren. – Robin

Antwort

7

Der einfache Weg zur Vermeidung von instanceof testing ist polymorph zu versenden; z.B.

wobei jede Nachrichtenklasse eine geeignete doOperations(Client client) Methode definiert.

EDIT: zweite Lösung, die besser entspricht den Anforderungen.

Eine Alternative, die eine Folge von ‚Instanceof‘ Tests mit einer Switch-Anweisung ersetzt ist:

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

Jeder IMessage Klasse benötigt eine int getMessageType() Methode definieren den entsprechenden Code zurückzukehren. Enums funktionieren genauso gut und sind eleganter, IMO.

+1

Der zweite Ansatz erfordert möglicherweise noch einen Cast, wenn die verschiedenen * Prozess * Blöcke für den Zugriff auf Eigenschaften nur für Nachrichten Subtypen verfügbar sind. –

+0

Ich mag nicht die Idee des Clients zu definieren, wie die Nachricht verarbeitet werden soll. Was passiert, wenn Sie doOperations ändern müssen - wie erhalten Sie diesen aktualisierten Code für alle Clients? Ich denke, ein Besuchermuster wäre in diesem Fall besser. Damit der Kundenbesucher von der Nachricht akzeptiert wird und sich schleichen kann, um zu tun, was er will. – pjp

+0

@pgp: ähm ... Ich glaube, du verstehst das Problem falsch. Die Art, wie das Problem gestellt wird, ist die Logik der Verarbeitung der Nachrichten >> spezifisch für die Client-Klasse. –

4

Eine Option ist hier eine Handler-Kette. Sie haben eine Kette von Handlern, von denen jeder eine Nachricht verarbeiten kann (falls zutreffend) und dann verbrauchen es, was bedeutet, es wird nicht weiter unten in der Kette weitergegeben werden. Zunächst definieren Sie die Handler Schnittstelle:

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

Und dann die Behandlungskette Logik wie folgt aussieht:

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

Dann kann jeder Handler entscheiden/handhaben consume ein Ereignis:

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

Of natürlich, das wird nicht die Beseitigung der instanceof s - aber es bedeutet, Sie haben keine großen if-elseif-else-if-instanceof Block, der nicht lesbar sein kann

+0

Lesbarkeit ist eine Frage der Meinung/des Geschmacks. Das Erstellen neuer Handler-Klassen für jeden Nachrichtentyp bedeutet, dass Sie sich (abgesehen von der Client-Klasse und den IMessage-Klassen) erneut an einen anderen Ort begeben müssen, um die Logik zu finden. Ich würde das nicht als "großen Lesbarkeitsgewinn" bezeichnen. –

+1

Weder würde ich! Alles hängt davon ab, ob Sie große 'if-else-instanceof'-Blöcke hassen. Es klingt so, als ob das OP es tut, also habe ich es vorgeschlagen. Die Handler-Kette * ist aber * nützlich, weil sie flexibler ist als if-else. Ein Handler muss kein Ereignis konsumieren, daher kann es von mehr als einem Handler verarbeitet werden. –

1

Welche Art von Nachrichtensystem verwenden Sie?

Viele haben Optionen zum Hinzufügen eines Filters zu den Handlern basierend auf Nachrichtenheader oder Inhalt. Wenn dies unterstützt wird, können Sie einfach einen Handler mit einem Filter erstellen, basierend auf Nachrichtentyp, dann ist der Code schön und sauber, ohne die Notwendigkeit für instanceof oder Typüberprüfung (da das Messaging-System bereits für Sie überprüft).

Ich weiß, dass Sie dies in JMS oder dem OSGi-Ereignisdienst tun können.

Da Sie JMS verwenden, können Sie grundsätzlich Ihre Listener registrieren. Dies erstellt einen Listener für jeden eindeutigen Nachrichtentyp.

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

Jetzt wird jeder Handler des spezifischen Nachrichtentyp nur (kein instanceof oder if-then) erhält, natürlich unter der Annahme, dass der Absender die Art über Anrufe zu setJMSType() auf der Ansage setzt.

Diese Methode ist in Nachricht integriert, aber Sie können natürlich Ihre eigene Header-Eigenschaft erstellen und stattdessen filtern.

+0

Ich benutze JMS und muss mir Ihren Vorschlag ansehen. – volker238

+0

@ volker238 - Es wird als Selektor bezeichnet und Sie können es verwenden, um basierend auf Nachrichtenheaderinformationen zu filtern. – Robin

+0

@ volker238 - Ich habe ein Code-Snippet hinzugefügt, basierend auf der Tatsache, dass Sie JMS verwenden. Unter der Annahme, dass Sie Header-Eigenschaften festlegen können, ist der Selektor der absolut beste Weg, da er es der JMS-Implementierung ermöglicht, das gesamte Routing für Sie durchzuführen. Keine Notwendigkeit für hässliche Handhabung Code, den Sie selbst rollen. – 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

Eine Java 8-Lösung, die doppelten Versand verwendet. Wird instanceof nicht vollständig los, erfordert aber nur eine Überprüfung pro Nachricht anstelle einer if-elseif-Kette.

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