Question

J'ai une question sur la meilleure façon d'exposer une interface distante asynchrone.

Les conditions sont les suivantes :

  • Le protocole est asynchrone
  • Un tiers peut modifier les données à tout moment
  • L’aller-retour des commandes peut être important
  • Le modèle doit être bien adapté à l'interaction avec l'interface utilisateur
  • Le protocole prend en charge les requêtes sur certains objets, tout comme le modèle.

Afin d'améliorer mes compétences manquantes dans ce domaine (et de perfectionner mon Java en général), j'ai commencé un projet 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 en tant que modèle de données soigné (dans ce cas, suivre la gestion et la gestion des événements) ?

Je suis ouvert à tout, des discussions génériques à la suppression de noms de modèles en passant par des exemples et des correctifs concrets :)


Mon objectif principal ici est d'en apprendre davantage sur cette classe de problèmes en général.Si mon projet peut en tirer profit, très bien, mais je le présente uniquement pour avoir de quoi engager une discussion.

J'ai implémenté une abstraction de protocole que j'appelle 'client' (pour des raisons héritées) ce qui me permet d'accéder aux fonctionnalités les plus exposées à l'aide d'appels de méthodes dont je suis satisfait même si c'est loin d'être parfait.

Les fonctionnalités fournies par le démon xmms2 sont des éléments tels que la recherche de pistes, la récupération et la manipulation de métadonnées, la modification de l'état de lecture, le chargement de listes de lecture, etc.

Je suis en train de mettre à jour vers 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 créer une meilleure abstraction au-dessus de l'interface du protocole, une qui permet une interaction plus naturelle avec le démon.Le courant 'modèle' l'implémentation est difficile à utiliser et est franchement assez moche (sans parler du code UI qui est vraiment horrible).

Aujourd'hui, j'ai le Des pistes interface que je peux utiliser pour obtenir des instances de Piste classes en fonction de leur identifiant.La recherche s'effectue via le Collections interface (malheureux conflit d'espace de noms) que je préférerais déplacer vers Tracks, je pense.

Toutes les données peuvent être modifiées par un tiers à tout moment, et cela doit être correctement reflété dans le modèle et les notifications de modification distribuées.

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

  • Connexion
    • Lecture getPlayback()
      • Lecture, pause, saut, piste actuelle, etc.
      • Exposer les changements d’état de lecture
    • Pistes getTracks()
      • Suivre getTrack (id), etc.
      • Exposer les mises à jour des pistes
    • CollectionsgetCollection()
      • Charger et manipuler des listes de lecture ou des collections nommées
      • Bibliothèque multimédia de requêtes
      • Exposer les mises à jour de la collection
Était-ce utile?

La solution

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

Pour les requêtes, je suggérerais d'utiliser le chaînage de méthodes pour créer l'objet de requête, et chaque objet renvoyé par le chaînage de méthodes devrait être Iterable.Semblable au modèle Djangos.Dis que tu as QuerySet qui met en œuvre Iterable<Song>.Vous pourrez alors appeler allSongs() ce qui renverrait un résultat itérant sur toutes les chansons.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.

J'espère que cela vous aidera comme point de départ.

Autres conseils

Iterable n'a que la méthode Iterator get() ou quelque chose du genre.Il n'est donc pas nécessaire de créer une requête ou d'exécuter du code jusqu'à ce que vous commenciez réellement à itérer.Cela rend l'exécution dans votre exemple redondante.Cependant, le thread sera verrouillé jusqu'à ce que le premier résultat soit disponible. Vous pouvez donc envisager d'utiliser un exécuteur pour exécuter le code de la requête dans un thread distinct.

@Acier

C'est certainement possible, mais comme vous le notez, cela le bloquerait (à la maison pendant environ 10 secondes en raison de disques en veille), ce qui signifie que je ne peux pas l'utiliser pour mettre à jour directement l'interface utilisateur.

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 même si la solution de l'itérateur en elle-même est plutôt élégante, elle ne s'intégrera pas très bien.En fin de compte, quelque chose mettant en œuvre IStructuredContentProvider doit renvoyer un tableau de tous les objets afin de l'afficher dans un Visionneuse de table, donc si je peux m'en sortir en obtenant quelque chose comme ça à partir d'un rappel...:)

Je vais y réfléchir davantage.Je pourrais peut-être trouver une solution.Cela donne un joli look au code.

@Acier:Merci beaucoup !

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

Je résout actuellement un problème similaire en utilisant un thread de travail et une file d'attente de blocage pour distribuer les réponses aux commandes entrantes, mais cette approche ne se traduit pas 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 j'enverrai des événements de notification lors des mises à jour sur le formulaire.

somehandlername(int changes, Track old_track, Track new_track)

ou similaire, mais je pourrais alors me retrouver avec plusieurs versions du même morceau.

Je vais certainement me pencher sur le chaînage des méthodes Djangos.J'ai examiné des constructions similaires mais je n'ai pas réussi à trouver une bonne variante.Renvoyer quelque chose d'itérable est intéressant, mais la requête peut prendre un certain temps et je ne voudrais pas réellement 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()

renvoyer un Future pourrait fonctionner...

Mes conclusions jusqu'à présent ;

Je ne sais pas 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 */
}

Quiconque veut savoir quand quelque chose est arrivé à une piste de la bibliothèque implémenterait ceci...

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

C'est ainsi que les requêtes sont construites.Enchaînez les appels à votre guise.le jury est toujours sur get() cependant.Il manque certains détails, tels que la façon dont je dois gérer les caractères génériques et les requêtes plus avancées avec disjonctions.J'aurais peut-être juste besoin d'une fonctionnalité de rappel d'achèvement, peut-être similaire à celle Jeton d'achèvement asynchrone, mais nous verrons cela.Peut-être que cela se produira à un niveau 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 des pistes n’est en grande partie que le ciment entre la connexion et les pistes individuelles.Ce sera celui qui implémentera ou gérera la mise en cache des métadonnées, le cas échéant (comme aujourd'hui, mais je pense que je vais simplement le supprimer pendant la refactorisation et voir si j'en ai réellement besoin).En outre, cela fournit des mises à jour des pistes Medialib, car ce serait tout simplement trop de travail de l'implémenter par piste.

interface Tracks {
    TrackQuery allTracks();

    void addUpdateListener(TrackUpdateListener listener);
    void removeUpdateListener(TrackUpdateListener listener);
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top