У меня вопрос о том, как лучше всего раскрыть асинхронный удаленный интерфейс.
Условия следующие:
Чтобы улучшить мои недостающие навыки в этой области (и освежить мою Java в целом), я запустил проект, чтобы создать интерфейс на основе Eclipse для xmms2 (описанный ниже).
Итак, вопрос: как мне представить удаленный интерфейс как аккуратную модель данных (в данном случае отслеживать управление и обработку событий)?
Я приветствую все, от общих обсуждений до именования шаблонов или конкретных примеров и исправлений :)
Моя основная цель здесь - узнать об этом классе проблем в целом. Если мой проект может извлечь из этого выгоду, хорошо, но я представляю это строго для того, чтобы было о чем начать обсуждение.
Я реализовал абстракцию протокола, которую я называю 'клиент' (по устаревшим причинам), которая позволяет мне получать доступ к наиболее уязвимым функциям с помощью вызовов методов, которые меня устраивают, даже если они далеко не идеальны.
Функции, предоставляемые демоном xmms2, - это такие вещи, как поиск треков, извлечение метаданных и манипулирование ими, изменение состояния воспроизведения, загрузка списков воспроизведения и т. д. И т. Д.
Я занимаюсь обновлением до последней стабильной версии xmms2 и решил, что могу исправить некоторые очевидные недостатки моей текущей реализации.
Мой план состоит в том, чтобы создать лучшую абстракцию поверх интерфейса протокола, которая позволит более естественное взаимодействие с демоном. Текущая реализация 'модель' сложна в использовании и, откровенно говоря, довольно уродлива (не говоря уже о UI-коде, который действительно ужасен).
Сегодня у меня есть интерфейс Треки, который я могу использовать для получения экземпляров классов Отслеживать на основе их идентификатора. Поиск осуществляется через интерфейс Коллекции (прискорбное столкновение пространств имен), который, я думаю, я бы предпочел переместить в Tracks.
Любые данные могут быть изменены третьей стороной в любое время, и это должно быть должным образом отражено в модели и распространяемых уведомлениях об изменениях.
Эти интерфейсы открываются при подключении, возвращая иерархию объектов, которая выглядит следующим образом:




Что касается асинхронного бита, я бы посоветовал проверить java.util.concurrent и особенно интерфейс Future<T>. Будущий интерфейс используется для представления объектов, которые еще не готовы, но создаются в отдельном потоке. Вы говорите, что объекты могут быть изменены в любое время третьей стороной, но я все же предлагаю вам использовать здесь неизменяемые возвращаемые объекты и вместо этого иметь отдельный журнал потоков / событий, на который вы можете подписаться, чтобы вас заметили, когда истекает срок действия объектов. Я мало занимаюсь программированием с пользовательским интерфейсом, но я считаю, что использование Futures для асинхронных вызовов позволит вам иметь гибкий графический интерфейс, а не тот, который ждал ответа от сервера.
Для запросов я бы предложил использовать цепочку методов для создания объекта запроса, и каждый объект, возвращаемый цепочкой методов, должен быть Iterable. Подобно модели Djangos. Скажем, у вас есть QuerySet, который реализует Iterable<Song>. Затем вы можете вызвать allSongs(), который вернет результат, повторяющийся по всем песням. Или allSongs().artist("Beatles"), и у вас будет итерация для всех песен Betles. Или даже allSongs().artist("Beatles").years(1965,1967) и тд.
Надеюсь, это поможет в качестве отправной точки.
@ Staale: Большое спасибо!
Использование Future для асинхронных операций интересно. Единственный недостаток в том, что он не поддерживает обратные вызовы. Но опять же, я попробовал этот подход, и посмотрите, к чему это привело :)
В настоящее время я решаю аналогичную проблему, используя рабочий поток и блокирующую очередь для отправки ответов на входящие команды, но этот подход не очень хорошо переводится.
Удаленные объекты можно изменять, но, поскольку я использую потоки, я стараюсь сохранять объекты неизменными. Моя текущая гипотеза заключается в том, что я буду отправлять уведомления о событиях отслеживания обновлений в форме.
somehandlername(int changes, Track old_track, Track new_track)
или что-то подобное, но тогда я могу получить несколько версий одного и того же трека.
Я обязательно рассмотрю цепочку методов Djangos. Я искал похожие конструкции, но не смог придумать хороший вариант. Возврат чего-то итерируемого интересен, но для выполнения запроса может потребоваться некоторое время, и я бы не хотел фактически выполнять запрос до того, как он будет полностью построен.
Возможно что-то вроде
Tracks.allSongs().artist("Beatles").years(1965,1967).execute()
возвращение будущего может сработать ...
Iterable имеет только метод Iterator get () или что-то в этом роде. Поэтому нет необходимости создавать какие-либо запросы или выполнять какой-либо код, пока вы не начнете итерацию. Это делает выполнение в вашем примере избыточным. Однако поток будет заблокирован до тех пор, пока не будет доступен первый результат, поэтому вы можете рассмотреть возможность использования Executor для запуска кода запроса в отдельном потоке.
@ Staale
Это, конечно, возможно, но, как вы заметили, это приведет к блокировке (дома примерно на 10 секунд из-за спящих дисков), то есть я не могу использовать его для обновления пользовательского интерфейса напрямую.
Я мог бы использовать итератор для создания копии результата в отдельном потоке, а затем отправить ее в пользовательский интерфейс, но, хотя решение итератора само по себе довольно элегантно, оно не очень хорошо вписывается. В конце концов, что-то, реализующее IStructuredContentProvider, должно вернуть массив всех объектов, чтобы отобразить его в TableViewer, поэтому, если я смогу получить что-то подобное из обратного вызова ... :)
Я подумаю еще немного. Я мог бы просто что-нибудь придумать. Это придает коду красивый вид.
Мои выводы на данный момент;
Я не понимаю, использовать ли геттеры для объектов Track или просто раскрывать члены, поскольку объект неизменен.
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 */
}
Любой, кто хочет знать, когда что-то случилось с треком в библиотеке, реализовал бы это ...
interface TrackUpdateListener {
void trackUpdate(Track oldTrack, Track newTrack);
}
Так строятся запросы. Звоните по цепочке, сколько душе угодно. однако жюри по поводу get () еще не принято. Отсутствуют некоторые детали, например, как я должен обрабатывать подстановочные знаки и более сложные запросы с дизъюнкциями. Мне может просто понадобиться некоторая функция обратного вызова завершения, возможно, аналогичная Токен асинхронного завершения, но мы посмотрим на это. Возможно, это произойдет в дополнительном слое.
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();
}
Некоторые примеры:
tracks.allTracks();
tracks.allTracks().artist("Front 242").album("Tyranny (For You)");
Интерфейс треков - это в основном клей между соединением и отдельными треками. Это будет тот, который реализует или управляет кэшированием метаданных, если таковое имеется (как сегодня, но я думаю, что просто удалю его во время рефакторинга и посмотрю, действительно ли оно мне нужно). Кроме того, это обеспечивает обновления треков medialib, так как было бы слишком сложно реализовать его по треку.
interface Tracks {
TrackQuery allTracks();
void addUpdateListener(TrackUpdateListener listener);
void removeUpdateListener(TrackUpdateListener listener);
}