公开远程接口或对象模型

发布于 2024-07-04 18:40:11 字数 1999 浏览 6 评论 0原文

我有一个关于公开异步远程接口的最佳方法的问题。

条件如下:

  • 协议是异步的
  • 第三方可以随时修改数据
  • 命令往返可能很重要
  • 模型应该非常适合 UI 交互
  • 协议支持对某些对象的查询,模型也必须支持

作为提高我在该领域缺乏的技能(并总体上温习 Java)的一种方法,我启动了一个项目< /a> 为 xmms2 创建基于 Eclipse 的前端(如下所述)。

所以,问题是; 我应该如何将远程接口公开为一个简洁的数据模型(在本例中,跟踪管理和事件处理)?

我欢迎任何内容,从一般性讨论到模式名称删除或具体示例和补丁:)


我在这里的主要目标是总体上了解此类问题。 如果我的项目可以从中受益,那很好,但我严格提出它是为了有一些东西可以引发讨论。

我已经实现了一个协议抽象,我称之为 'client' (用于遗留原因),这使我可以使用方法调用访问大多数公开的功能,我对此感到满意,即使它远非完美。

xmms2 守护进程提供的功能包括曲目搜索、元数据检索和操作、更改播放状态、加载播放列表等等。

我正在更新到 xmms2 的最新稳定版本,我想我也可以修复当前实现的一些明显的弱点。

我的计划是在协议接口之上构建一个更好的抽象,允许与守护进程进行更自然的交互。 当前的 'model' 实现很难使用,而且坦率地说相当丑陋(更不用说 UI 代码了,这真是太可怕了)。

今天我有 曲目< /a> 接口,我可以使用它来获取 根据班级 ID 跟踪 班级。 搜索是通过 集合执行的 接口(不幸的命名空间冲突),我认为我宁愿转移到 Tracks。

第三方可以随时修改任何数据,这应该正确反映在模型和分发的更改通知中

这些接口在连接时通过返回如下所示的对象层次结构来公开

    • 回放 getPlayback()
      • 播放、暂停、跳转、当前曲目等
      • 公开播放状态更改
    • 曲目 getTracks()
      • 跟踪 getTrack(id) 等
      • 公开曲目更新
    • 集合 getCollection()
      • 加载和操作播放列表或命名集合
      • 查询媒体库
      • 公开集合更新

I have a question on the best way of exposing an asynchronous remote interface.

The conditions are as follows:

  • The protocol is asynchronous
  • A third party can modify the data at any time
  • The command round-trip can be significant
  • The model should be well suited for UI interaction
  • The protocol supports queries over certain objects, and so must the model

As a means of improving my lacking skills in this area (and brush up my Java in general), I have started a project to create an Eclipse-based front-end for xmms2 (described below).

So, the question is; how should I expose the remote interface as a neat data model (In this case, track management and event handling)?

I welcome anything from generic discussions to pattern name-dropping or concrete examples and patches :)


My primary goal here is learning about this class of problems in general. If my project can gain from it, fine, but I present it strictly to have something to start a discussion around.

I've implemented a protocol abstraction which I call 'client' (for legacy reasons) which allows me to access most exposed features using method calls which I am happy with even if it's far from perfect.

The features provided by the xmms2 daemon are things like track searching, meta-data retrieval and manipulation, change playback state, load playlists and so on and so forth.

I'm in the middle of updating to the latest stable release of xmms2, and I figured I might as well fix some of the glaring weaknesses of my current implementation.

My plan is to build a better abstraction on top of the protocol interface, one that allows a more natural interaction with the daemon. The current 'model' implementation is hard to use and is frankly quite ugly (not to mention the UI-code which is truly horrible atm).

Today I have the Tracks interface which I can use to get instances of Track classes based on their id. Searching is performed through the Collections interface (unfortunate namespace clash) which I'd rather move to Tracks, I think.

Any data can be modified by a third party at any time, and this should be properly reflected in the model and change-notifications distributed

These interfaces are exposed when connecting, by returning an object hierarchy that looks like this:

  • Connection
    • Playback getPlayback()
      • Play, pause, jump, current track etc
      • Expose playback state changes
    • Tracks getTracks()
      • Track getTrack(id) etc
      • Expose track updates
    • Collections getCollection()
      • Load and manipulate playlists or named collections
      • Query media library
      • Expose collection updates

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

烟雨凡馨 2024-07-11 18:40:11

到目前为止我的结论;

由于对象是不可变的,我对是否对 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() 还没有定论。 缺少一些细节,例如我应该如何处理通配符和带有析取的更高级查询。 我可能只需要一些完成回调功能,可能类似于 异步完成令牌< /a>,但我们会看到这一点。 也许这会在附加层中发生。

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

My conclusions so far;

I am torn on whether to use getters for the Track objects or just expose the members since the object is immutable.

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

Anybody who wants to know when something happened to a track in the library would implement this...

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

This is how querys are built. Chain calls to your hearts content. the jury is still out on the get() though. There are some details missing, such as how I should handle wildcards and more advanced queries with disjunctions. I might just need some completion callback functionality, perhaps similar to the Asynchronous Completion Token, but we'll see about that. Perhaps that will happen in an additional layer.

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

Some examples:

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

The tracks interface is mostly just the glue between the connection and the individual tracks. It will be the one implementing or managing meta-data caching, if any (as today, but I think I'll just remove it during the refactoring and see if I actually need it). Also, this provides medialib track updates as it would just be too much work to implement it by track.

interface Tracks {
    TrackQuery allTracks();

    void addUpdateListener(TrackUpdateListener listener);
    void removeUpdateListener(TrackUpdateListener listener);
}
我的痛♀有谁懂 2024-07-11 18:40:11

@Staale:非常感谢!

使用 Future 进行异步操作很有趣。 唯一的缺点是它不提供回调。 但话又说回来,我尝试了这种方法,看看它给我带来了什么:)

我目前正在使用工作线程和阻塞队列来分派传入的命令回复来解决类似的问题,但这种方法翻译得不太好。

远程对象可以修改,但由于我确实使用线程,所以我尝试保持对象不可变。 我当前的假设是,我将在表单

somehandlername(int changes, Track old_track, Track new_track)

或类似形式上发送有关曲目更新的通知事件,但最终我可能会得到同一曲目的多个版本。

我一定会研究 Django 的方法链。 我一直在研究一些类似的结构,但一直无法想出一个好的变体。 返回可迭代的东西很有趣,但是查询可能需要一些时间才能完成,而且我不想在查询完全构建之前实际执行查询。

也许像

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

返回 Future 之类的东西可能会起作用......

@Staale: Thanks a bunch!

Using Future for the async operations is interesting. The only drawback being that it is doesn't provide callbacks. But then again, I tried that approach, and look where that got me :)

I'm currently solving a similar problem using a worker thread and a blocking queue for dispatching the incoming command replies, but that approach doesn't translate very well.

The remote objects can be modified, but since I do use threads, I try to keep the objects immutable. My current hypothesis is that I will send notification events on track updates on the form

somehandlername(int changes, Track old_track, Track new_track)

or similar, but then I might end up with several versions of the same track.

I'll definitely look into Djangos method chaining. I've been looking at some similar constructs but haven't been able to come up with a good variant. Returning something iterable is interesting, but the query could take some time to complete, and I wouldn't want to actually execute the query before it's completely constructed.

Perhaps something like

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

returning a Future might work...

回心转意 2024-07-11 18:40:11

Iterable 仅具有 Iterator get() 或类似方法。 因此,在实际开始迭代之前,无需构建任何查询或执行任何代码。 它确实使示例中的执行变得多余。 但是,线程将被锁定,直到第一个结果可用,因此您可以考虑使用执行器在单独的线程中运行查询代码。

Iterable only has the method Iterator get() or somesuch. So no need to build any query or execute any code until you actually start iterating. It does make the execute in your example redundant. However, the thread will be locked until the first result is available, so you might consider using an Executor to run the code for the query in a separate thread.

扬花落满肩 2024-07-11 18:40:11

@Staale

这当然有可能,但正如你所注意到的,这会使其阻塞(在家里由于磁盘休眠而阻塞大约 10 秒),这意味着我无法使用它直接更新 UI。

我可以使用迭代器在单独的线程中创建结果的副本,然后将其发送到 UI,但是虽然迭代器解决方案本身相当优雅,但它不太适合。 最后,实现 IStructuredContentProvider 需要返回所有对象的数组,以便在 TableViewer,所以如果我能逃脱得到类似的东西回调...:)

我会再考虑一下。 我也许能解决一些问题。 它确实使代码看起来很漂亮。

@Staale

It is certainly possibly, but as you note, that would make it blocking (at home for something like 10 seconds due to sleeping disks), meaning I can't use it to update the UI directly.

I could use the iterator to create a copy of the result in a separate thread and then send that to the UI, but while the iterator solution by itself is rather elegant, it won't fit in very well. In the end, something implementing IStructuredContentProvider needs to return an array of all the objects in order to display it in a TableViewer, so if I can get away with getting something like that out of a callback... :)

I'll give it some more thought. I might just be able to work out something. It does give the code a nice look.

嗳卜坏 2024-07-11 18:40:11

对于异步位,我建议检查 java.util.concurrent,尤其是 Future 接口。 future 接口用于表示尚未准备好但正在单独线程中创建的对象。 您说第三方可以随时修改对象,但我仍然建议您在此处使用不可变的返回对象,而是拥有一个单独的线程/事件日志,您可以订阅以在对象过期时得到通知。 我很少使用 UI 进行编程,但我相信使用 Futures 进行异步调用会让您拥有一个响应式 GUI,而不是等待服务器回复的 GUI。

对于查询,我建议使用方法链接来构建查询对象,并且方法链接返回的每个对象都应该是Iterable。 与 Django 的模型类似。 假设您有实现 IterableQuerySet。 然后,您可以调用 allSongs() ,这将返回迭代所有歌曲的结果。 或者 allSongs().artist("Beatles"),您将获得所有 Betles 歌曲的可迭代对象。 甚至是 allSongs().artist("Beatles").years(1965,1967) 等等。

希望这作为一个起点有所帮助。

For the asynchronous bit, I would suggest checking into java.util.concurrent, and especially the Future<T> interface. The future interface is used to represent objects which are not ready yet, but are being created in a separate thread. You say that objects can be modified at any time by a third party, but I would still suggest you use immutable return objects here, and instead have a separate thread/event log you can subscribe to to get noticed when objects expire. I have little programming with UIs, but I believe using Futures for asynchronous calls would let you have a responsive GUI, rather than one that was waiting for a server reply.

For the queries I would suggest using method chaining to build the query object, and each object returned by method chaining should be Iterable. Similar to how Djangos model is. Say you have QuerySet which implements Iterable<Song>. You can then call allSongs() which would return a result iterating over all Songs. Or allSongs().artist("Beatles"), and you would have an iterable over all Betles songs. Or even allSongs().artist("Beatles").years(1965,1967) and so on.

Hope this helps as a starting place.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文