Pergunta

Tenho uma dúvida sobre a melhor maneira de expor uma interface remota assíncrona.

As condições são as seguintes:

  • O protocolo é assíncrono
  • Um terceiro pode modificar os dados a qualquer momento
  • O comando roundtrip pode ser significativo
  • O modelo deve ser adequado para interação da IU
  • O protocolo suporta consultas sobre determinados objetos, e o modelo também deve

Como forma de melhorar minhas habilidades nesta área (e aprimorar meu Java em geral), comecei um projeto para criar um front-end baseado em Eclipse para xmms2 (Descrito abaixo).

Então, a questão é;como devo expor a interface remota como um modelo de dados organizado (neste caso, rastreie o gerenciamento e a manipulação de eventos)?

Aceito qualquer coisa, desde discussões genéricas até nomes de padrões ou exemplos e patches concretos :)


Meu objetivo principal aqui é aprender sobre essa classe de problemas em geral.Se meu projeto puder ganhar com isso, tudo bem, mas apresento-o estritamente para ter algo para iniciar uma discussão.

Eu implementei uma abstração de protocolo que chamo 'cliente' (por motivos legados), o que me permite acessar os recursos mais expostos usando chamadas de método com as quais estou satisfeito, mesmo que esteja longe de ser perfeito.

Os recursos fornecidos pelo daemon xmms2 são coisas como pesquisa de faixas, recuperação e manipulação de metadados, alteração do estado de reprodução, carregamento de listas de reprodução e assim por diante.

Estou no meio da atualização para a versão estável mais recente do xmms2 e achei que poderia muito bem corrigir alguns dos pontos fracos flagrantes da minha implementação atual.

Meu plano é construir uma abstração melhor sobre a interface do protocolo, que permita uma interação mais natural com o daemon.O actual 'modelo' a implementação é difícil de usar e francamente muito feia (sem mencionar o código da interface do usuário, que é realmente horrível).

Hoje eu tenho o Faixas interface que posso usar para obter instâncias de Acompanhar classes com base em seu id.A pesquisa é realizada através do Coleções interface (infeliz conflito de namespace) que prefiro mudar para Tracks, eu acho.

Quaisquer dados podem ser modificados por terceiros a qualquer momento, e isso deve ser devidamente refletido no modelo e nas notificações de alteração distribuídas

Essas interfaces são expostas durante a conexão, retornando uma hierarquia de objetos semelhante a esta:

  • Conexão
    • Reprodução getPlayback()
      • Reproduzir, pausar, pular, faixa atual, etc.
      • Expor alterações no estado de reprodução
    • Rastreia getTracks()
      • Rastrear getTrack(id) etc.
      • Expor atualizações de faixa
    • Coleções getCollection()
      • Carregar e manipular playlists ou coleções nomeadas
      • Consultar biblioteca de mídia
      • Expor atualizações da coleção
Foi útil?

Solução

Para o bit assíncrono, sugiro verificar java.util.concurrent, e especialmente o Future<T> interface.A interface futura é usada para representar objetos que ainda não estão prontos, mas estão sendo criados em um thread separado.Você diz que os objetos podem ser modificados a qualquer momento por terceiros, mas eu ainda sugiro que você use objetos de retorno imutáveis ​​​​aqui e, em vez disso, tenha um log de thread/evento separado no qual você pode se inscrever para ser notado quando os objetos expirarem.Tenho pouca programação com UIs, mas acredito que usar Futures para chamadas assíncronas permitiria que você tivesse uma GUI responsiva, em vez de uma que estivesse aguardando uma resposta do servidor.

Para as consultas eu sugeriria usar o encadeamento de métodos para construir o objeto de consulta, e cada objeto retornado pelo encadeamento de métodos deve ser Iterable.Semelhante à forma como o modelo Djangos é.Diga que você tem QuerySet que implementa Iterable<Song>.Você pode então ligar allSongs() que retornaria um resultado iterando todas as músicas.Ou allSongs().artist("Beatles"), e você teria um iterável sobre todas as músicas do Betles.Ou mesmo allSongs().artist("Beatles").years(1965,1967) e assim por diante.

Espero que isso ajude como ponto de partida.

Outras dicas

Iterable possui apenas o método Iterator get() ou algo assim.Portanto, não há necessidade de criar nenhuma consulta ou executar qualquer código até que você realmente comece a iterar.Isso torna a execução no seu exemplo redundante.No entanto, o thread será bloqueado até que o primeiro resultado esteja disponível, então você pode considerar usar um Executor para executar o código da consulta em um thread separado.

@Staale

Certamente é possível, mas como você observou, isso o bloquearia (em casa por cerca de 10 segundos devido a discos inativos), o que significa que não posso usá-lo para atualizar a interface do usuário diretamente.

Eu poderia usar o iterador para criar uma cópia do resultado em um thread separado e depois enviá-lo para a interface do usuário, mas embora a solução do iterador por si só seja bastante elegante, ela não se encaixará muito bem.No final, algo implementando IStructuredContentProvider precisa retornar um array de todos os objetos para exibi-lo em um Visualizador de tabela, então, se eu conseguir obter algo assim em um retorno de chamada ...:)

Vou pensar mais um pouco.Talvez eu consiga resolver alguma coisa.Isso dá uma boa aparência ao código.

@Staale:Muito obrigado!

Usar Future para operações assíncronas é interessante.A única desvantagem é que não fornece retornos de chamada.Mas, novamente, tentei essa abordagem e veja onde isso me levou :)

Atualmente estou resolvendo um problema semelhante usando um thread de trabalho e uma fila de bloqueio para despachar as respostas dos comandos recebidos, mas essa abordagem não se traduz muito bem.

Os objetos remotos podem ser modificados, mas como uso threads, tento manter os objetos imutáveis.Minha hipótese atual é que enviarei eventos de notificação sobre atualizações de rastreamento no formulário

somehandlername(int changes, Track old_track, Track new_track)

ou similar, mas posso acabar com várias versões da mesma faixa.

Definitivamente vou dar uma olhada no encadeamento de métodos do Djangos.Estive analisando algumas construções semelhantes, mas não consegui encontrar uma boa variante.Retornar algo iterável é interessante, mas a consulta pode levar algum tempo para ser concluída e eu não gostaria de realmente executar a consulta antes de ser completamente construída.

Talvez algo como

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

devolver um futuro pode funcionar ...

Minhas conclusões até agora;

Não sei se devo usar getters para os objetos Track ou apenas expor os membros, já que o objeto é imutável.

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

Qualquer pessoa que queira saber quando algo aconteceu com uma faixa da biblioteca implementaria isso...

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

É assim que as consultas são construídas.Chamadas em cadeia para o conteúdo do seu coração.o júri ainda não decidiu sobre get().Faltam alguns detalhes, como como devo lidar com curingas e consultas mais avançadas com disjunções.Talvez eu precise apenas de alguma funcionalidade de retorno de chamada de conclusão, talvez semelhante ao Token de conclusão assíncrona, mas veremos isso.Talvez isso aconteça em uma camada 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();
}

Alguns exemplos:

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

A interface das trilhas é basicamente apenas a cola entre a conexão e as trilhas individuais.Será aquele que implementará ou gerenciará o cache de metadados, se houver (como hoje, mas acho que vou removê-lo durante a refatoração e ver se realmente preciso dele).Além disso, isso fornece atualizações de faixa do medialib, pois seria muito trabalhoso implementá-lo por faixa.

interface Tracks {
    TrackQuery allTracks();

    void addUpdateListener(TrackUpdateListener listener);
    void removeUpdateListener(TrackUpdateListener listener);
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top