公开远程接口或者对象模型
我对公开异步远程接口的最佳方法有疑问。
条件如下:
- 该协议是异步的
- 第三方可以随时修改数据
- 命令往返可能很重要
- 该模型应非常适合UI交互
- 该协议支持对某些对象的查询,因此模型也必须支持
为了提高我在这方面缺乏的技能(并全面复习Java),我启动了一个项目,为xmms2创建一个基于Eclipse的前端(如下所述)。
所以,问题是;我应该如何将远程接口公开为整洁的数据模型(在这种情况下,是跟踪管理和事件处理)?
我欢迎从通用讨论到模式名称删除或者具体示例和补丁的所有内容:)
我的主要目标是总体上学习此类问题。如果我的项目可以从中受益,那很好,但是我严格地提出它,以便可以展开一些讨论。
我已经实现了一个协议抽象,我称之为"客户端"(出于传统原因),它使我能够使用我满意的方法调用来访问大多数公开的功能,即使它远非完美。
xmms2守护程序提供的功能包括曲目搜索,元数据检索和操作,更改播放状态,加载播放列表等。
我正在更新xmms2的最新稳定版本,并且我认为我还可以修复当前实现中的一些明显缺陷。
我的计划是在协议接口的基础上构建更好的抽象,以便与守护程序进行更自然的交互。当前的"模型"实现很难使用,并且坦率地说非常丑陋(更不用说UI代码了,这是真正可怕的atm)。
今天,我有了Tracks接口,可用于根据其ID获取Track类的实例。我认为,搜索是通过Collections接口执行的(不幸的是,名称空间冲突),我希望将其移至Tracks。
任何数据都可以由第三方随时修改,并且应该在模型和更改通知中适当反映
通过返回如下所示的对象层次结构,可以在连接时公开这些接口:
- 公开播放状态更改
- 公开曲目更新
- 查询媒体库
- 公开馆藏更新
解决方案
回答
对于异步位,我建议检查" java.util.concurrent",尤其是" Future <T>"接口。 future接口用于表示尚未准备好但正在单独的线程中创建的对象。我们说对象可以随时由第三方修改,但是我仍然建议我们在此处使用不可变的返回对象,而要有一个单独的线程/事件日志,我们可以订阅该线程/事件日志以在对象过期时引起注意。我很少使用UI进行编程,但是我相信使用Futures进行异步调用将使我们拥有响应式GUI,而不是等待服务器回复的GUI。
对于查询,我建议使用方法链来构建查询对象,方法链返回的每个对象都应该是"可迭代的"。与Django模型相似。假设我们具有实现Iterable <Song>的QuerySet。然后,我们可以调用allSongs()
,这将返回遍历所有乐曲的结果。或者allSongs()。artist(" Beatles")
,我们将对所有Betles歌曲具有可迭代性。甚至allSongs()。artist(" Beatles")。years(1965,1967)
等。
希望这可以作为一个起点。
回答
@Staale:谢谢一堆!
使用Future进行异步操作很有趣。唯一的缺点是它不提供回调。但是话又说回来,我尝试了这种方法,然后看那是什么让我:)
我目前正在使用工作线程和阻塞队列来调度传入的命令答复来解决类似的问题,但是这种方法的翻译效果不是很好。
可以修改远程对象,但是由于我确实使用线程,因此我尝试使对象保持不变。我目前的假设是,我将在表单上的跟踪更新中发送通知事件
somehandlername(int changes, Track old_track, Track new_track)
或者类似的内容,但随后我可能会得到同一曲目的多个版本。
我一定会研究Django的方法链接。我一直在寻找一些类似的构造,但一直无法提出一个好的变体。返回可迭代的内容很有趣,但是查询可能需要一些时间才能完成,并且我不希望在完全构建查询之前实际执行查询。
也许像
Tracks.allSongs().artist("Beatles").years(1965,1967).execute()
返回未来可能会工作...
回答
Iterable仅具有Iterator get()之类的方法。因此,在我们真正开始迭代之前,无需构建任何查询或者执行任何代码。它确实使示例中的执行成为多余。但是,线程将被锁定,直到获得第一个结果为止,因此我们可以考虑使用执行程序在单独的线程中运行查询代码。
回答
@Staale
当然有可能,但是正如我们所注意到的,这将使其阻塞(由于磁盘处于休眠状态,在家里大约10秒钟),这意味着我不能使用它直接更新UI。
我可以使用迭代器在单独的线程中创建结果的副本,然后将其发送到UI,但是,尽管迭代器解决方案本身非常好用,但却不太适合。最后,实现IStructuredContentProvider的对象需要返回所有对象的数组才能在TableViewer中显示它,因此,如果我可以从回调中获取类似的东西... :)
我会再考虑一下。我也许可以解决一些问题。它确实使代码看起来不错。
回答
到目前为止我的结论;
由于该对象是不可变的,我是否应该为该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); }