题
我有一个关于公开异步远程接口的最佳方法的问题。
条件如下:
- 协议是异步的
- 第三方可以随时修改数据
- 命令往返可能很重要
- 该模型应该非常适合 UI 交互
- 该协议支持对某些对象的查询,模型也必须支持
作为提高我在该领域缺乏的技能(并总体上温习我的 Java)的一种方法,我开始了 项目 创建一个基于 Eclipse 的前端 xmms2 (如下面所描述的)。
所以,问题是;我应该如何将远程接口公开为简洁的数据模型(在本例中为跟踪管理和事件处理)?
我欢迎任何内容,从一般性讨论到模式名称删除或具体示例和补丁:)
我的主要目标是总体了解此类问题。如果我的项目可以从中受益,那很好,但我严格提出它是为了有一些东西可以引发讨论。
我已经实现了一个协议抽象,我称之为 '客户' (出于遗留原因)这允许我使用方法调用访问大多数公开的功能,我对此很满意,即使它远非完美。
xmms2 守护进程提供的功能包括曲目搜索、元数据检索和操作、更改播放状态、加载播放列表等等。
我正在更新到 xmms2 的最新稳定版本,我想我也可以修复当前实现的一些明显的弱点。
我的计划是在协议接口之上构建一个更好的抽象,允许与守护进程进行更自然的交互。目前的 '模型' 实现很难使用,而且坦率地说相当丑陋(更不用说 UI 代码了,这确实是可怕的 atm)。
今天我有 曲目 我可以使用它来获取实例的接口 追踪 基于他们的 id 的类。搜索是通过 收藏 接口(不幸的命名空间冲突),我认为我宁愿转移到 Tracks。
任何数据都可以由第三方随时修改,这应该正确反映在模型和分发的更改通知中
连接时,通过返回如下所示的对象层次结构来公开这些接口:
- 联系
- 回放 getPlayback()
- 播放、暂停、跳转、当前曲目等
- 暴露播放状态变化
- 曲目 getTracks()
- 跟踪 getTrack(id) 等
- 公开曲目更新
- 集合 getCollection()
- 加载和操作播放列表或命名集合
- 查询媒体库
- 公开集合更新
- 回放 getPlayback()
解决方案
对于异步位,我建议检查一下 java.util.concurrent
, ,尤其是 Future<T>
界面。future 接口用于表示尚未准备好但正在单独线程中创建的对象。您说第三方可以随时修改对象,但我仍然建议您在此处使用不可变的返回对象,而是拥有一个单独的线程/事件日志,您可以订阅以在对象过期时得到通知。我很少使用 UI 进行编程,但我相信使用 Futures 进行异步调用会让您拥有一个响应式 GUI,而不是等待服务器回复的 GUI。
对于查询,我建议使用方法链接来构建查询对象,并且方法链接返回的每个对象应该是 Iterable
. 。与 Django 的模型类似。说你有 QuerySet
它实现了 Iterable<Song>
. 。然后你可以打电话 allSongs()
这将返回迭代所有歌曲的结果。或者 allSongs().artist("Beatles")
, ,您将获得所有 Betles 歌曲的迭代。甚至 allSongs().artist("Beatles").years(1965,1967)
等等。
希望这作为一个起点有所帮助。
其他提示
Iterable 仅具有 Iterator get() 或类似方法。因此,在实际开始迭代之前,无需构建任何查询或执行任何代码。它确实使示例中的执行变得多余。但是,线程将被锁定,直到第一个结果可用,因此您可以考虑使用执行器在单独的线程中运行查询代码。
@斯塔莱: :非常感谢!
使用 Future 进行异步操作很有趣。唯一的缺点是它不提供回调。但话又说回来,我尝试了这种方法,看看它给我带来了什么:)
我目前正在使用工作线程和阻塞队列来解决类似的问题来分派传入的命令回复,但这种方法的转换效果不是很好。
远程对象可以修改,但由于我确实使用线程,所以我尝试保持对象不可变。我当前的假设是我将发送有关表单上跟踪更新的通知事件
somehandlername(int changes, Track old_track, Track new_track)
或类似的,但我最终可能会得到同一首曲目的多个版本。
我一定会研究 Django 的方法链。我一直在研究一些类似的结构,但一直无法想出一个好的变体。返回可迭代的东西很有趣,但是查询可能需要一些时间才能完成,而且我不想在查询完全构建之前实际执行查询。
也许像
Tracks.allSongs().artist("Beatles").years(1965,1967).execute()
返回 Future 可能会起作用......
到目前为止我的结论;
由于对象是不可变的,我对是否对 Track 对象使用 getter 还是仅仅公开成员感到犹豫。
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);
}