2013-12-11 10 views
6

Possiedo un client Java che desidera comunicare con un dispositivo tramite messaggi tramite comunicazione seriale. Il client dovrebbe essere in grado di utilizzare un'API pulita, estrapolando i brutti dettagli della comunicazione seriale. Il client può inviare molti tipi di messaggi attraverso quell'API e ottiene le risposte. Sto cercando un consiglio su quale modo è meglio implementare questa API.Come progettare un API di tipo safe message in Java?

Per semplicità, diciamo che abbiamo solo due tipi di messaggi: HelloMessage che innesca un HelloResponse e InitMessage che innesca una InitResponse (in realtà, ci sono molti di più)

Progettare l'API (vale a dire, l'astrazione Java il dispositivo) ho potuto avere:

Un metodo per tipo di messaggio:

public class DeviceAPI { 
    public HelloResponse sendHello(HelloMessage){...} 
    public InitResponse sendInit(InitMessage){...} 
    ... and many more message types .... 

Questo è ben sICURO tipo. (Potrebbe anche essere più volte lo stesso metodo send(), sovraccarico, ma è quasi lo stesso). Ma è molto esplicito e non molto flessibile - non possiamo aggiungere messaggi senza modificare l'API.

mi potrebbe anche avere un unico metodo di invio, che prende tutti i tipi di messaggi:

class HelloMessage implements Message 
class HelloResponse implements Response 
... 
public class DeviceAPI { 
    public Response send(Message msg){ 
    if(msg instanceof HelloMessage){ 
     // do the sending, get the response 
     return theHelloResponse 
    } else if(msg instanceof ... 

Ciò semplifica l'API (solo metodo) e permette di tipi di messaggio aggiunta di ulteriori successiva senza cambiare l'API. Allo stesso tempo, richiede che il Cliente verifichi il tipo di risposta e lo trasmetta al tipo giusto.

Codice cliente:

DeviceAPI api = new DeviceAPI(); 
HelloMessage msg = new HelloMessage(); 
Response rsp = api.send(msg); 
if(rsp instanceOf HelloResponse){ 
    HelloResponse hrsp = (HelloResponse)rsp; 
    ... do stuff ... 

Questo è brutto a mio parere.

Che cosa mi consiglia? Ci sono altri approcci che danno risultati più puliti?

Riferimenti benvenuti! Come hanno fatto gli altri a risolvere questo?

+0

Un tipo parametrizzato con enum potrebbe essere una possibilità? O questo non è possibile (affatto). – skiwi

+0

Controlla il tipo di "Risposta" e lanci poi HelloMessage. Sembra grottesco. Ad ogni modo, è possibile nascondere le operazioni "instanceOf" e "casting" nelle classi di implementazione. – arjacsoh

+0

Non penso che dover controllare i tipi sia brutto, è probabile che lo implementerei anche io. Se non vuoi questo, penso che il punto giusto da riconsiderare sia il tuo modello; cosa rende necessario che queste risposte siano oggetti diversi? Non possono essere generici, per esempio? –

risposta

1

Qui è un modo per farlo in modo utilizzano farmaci generici type-safe (ed estensibile):

public interface MessageType {  
    public static final class HELLO implements MessageType {}; 
} 

public interface Message<T extends MessageType> { 
    Class<T> getTypeClass(); 
} 

public interface Response<T extends MessageType> { 
} 

public class HelloMessage implements Message<MessageType.HELLO> { 

    private final String name; 

    public HelloMessage(final String name) { 
     this.name = name; 
    } 

    @Override 
    public Class<MessageType.HELLO> getTypeClass() { 
     return MessageType.HELLO.class; 
    } 

    public String getName() { 
     return name; 
    } 

} 

public class HelloResponse implements Response<MessageType.HELLO> { 

    private final String name; 

    public HelloResponse(final String name) { 
     this.name = name; 
    } 

    public String getGreeting() { 
     return "hello " + name; 
    } 

} 

public interface MessageHandler<T extends MessageType, M extends Message<T>, R extends Response<T>> { 
    R handle(M message); 
} 

public class HelloMessageHandler 
    implements MessageHandler<MessageType.HELLO, HelloMessage, HelloResponse> { 
    @Override 
    public HelloResponse handle(final HelloMessage message) { 
     return new HelloResponse(message.getName()); 
    } 
} 

import java.util.HashMap; 
import java.util.Map; 

public class Device { 

    @SuppressWarnings("rawtypes") 
    private final Map<Class<? extends MessageType>, MessageHandler> handlers = 
     new HashMap<Class<? extends MessageType>, MessageHandler>(); 

    public <T extends MessageType, M extends Message<T>, R extends Response<T>> 
     void registerHandler(
      final Class<T> messageTypeCls, final MessageHandler<T, M, R> handler) { 
     handlers.put(messageTypeCls, handler); 
    } 

    @SuppressWarnings("unchecked") 
    private <T extends MessageType, M extends Message<T>, R extends Response<T>> 
     MessageHandler<T, M, R> getHandler(final Class<T> messageTypeCls) { 
     return handlers.get(messageTypeCls); 
    } 


    public <T extends MessageType, M extends Message<T>, R extends Response<T>> 
     R send(final M message) { 
     MessageHandler<T, M, R> handler = getHandler(message.getTypeClass()); 
     R resposnse = handler.handle(message); 
     return resposnse; 
    } 

} 

public class Main { 
    public static void main(final String[] args) { 
     Device device = new Device(); 
     HelloMessageHandler helloMessageHandler = new HelloMessageHandler(); 
     device.registerHandler(MessageType.HELLO.class, helloMessageHandler); 

     HelloMessage helloMessage = new HelloMessage("abhinav"); 
     HelloResponse helloResponse = device.send(helloMessage); 
     System.out.println(helloResponse.getGreeting()); 
    } 
} 

per aggiungere il supporto per un nuovo tipo di messaggio, implementare MessageType interfaccia per creare un nuovo tipo di messaggio, implementare Message, Response e MessageHandler interfacce per la nuova MessageType di classe e registrare il gestore per il nuovo tipo di messaggio da chiamando Device.registerHandler.

+0

Soluzione molto pulita, grazie! (molto simile a quello di Skiwi). Dopo un po 'di modifiche ho anche potuto rimuovere il metodo getTypeClass() dal messaggio. È possibile accedere a quelle stesse informazioni con message.getClass(). GetGenericInterfaces() [0] .getActualTypeArguments() (richiede un certo cast di ParameterizedType da qualche parte) – Philipp

2

Ho un esempio completamente funzionante ora di ciò che si vuole:

per definire i tipi di messaggi:

public interface MessageType { 
    public static class INIT implements MessageType { } 
    public static class HELLO implements MessageType { } 
} 

Base Message e Response classi:

public class Message<T extends MessageType> { 

} 

public class Response<T extends MessageType> { 

} 

Creare messaggi init personalizzati e le risposte:

public class InitMessage extends Message<MessageType.INIT> { 
    public InitMessage() { 
     super(); 
    } 

    public String getInit() { 
     return "init"; 
    } 
} 

public class InitResponse extends Response<MessageType.INIT> { 
    public InitResponse() { 
     super(); 
    } 

    public String getInit() { 
     return "init"; 
    } 
} 

creare messaggi e risposte personalizzate ciao:

public class HelloMessage extends Message<MessageType.HELLO> { 
    public HelloMessage() { 
     super(); 
    } 

    public String getHello() { 
     return "hello"; 
    } 
} 

public class HelloResponse extends Response<MessageType.HELLO> { 
    public HelloResponse() { 
     super(); 
    } 

    public String getHello() { 
     return "hello"; 
    } 
} 

Il DeviceAPI:

public class DeviceAPI { 
    public <T extends MessageType, R extends Response<T>, M extends Message<T>> R send(M message) { 
     if (message instanceof InitMessage) { 
      InitMessage initMessage = (InitMessage)message; 
      System.out.println("api: " + initMessage.getInit()); 
      return (R)(new InitResponse()); 
     } 
     else if (message instanceof HelloMessage) { 
      HelloMessage helloMessage = (HelloMessage)message; 
      System.out.println("api: " + helloMessage.getHello()); 
      return (R)(new HelloResponse()); 
     } 
     else { 
      throw new IllegalArgumentException(); 
     } 
    } 
} 

Nota che è necessario un albero di tipo instanceof, ma è necessario per gestire il tipo di messaggio.

E un esempio di lavoro:

public static void main(String[] args) { 
    DeviceAPI api = new DeviceAPI(); 

    InitMessage initMsg = new InitMessage(); 
    InitResponse initResponse = api.send(initMsg); 
    System.out.println("client: " + initResponse.getInit()); 

    HelloMessage helloMsg = new HelloMessage(); 
    HelloResponse helloResponse = api.send(helloMsg); 
    System.out.println("client: " + helloResponse.getHello()); 
} 

uscita:

api: init 
client: init 
api: hello 
client: hello 

UPDATE: esempio Aggiunto su come ricevere input dai messaggi il cliente vuole inviare.

+0

Ancora lavorando su una soluzione migliore. – skiwi

+0

Grazie, è interessante. Supponiamo che le risposte contengano informazioni diverse, come potrei accedervi? – Philipp

+0

@Philipp Lavorando su questo al momento, si vuole essere in grado di usare qualcosa come 'InitMessage' invece di' Message '. – skiwi

0

Io non credo che questo è brutto affatto:

if(rsp instanceOf HelloResponse){ 
    HelloResponse hrsp = (HelloResponse)rsp; 
    ... 
else if ... 

fino a quando non si dispone di come 100 risposte diverse. Puoi incapsulare molti tipi di risposte in una, a seconda dei dati che hanno. Per esempio:

class GenericResponse{ 
    private String message; 
    private int responseType; 
    ... 
} 

ho sviluppato alcuni giochi multiplayer e questo è un buon modo per farlo. Nel caso in cui ci siano troppi diversi tipi di messaggi, è possibile utilizzare solo tipi di java generici, come nell'esempio di skiwi sopra.

Speranza che aiuta

+0

Mi è stato insegnato quell'istanzaSe le scale sono odore di codice. Ecco perché sto cercando un approccio OO in più. Forse con un modello di visitatore? – Philipp

1

Si potrebbe avere un sistema di gestori di messaggi, e la vostra DeviceAPI può scegliere quale gestore è adatto per il messaggio in arrivo; e delegare al gestore di messaggi appropriato:

class DeviceAPI { 

private List<Handler> msgHandlers = new ArrayList<Handler>(); 

public DeviceAPI(){ 
    msgHandlers.add(new HelloHandler()); 
      //Your other message handlers can be added 
} 

public Response send(Message msg) throws Exception{ 
    for (Handler handler : msgHandlers) { 
     if (handler.canHandle(msg)){ 
      return handler.handle(msg); 
     } 
    } 
    throw new Exception("No message handler defined for " + msg); 
} 
} 

Il HelloHandler sarà simile:

interface Handler<T extends Message, U extends Response> { 
    boolean canHandle(Message message); 
    U handle(T message); 
} 


class HelloHandler implements Handler<HelloMessage, HelloResponse> { 

@Override 
public boolean canHandle(Message message) { 
    return message instanceof HelloMessage; 
} 

@Override 
public HelloResponse handle(HelloMessage message) { 
    //Process your message 
    return null; 
} 
} 

Idem per gli altri messaggi.Sono sicuro che potresti renderlo più elegante, ma l'idea rimane la stessa: non c'è un metodo mostro con ifs; usa invece il polimorfismo.

+0

Mi piace l'idea: è più un approccio di callback. – Philipp

Problemi correlati