Domanda

Ho una domanda sul modo migliore di esporre un'interfaccia remota asincrona.

Le condizioni sono le seguenti:

  • Il protocollo è asincrono
  • Una terza parte può modificare i dati in qualsiasi momento
  • Il comando di andata e ritorno può essere significativo
  • Il modello dovrebbe essere adatto per l'interazione dell'interfaccia utente
  • Il protocollo supporta query su determinati oggetti, così come deve farlo il modello

Come mezzo per migliorare le mie competenze mancanti in quest'area (e rispolverare il mio Java in generale), ho avviato un progetto per creare un front-end basato su Eclipse xmms2 (descritto sotto).

Quindi, la domanda è;come dovrei esporre l'interfaccia remota come un modello di dati accurato (in questo caso, tenere traccia della gestione e della gestione degli eventi)?

Accolgo con favore qualsiasi cosa, dalle discussioni generiche all'eliminazione dei nomi dei pattern o agli esempi concreti e alle patch :)


Il mio obiettivo principale qui è conoscere questa classe di problemi in generale.Se il mio progetto può trarne vantaggio, bene, ma lo presento esclusivamente per avere qualcosa su cui avviare una discussione.

Ho implementato un'astrazione di protocollo che chiamo 'cliente' (per motivi legacy) che mi consente di accedere alle funzionalità più esposte utilizzando chiamate di metodo di cui sono soddisfatto anche se è tutt'altro che perfetto.

Le funzionalità fornite dal demone xmms2 sono cose come la ricerca di tracce, il recupero e la manipolazione dei metadati, la modifica dello stato di riproduzione, il caricamento di playlist e così via.

Sono nel bel mezzo dell'aggiornamento all'ultima versione stabile di xmms2 e ho pensato che avrei potuto anche correggere alcuni dei evidenti punti deboli della mia attuale implementazione.

Il mio piano è creare un'astrazione migliore sull'interfaccia del protocollo, che consenta un'interazione più naturale con il demone.Il corrente 'modello' l'implementazione è difficile da usare ed è francamente piuttosto brutta (per non parlare del codice dell'interfaccia utente che è davvero orribile).

Oggi ho il Brani interfaccia che posso usare per ottenere istanze di Traccia classi in base al loro ID.La ricerca viene eseguita tramite Collezioni (sfortunato scontro di namespace) che preferirei spostare su Tracks, credo.

Tutti i dati possono essere modificati da terzi in qualsiasi momento e ciò dovrebbe essere adeguatamente riflesso nel modello e nelle notifiche di modifica distribuite

Queste interfacce vengono esposte durante la connessione, restituendo una gerarchia di oggetti simile a questa:

  • Connessione
    • Riproduzione getPlayback()
      • Riproduci, metti in pausa, salta, traccia corrente ecc
      • Esporre i cambiamenti dello stato di riproduzione
    • Tracce getTracks()
      • Traccia getTrack(id) ecc
      • Esporre gli aggiornamenti delle tracce
    • Collezioni getCollection()
      • Carica e manipola playlist o raccolte con nome
      • Interroga la libreria multimediale
      • Esporre gli aggiornamenti della raccolta
È stato utile?

Soluzione

Per la parte asincrona, suggerirei di effettuare il check-in java.util.concurrent, e soprattutto il Future<T> interfaccia.L'interfaccia futura viene utilizzata per rappresentare oggetti che non sono ancora pronti, ma vengono creati in un thread separato.Dici che gli oggetti possono essere modificati in qualsiasi momento da una terza parte, ma ti suggerirei comunque di utilizzare qui oggetti di ritorno immutabili e di avere invece un thread/log eventi separato a cui puoi iscriverti per essere notato quando gli oggetti scadono.Ho poca programmazione con le interfacce utente, ma credo che l'utilizzo di Futures per le chiamate asincrone ti consentirebbe di avere una GUI reattiva, piuttosto che una in attesa di una risposta dal server.

Per le query suggerirei di utilizzare il concatenamento di metodi per creare l'oggetto query e ogni oggetto restituito dal concatenamento di metodi dovrebbe esserlo Iterable.Simile a come è il modello di Django.Di' di averlo fatto QuerySet che implementa Iterable<Song>.Successivamente potrai chiamare allSongs() che restituirebbe un risultato ripetuto su tutte le canzoni.O allSongs().artist("Beatles"), e avresti un iterabile su tutte le canzoni di Betles.O anche allSongs().artist("Beatles").years(1965,1967) e così via.

Spero che questo aiuti come punto di partenza.

Altri suggerimenti

Iterable ha solo il metodo Iterator get() o qualcosa del genere.Quindi non è necessario creare alcuna query o eseguire alcun codice finché non si inizia effettivamente a ripetere.Rende ridondante l'esecuzione nel tuo esempio.Tuttavia, il thread verrà bloccato finché non sarà disponibile il primo risultato, quindi potresti prendere in considerazione l'utilizzo di un esecutore per eseguire il codice per la query in un thread separato.

@Staale

È certamente possibile, ma come hai notato, ciò lo renderebbe bloccante (a casa per qualcosa come 10 secondi a causa dei dischi inattivi), il che significa che non posso usarlo per aggiornare direttamente l'interfaccia utente.

Potrei usare l'iteratore per creare una copia del risultato in un thread separato e quindi inviarla all'interfaccia utente, ma sebbene la soluzione dell'iteratore di per sé sia ​​piuttosto elegante, non si adatterà molto bene.Alla fine, qualcosa di implementativo IStructuredContentProvider deve restituire un array di tutti gli oggetti per visualizzarlo in a Visualizzatore tabelle, quindi se riesco a farla franca ottenendo qualcosa del genere da una richiamata...:)

Ci penserò ancora un po'.Potrei semplicemente riuscire a capire qualcosa.Dà al codice un bell'aspetto.

@Staale:Grazie mille!

L'utilizzo di Future per le operazioni asincrone è interessante.L'unico inconveniente è che non fornisce callback.Ma poi di nuovo, ho provato questo approccio e guarda dove mi ha portato :)

Attualmente sto risolvendo un problema simile utilizzando un thread di lavoro e una coda di blocco per l'invio delle risposte ai comandi in arrivo, ma questo approccio non si traduce molto bene.

Gli oggetti remoti possono essere modificati, ma poiché utilizzo i thread, cerco di mantenere gli oggetti immutabili.La mia ipotesi attuale è che invierò eventi di notifica sugli aggiornamenti della traccia nel modulo

somehandlername(int changes, Track old_track, Track new_track)

o simili, ma poi potrei ritrovarmi con diverse versioni della stessa traccia.

Esaminerò sicuramente il concatenamento del metodo Djangos.Ho cercato alcuni costrutti simili ma non sono riuscito a trovare una buona variante.Restituire qualcosa di iterabile è interessante, ma il completamento della query potrebbe richiedere del tempo e non vorrei eseguire effettivamente la query prima che sia completamente costruita.

Forse qualcosa del genere

Tracks.allSongs().artist("Beatles").years(1965,1967).execute()

restituire un futuro potrebbe funzionare...

Le mie conclusioni finora;

Sono indeciso se utilizzare i getter per gli oggetti Track o semplicemente esporre i membri poiché l'oggetto è immutabile.

class Track {
    public final String album;
    public final String artist;
    public final String title;
    public final String genre;
    public final String comment;

    public final String cover_id;

    public final long duration;
    public final long bitrate;
    public final long samplerate;
    public final long id;
    public final Date date;

    /* Some more stuff here */
}

Chiunque voglia sapere quando è successo qualcosa a una traccia nella libreria lo implementerebbe...

interface TrackUpdateListener {
    void trackUpdate(Track oldTrack, Track newTrack);
}

Ecco come vengono costruite le query.Chiamate a catena a tuo piacimento.la giuria è ancora fuori sul get() però.Mancano alcuni dettagli, ad esempio come dovrei gestire i caratteri jolly e le query più avanzate con disgiunzioni.Potrei semplicemente aver bisogno di alcune funzionalità di callback di completamento, forse simili a Token di completamento asincrono, ma questo lo vedremo.Forse ciò avverrà in un livello aggiuntivo.

interface TrackQuery extends Iterable<Track> {
    TrackQuery years(int from, int to);
    TrackQuery artist(String name);
    TrackQuery album(String name);
    TrackQuery id(long id);
    TrackQuery ids(long id[]);

    Future<Track[]> get();
}

Qualche esempio:

tracks.allTracks();
tracks.allTracks().artist("Front 242").album("Tyranny (For You)");

L'interfaccia delle tracce è per lo più solo il collante tra la connessione e le singole tracce.Sarà quello che implementerà o gestirà la memorizzazione nella cache dei metadati, se presente (come oggi, ma penso che lo rimuoverò semplicemente durante il refactoring e vedrò se ne ho effettivamente bisogno).Inoltre, questo fornisce aggiornamenti sulle tracce di Medialib poiché sarebbe semplicemente troppo lavoro implementarlo per traccia.

interface Tracks {
    TrackQuery allTracks();

    void addUpdateListener(TrackUpdateListener listener);
    void removeUpdateListener(TrackUpdateListener listener);
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top