Frage

Ich habe eine Frage dazu, wie ich eine asynchrone Remote-Schnittstelle am besten verfügbar machen kann.

Die Bedingungen sind wie folgt:

  • Das Protokoll ist asynchron
  • Ein Dritter kann die Daten jederzeit ändern
  • Der Befehls-Roundtrip kann erheblich sein
  • Das Modell sollte gut für die UI-Interaktion geeignet sein
  • Das Protokoll unterstützt Abfragen über bestimmte Objekte, und das gilt auch für das Modell

Um meine mangelnden Kenntnisse in diesem Bereich zu verbessern (und mein Java im Allgemeinen aufzufrischen), habe ich eine begonnen Projekt um ein Eclipse-basiertes Frontend für zu erstellen xmms2 (nachstehend beschrieben).

Die Frage ist also;Wie soll ich die Remote-Schnittstelle als übersichtliches Datenmodell verfügbar machen (in diesem Fall Track-Management und Event-Handling)?

Ich begrüße alles, von allgemeinen Diskussionen über das Weglassen von Musternamen bis hin zu konkreten Beispielen und Patches :)


Mein Hauptziel hier ist es, etwas über diese Klasse von Problemen im Allgemeinen zu lernen.Wenn mein Projekt davon profitieren kann, ist das in Ordnung, aber ich präsentiere es ausschließlich, um etwas zu haben, worüber ich eine Diskussion anstoßen kann.

Ich habe eine Protokollabstraktion implementiert, die ich aufrufe 'Klient' (aus Legacy-Gründen), wodurch ich mithilfe von Methodenaufrufen auf die meisten exponierten Funktionen zugreifen kann, mit denen ich zufrieden bin, auch wenn sie alles andere als perfekt sind.

Zu den vom xmms2-Daemon bereitgestellten Funktionen gehören Dinge wie die Suche nach Titeln, das Abrufen und Bearbeiten von Metadaten, das Ändern des Wiedergabestatus, das Laden von Wiedergabelisten usw.

Ich bin gerade dabei, auf die neueste stabile Version von xmms2 zu aktualisieren, und ich dachte, ich könnte auch einige der eklatanten Schwächen meiner aktuellen Implementierung beheben.

Mein Plan ist es, eine bessere Abstraktion auf der Protokollschnittstelle aufzubauen, die eine natürlichere Interaktion mit dem Daemon ermöglicht.Die jetzige 'Modell' Die Implementierung ist schwer zu verwenden und ehrlich gesagt ziemlich hässlich (ganz zu schweigen vom UI-Code, der wirklich schrecklich ist).

Heute habe ich das Spuren Schnittstelle, über die ich Instanzen abrufen kann Schiene Klassen basierend auf ihrer ID.Die Suche erfolgt über die Sammlungen Schnittstelle (unglücklicher Namespace-Konflikt), die ich meiner Meinung nach lieber auf Tracks verschieben würde.

Alle Daten können jederzeit von Dritten geändert werden, und dies sollte im Modell und in den verteilten Änderungsbenachrichtigungen ordnungsgemäß widergespiegelt werden

Diese Schnittstellen werden beim Herstellen einer Verbindung verfügbar gemacht, indem eine Objekthierarchie zurückgegeben wird, die wie folgt aussieht:

  • Verbindung
    • Wiedergabe getPlayback()
      • Wiedergabe, Pause, Sprung, aktueller Titel usw
      • Machen Sie Änderungen des Wiedergabestatus sichtbar
    • Verfolgt getTracks()
      • Verfolgen Sie getTrack(id) usw
      • Trackaktualisierungen verfügbar machen
    • Sammlungen getCollection()
      • Laden und bearbeiten Sie Wiedergabelisten oder benannte Sammlungen
      • Medienbibliothek abfragen
      • Sammlungsaktualisierungen verfügbar machen
War es hilfreich?

Lösung

Für das asynchrone Bit würde ich vorschlagen, einen Blick darauf zu werfen java.util.concurrent, und vor allem die Future<T> Schnittstelle.Die zukünftige Schnittstelle dient zur Darstellung von Objekten, die noch nicht fertig sind, aber in einem separaten Thread erstellt werden.Sie sagen, dass Objekte jederzeit von Dritten geändert werden können, aber ich würde dennoch vorschlagen, dass Sie hier unveränderliche Rückgabeobjekte verwenden und stattdessen ein separates Thread-/Ereignisprotokoll haben, das Sie abonnieren können, um benachrichtigt zu werden, wenn Objekte ablaufen.Ich programmiere wenig mit UIs, aber ich glaube, dass die Verwendung von Futures für asynchrone Aufrufe eine reaktionsfähige GUI ermöglichen würde, anstatt eine, die auf eine Serverantwort wartet.

Für die Abfragen würde ich vorschlagen, die Methodenverkettung zu verwenden, um das Abfrageobjekt zu erstellen, und jedes von der Methodenverkettung zurückgegebene Objekt sollte dies tun Iterable.Ähnlich wie das Djangos-Modell.Sagen wir, das haben Sie QuerySet was umsetzt Iterable<Song>.Dann können Sie anrufen allSongs() was ein Ergebnis zurückgeben würde, das alle Songs durchläuft.Oder allSongs().artist("Beatles"), und Sie hätten eine Iteration über alle Betles-Songs.Oder auch allSongs().artist("Beatles").years(1965,1967) und so weiter.

Ich hoffe, das hilft als Ausgangspunkt.

Andere Tipps

Iterable hat nur die Methode Iterator get() oder so.Sie müssen also keine Abfrage erstellen oder Code ausführen, bis Sie tatsächlich mit der Iteration beginnen.Dadurch wird die Ausführung in Ihrem Beispiel überflüssig.Der Thread bleibt jedoch gesperrt, bis das erste Ergebnis verfügbar ist. Daher können Sie erwägen, einen Executor zu verwenden, um den Code für die Abfrage in einem separaten Thread auszuführen.

@Staale

Es ist sicherlich möglich, aber wie Sie bemerkt haben, würde es dadurch blockieren (zu Hause für etwa 10 Sekunden aufgrund schlafender Festplatten), was bedeutet, dass ich es nicht verwenden kann, um die Benutzeroberfläche direkt zu aktualisieren.

Ich könnte den Iterator verwenden, um eine Kopie des Ergebnisses in einem separaten Thread zu erstellen und diese dann an die Benutzeroberfläche zu senden, aber obwohl die Iteratorlösung an sich recht elegant ist, passt sie nicht sehr gut hinein.Am Ende etwas Umsetzendes IStructuredContentProvider muss ein Array aller Objekte zurückgeben, um es in einem anzuzeigen TableViewer, wenn ich also damit durchkomme, so etwas aus einem Rückruf herauszuholen ...:) :)

Ich werde noch etwas darüber nachdenken.Vielleicht schaffe ich es einfach, etwas herauszufinden.Es verleiht dem Code ein schönes Aussehen.

@Staale:Vielen Dank!

Es ist interessant, Future für die asynchronen Vorgänge zu verwenden.Der einzige Nachteil besteht darin, dass keine Rückrufe möglich sind.Aber andererseits habe ich diesen Ansatz ausprobiert und schau, wohin mich das geführt hat :)

Ich löse derzeit ein ähnliches Problem mithilfe eines Arbeitsthreads und einer Blockierungswarteschlange zum Versenden der eingehenden Befehlsantworten, aber dieser Ansatz lässt sich nicht sehr gut umsetzen.

Die Remote-Objekte können geändert werden, aber da ich Threads verwende, versuche ich, die Objekte unveränderlich zu halten.Meine aktuelle Hypothese ist, dass ich Benachrichtigungsereignisse senden werde, um Aktualisierungen im Formular zu verfolgen

somehandlername(int changes, Track old_track, Track new_track)

oder ähnliches, aber dann könnte es sein, dass ich am Ende mehrere Versionen desselben Titels habe.

Ich werde mir auf jeden Fall die Methodenverkettung von Djangos ansehen.Ich habe mir einige ähnliche Konstrukte angesehen, konnte mir aber keine gute Variante einfallen lassen.Es ist interessant, etwas Iterierbares zurückzugeben, aber die Abfrage kann einige Zeit in Anspruch nehmen, und ich möchte die Abfrage nicht tatsächlich ausführen, bevor sie vollständig erstellt ist.

Vielleicht so etwas wie

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

Die Rückgabe einer Zukunft könnte funktionieren ...

Meine bisherigen Schlussfolgerungen;

Ich bin hin- und hergerissen, ob ich Getter für die Track-Objekte verwenden oder nur die Mitglieder verfügbar machen soll, da das Objekt unveränderlich ist.

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 */
}

Jeder, der wissen möchte, wann etwas mit einem Titel in der Bibliothek passiert ist, würde dies implementieren ...

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

So werden Abfragen erstellt.Kettenanrufe nach Herzenslust.Die Entscheidung über get() ist jedoch noch nicht abgeschlossen.Es fehlen einige Details, z. B. wie ich mit Platzhaltern und komplexeren Abfragen mit Disjunktionen umgehen soll.Ich benötige möglicherweise nur eine Callback-Funktion zur Vervollständigung, vielleicht ähnlich der Asynchrones Abschlusstoken, aber das werden wir sehen.Vielleicht geschieht das in einer zusätzlichen Ebene.

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

Einige Beispiele:

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

Die Gleisschnittstelle ist meist nur der Kleber zwischen der Verbindung und den einzelnen Gleisen.Es wird dasjenige sein, das ggf. das Metadaten-Caching implementiert oder verwaltet (wie heute, aber ich denke, ich werde es während des Refactorings einfach entfernen und prüfen, ob ich es tatsächlich benötige).Außerdem werden dadurch Medialib-Track-Updates bereitgestellt, da es einfach zu aufwändig wäre, diese pro Track zu implementieren.

interface Tracks {
    TrackQuery allTracks();

    void addUpdateListener(TrackUpdateListener listener);
    void removeUpdateListener(TrackUpdateListener listener);
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top