Pregunta

Tengo una pregunta sobre la mejor manera de exponer una interfaz remota asincrónica.

Las condiciones son las siguientes:

  • El protocolo es asíncrono.
  • Un tercero puede modificar los datos en cualquier momento.
  • El viaje de ida y vuelta del comando puede ser significativo
  • El modelo debe ser adecuado para la interacción con la interfaz de usuario.
  • El protocolo admite consultas sobre ciertos objetos, al igual que el modelo.

Como medio para mejorar mis habilidades que me faltan en esta área (y mejorar mi Java en general), comencé un proyecto para crear una interfaz basada en Eclipse para xmms2 (descrito abajo).

Entonces la pregunta es;¿Cómo debo exponer la interfaz remota como un modelo de datos ordenado (en este caso, gestión de seguimiento y manejo de eventos)?

Doy la bienvenida a cualquier cosa, desde discusiones genéricas hasta nombres de patrones o ejemplos y parches concretos :)


Mi objetivo principal aquí es aprender sobre esta clase de problemas en general.Si mi proyecto puede beneficiarse de él, está bien, pero lo presento estrictamente para tener algo sobre lo que iniciar una discusión.

He implementado una abstracción de protocolo que llamo 'cliente' (por razones heredadas) que me permite acceder a las funciones más expuestas mediante llamadas a métodos con las que estoy contento incluso si está lejos de ser perfecto.

Las funciones proporcionadas por el demonio xmms2 son cosas como búsqueda de pistas, recuperación y manipulación de metadatos, cambio de estado de reproducción, carga de listas de reproducción, etc.

Estoy en medio de la actualización a la última versión estable de xmms2 y pensé que también podría corregir algunas de las debilidades evidentes de mi implementación actual.

Mi plan es construir una mejor abstracción sobre la interfaz del protocolo, una que permita una interacción más natural con el demonio.La corriente 'modelo' La implementación es difícil de usar y, francamente, bastante fea (sin mencionar el código de interfaz de usuario, que es realmente horrible).

Hoy tengo el Pistas interfaz que puedo usar para obtener instancias de Pista clases basadas en su identificación.La búsqueda se realiza a través del Colecciones interfaz (desafortunado choque de espacios de nombres) que prefiero pasar a Tracks, creo.

Cualquier dato puede ser modificado por un tercero en cualquier momento, debiendo quedar debidamente reflejado en el modelo y en las notificaciones de cambios distribuidas.

Estas interfaces se exponen al conectarse, al devolver una jerarquía de objetos similar a esta:

  • Conexión
    • Reproducción getPlayback()
      • Reproducir, pausar, saltar, pista actual, etc.
      • Exponer cambios de estado de reproducción
    • Pistas getTracks()
      • Seguimiento de getTrack(id), etc.
      • Exponer actualizaciones de seguimiento
    • Colecciones getCollection()
      • Cargar y manipular listas de reproducción o colecciones con nombre
      • Consultar biblioteca de medios
      • Exponer actualizaciones de la colección
¿Fue útil?

Solución

Para el bit asincrónico, sugeriría consultar java.util.concurrent, y especialmente el Future<T> interfaz.La interfaz futura se utiliza para representar objetos que aún no están listos, pero que se están creando en un hilo separado.Usted dice que un tercero puede modificar los objetos en cualquier momento, pero aún así le sugiero que use objetos de retorno inmutables aquí y, en su lugar, tenga un hilo/registro de eventos separado al que pueda suscribirse para recibir atención cuando los objetos caduquen.Tengo poca programación con UI, pero creo que usar Futures para llamadas asincrónicas le permitiría tener una GUI receptiva, en lugar de una que estuviera esperando una respuesta del servidor.

Para las consultas, sugeriría usar el encadenamiento de métodos para crear el objeto de consulta, y cada objeto devuelto por el encadenamiento de métodos debe ser Iterable.Similar a cómo es el modelo de Django.di que tienes QuerySet que implementa Iterable<Song>.Luego puedes llamar allSongs() lo que devolvería un resultado iterando sobre todas las canciones.O allSongs().artist("Beatles"), y tendrías un iterable sobre todas las canciones de Betles.O incluso allSongs().artist("Beatles").years(1965,1967) etcétera.

Espero que esto ayude como punto de partida.

Otros consejos

Iterable solo tiene el método Iterator get() o algo así.Por lo tanto, no es necesario crear ninguna consulta ni ejecutar ningún código hasta que realmente comience a iterar.Hace que la ejecución en su ejemplo sea redundante.Sin embargo, el hilo se bloqueará hasta que el primer resultado esté disponible, por lo que podría considerar usar un Ejecutor para ejecutar el código de la consulta en un hilo separado.

@Staale

Ciertamente es posible, pero como habrás notado, eso lo bloquearía (en casa durante unos 10 segundos debido a los discos inactivos), lo que significa que no puedo usarlo para actualizar la interfaz de usuario directamente.

Podría usar el iterador para crear una copia del resultado en un hilo separado y luego enviarlo a la interfaz de usuario, pero si bien la solución del iterador en sí es bastante elegante, no encajará muy bien.Al final, algo que se implementa Proveedor de contenido estructurado necesita devolver una matriz de todos los objetos para poder mostrarlos en un Visor de tablas, así que si puedo salirme con la mía obteniendo algo así de una devolución de llamada...:)

Lo pensaré un poco más.Tal vez pueda resolver algo.Le da al código una buena apariencia.

@Staale:¡Gracias un montón!

Usar Future para las operaciones asíncronas es interesante.El único inconveniente es que no proporciona devoluciones de llamada.Pero, de nuevo, probé ese enfoque y mira adónde me llevó :)

Actualmente estoy resolviendo un problema similar usando un subproceso de trabajo y una cola de bloqueo para enviar las respuestas de los comandos entrantes, pero ese enfoque no se traduce muy bien.

Los objetos remotos se pueden modificar, pero como uso subprocesos, trato de mantener los objetos inmutables.Mi hipótesis actual es que enviaré eventos de notificación sobre actualizaciones de seguimiento en el formulario.

somehandlername(int changes, Track old_track, Track new_track)

o similar, pero entonces podría terminar con varias versiones del mismo tema.

Definitivamente investigaré el encadenamiento de métodos de Django.He estado mirando algunas construcciones similares pero no he podido encontrar una buena variante.Devolver algo iterable es interesante, pero la consulta puede tardar algún tiempo en completarse y no me gustaría ejecutar la consulta antes de que esté completamente construida.

Quizás algo como

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

Devolver un futuro podría funcionar...

Mis conclusiones hasta ahora;

No sé si usar captadores para los objetos Track o simplemente exponer los miembros ya que el objeto es inmutable.

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

Cualquiera que quiera saber cuándo sucedió algo con una pista en la biblioteca implementaría esto...

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

Así es como se construyen las consultas.Encadena llamadas a tu gusto.Sin embargo, el jurado todavía está deliberando sobre get().Faltan algunos detalles, como cómo debo manejar los comodines y consultas más avanzadas con disyunciones.Puede que sólo necesite alguna funcionalidad de devolución de llamada de finalización, quizás similar a la Token de finalización asincrónica, pero eso ya lo veremos.Quizás eso suceda en una capa adicional.

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

Algunos ejemplos:

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

La interfaz de las pistas es principalmente sólo el pegamento entre la conexión y las pistas individuales.Será quien implemente o administre el almacenamiento en caché de metadatos, si lo hay (como hoy, pero creo que lo eliminaré durante la refactorización y veré si realmente lo necesito).Además, esto proporciona actualizaciones de seguimiento de medialib, ya que sería demasiado trabajo implementarlo por seguimiento.

interface Tracks {
    TrackQuery allTracks();

    void addUpdateListener(TrackUpdateListener listener);
    void removeUpdateListener(TrackUpdateListener listener);
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top