2008-08-31 2 views
2

J'ai une question sur la meilleure façon d'exposer une interface distante asynchrone.Affichage d'une interface distante ou d'un modèle d'objet

Les conditions sont les suivantes:

  • Le protocole est asynchrone
  • un tiers peut modifier les données à tout moment
  • La commande aller-retour peut être importante
  • Le modèle devrait convient bien pour l'interaction de l'interface utilisateur
  • Le protocole prend en charge les requêtes sur certains objets, tout comme le modèle.

Afin d'améliorer mon manque de compétences dans ce domaine (et de rafraîchir mon Java en général), j'ai démarré un project pour créer un frontal basé sur Eclipse pour xmms2 (décrit ci-dessous).

Donc, la question est; comment dois-je exposer l'interface distante comme un modèle de données soigné (dans ce cas, la gestion des pistes et la gestion des événements)?

Je me félicite tout de discussions génériques à motif name-dropping ou des exemples concrets et patches :)


Mon objectif principal ici est l'apprentissage de cette classe de problèmes en général. Si mon projet peut en tirer profit, c'est bien, mais je le présente strictement pour avoir quelque chose pour lancer une discussion. J'ai implémenté une abstraction de protocole que j'appelle 'client' (pour des raisons héritées) qui me permet d'accéder aux fonctionnalités les plus exposées en utilisant des appels de méthodes dont je suis content même si c'est loin d'être parfait. Les fonctionnalités fournies par le démon xmms2 sont la recherche de pistes, la récupération et la manipulation de métadonnées, la modification de l'état de la lecture, le chargement de listes de lecture et ainsi de suite.

Je suis en train de mettre à jour la dernière version stable de xmms2, et j'ai pensé que je pourrais aussi bien corriger certaines des faiblesses flagrantes de mon implémentation actuelle.

Mon plan est de construire une meilleure abstraction au-dessus de l'interface de protocole, une qui permet une interaction plus naturelle avec le démon. L'implémentation actuelle de 'model' est difficile à utiliser et franchement assez moche (pour ne pas mentionner le code UI qui est vraiment horrible atm).

Aujourd'hui, j'ai l'interface Tracks que je peux utiliser pour obtenir des instances de classes Track en fonction de leur identifiant. La recherche est effectuée via l'interface Collections (malheureux conflit d'espace de noms) que je préfère passer à Tracks, je pense.

Toutes les données peuvent être modifiées par un tiers à tout moment, et cela devrait être dûment prises en compte dans le modèle et changement des notifications distribuées

Ces interfaces sont exposées lors de la connexion, en retournant une hiérarchie d'objets qui ressemble ceci:

  • connexion
    • lecture getPlayback()
      • Play, pause, saut, piste en cours etc
      • Expose état lecture change
    • Tracks getTracks()
      • piste getTrack (id) etc
      • Expose les mises à jour de piste
    • Collections getCollection()
      • Charger et manipuler des listes de lecture ou nommées collections
      • Recherche médiathèque
      • Expose les mises à jour de collecte

Répondre

2

Pour le bit asynchrone, je suggère de vérifier dans java.util.concurrent, et en particulier l'interface Future<T>. L'interface future est utilisée pour représenter les objets qui ne sont pas encore prêts, mais qui sont créés dans un thread distinct. Vous dites que les objets peuvent être modifiés à tout moment par un tiers, mais je vous suggère quand même d'utiliser des objets de retour immuables ici, et d'avoir un thread/événement distinct auquel vous pouvez vous abonner pour être remarqué lorsque les objets expirent. J'ai peu de programmation avec les interfaces utilisateur, mais je crois que l'utilisation de Futures pour les appels asynchrones vous permettrait d'avoir une interface graphique réactive, plutôt qu'une interface qui attendait une réponse du serveur.

Pour les requêtes, je suggérerais d'utiliser le chaînage de méthode pour créer l'objet de requête, et chaque objet renvoyé par le chaînage de méthode devrait être Iterable. Semblable à la façon dont le modèle Djangos est. Supposons que vous ayez QuerySet qui implémente Iterable<Song>. Vous pouvez alors appeler le allSongs() qui retournera un résultat itérant sur tous les morceaux. Ou allSongs().artist("Beatles"), et vous auriez un itérable sur toutes les chansons de Betles. Ou même allSongs().artist("Beatles").years(1965,1967) et ainsi de suite.

Espérons que cela aide comme point de départ.

0

@Staale: Merci beaucoup!

L'utilisation de Future pour les opérations asynchrones est intéressante. Le seul inconvénient étant qu'il ne fournit pas de rappels. Mais là encore, j'ai essayé cette approche, et regardez où cela m'a eu :)

Je suis en train de résoudre un problème similaire en utilisant un thread de travail et une file d'attente de blocage pour envoyer les réponses de commande entrantes, mais cette approche ne fonctionne pas. traduit très bien.

Les objets distants peuvent être modifiés, mais comme j'utilise des threads, j'essaie de garder les objets immuables. Mon hypothèse actuelle est que je vais envoyer les événements de notification des mises à jour de piste sur le formulaire

somehandlername(int changes, Track old_track, Track new_track) 

ou similaire, mais je pourrais finir avec plusieurs versions de la même piste.

Je vais certainement me pencher sur le chaînage de la méthode Djangos. J'ai regardé des constructions similaires mais je n'ai pas réussi à trouver une bonne variante. Renvoyer quelque chose de itérable est intéressant, mais la requête pourrait prendre un certain temps à se terminer, et je ne voudrais pas vraiment exécuter la requête avant qu'elle ne soit complètement construite.

Peut-être quelque chose comme

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

retourner un avenir pourrait fonctionner ...

0

Iterable a seulement la méthode Iterator get() ou somesuch. Il n'est donc pas nécessaire de créer une requête ou d'exécuter du code tant que vous n'avez pas démarré l'itération. Cela rend l'exécution dans votre exemple redondante. Toutefois, le thread sera verrouillé jusqu'à ce que le premier résultat soit disponible. Vous pouvez donc envisager d'utiliser un Executor pour exécuter le code de la requête dans un thread distinct.

0

@Staale

Il est certainement possible, mais comme vous le constatez, cela rendrait le blocage (à la maison pour quelque chose comme 10 secondes en raison de disques de couchage), ce qui signifie que je ne peux pas l'utiliser pour mettre à jour l'interface utilisateur directement.

Je pourrais utiliser l'itérateur pour créer une copie du résultat dans un thread séparé, puis l'envoyer à l'interface utilisateur, mais si la solution de l'itérateur est plutôt élégante, elle ne s'intégrera pas très bien. À la fin, quelque chose mettant en œuvre IStructuredContentProvider doit retourner un tableau de tous les objets afin de l'afficher dans un TableViewer, donc si je peux obtenir quelque chose comme ça à partir d'un rappel ... :)

I Je vais y réfléchir davantage. Je pourrais juste être en mesure de travailler sur quelque chose. Cela donne un bel aspect au code.

0

Mes conclusions jusqu'ici;

Je suis déchiré à savoir s'il faut utiliser des getters pour les objets Track ou simplement exposer les membres puisque l'objet est immuable.

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

Toute personne qui veut savoir quand quelque chose est arrivé à une piste dans la bibliothèque mettra en œuvre cette ...

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

Voici comment querys sont construits. Chaîne d'appels au contenu de votre cœur. le jury est toujours sur le get() cependant. Certains détails sont manquants, tels que la façon dont je devrais gérer les caractères génériques et les requêtes plus avancées avec des disjonctions. Je pourrais juste besoin d'une fonctionnalité de rappel d'achèvement, peut-être similaire à la Asynchronous Completion Token, mais nous verrons à ce sujet. Peut-être que cela se produira dans une couche supplémentaire.

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

Quelques exemples:

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

L'interface pistes est la plupart du temps juste la colle entre la connexion et les pistes individuelles. Ce sera la mise en œuvre ou la gestion de la mise en cache des métadonnées, le cas échéant (comme aujourd'hui, mais je pense que je vais juste l'enlever pendant le refactoring et voir si j'en ai vraiment besoin). En outre, ceci fournit des mises à jour de voie de medialib car ce serait trop de travail pour l'implémenter par voie.

interface Tracks { 
    TrackQuery allTracks(); 

    void addUpdateListener(TrackUpdateListener listener); 
    void removeUpdateListener(TrackUpdateListener listener); 
} 
Questions connexes