如何选择适当的Java数据结构来建模1-n关系映射?

3

情境

我将尽可能简洁地说明。基本上,参考这个类图,我有一个门面,管理一组SocketManager(每个SocketManager管理一个Socket连接)。每个SocketManager使用唯一的SocketUserId登录到远程服务器。此外,每个SocketManager将接受来自客户端的消息,这些消息将发送到特定列表中的Receipients。为了讨论方便,可以将这些Receipients视为由名称标识的远程数据桶。

客户端将以以下方式发送数据:

SocketFacade facade = ...;

byte[] data = ...

facade.sendData( receipient, data );

SocketFacade 启动时,它将查询一个mysql表,该表返回 SocketUserIdReceipients 之间的1-m关系。我将使用MultiValuedMap来表示这种1-m关系。通过迭代地遍历这个map,多个SocketManager将被启动。
(1) Map< SocketUserId, List<Receipient> > 

例如,假设我们有两个SocketManager,分别具有"alice"和"tom"的SocketUserId
            +----SocketManager1 ( "alice" ) for Receipients { "B", "C" }
            |
 SocketFacade 
            |
            +----SocketManager2 ( "tom" ) for Receipients { "A", "D" }

问题

我不知道如何实现sendData方法。基本上,我需要一种方法将收件人(例如“B”)映射到其负责的SocketManager(例如SocketManager1)。

假设我这样做:

(2) Map< SocketUserId, SocketManager >
(3) Map< Receipient, SocketUserId >
  • Would I need a SoftReference for the value in (2) ?
  • Should I just map from Receipient directly to SocketManager ?
  • SocketFacade also supports methods that will mutate the relationship represented by (1). If I write to the database, the in-memory data structures in (1), (2), & (3) will need to change in sync. SocketFacade must be thread safe as well. Initial idea is to have some sort of a publish subscribe system whereby an add/remove to the DB will cause the changes to propagate via callbacks.

    interface Callback
    {
        void receipientAdded( Receipient r );
        void receipientDeleted( Receipient r );
    }
    

1
这里应该是“recipient”,而不是“receipient”。代码中的拼写错误是烦人的错误源。 - Svante
3个回答

1
在抽象层面上,您有:1个与n个OtherThing相关的Thing。您可以通过创建一个特殊的关系对象来建模,该对象引用Thing并包含对n个OtherThing的java.util.List的引用。这样可以使您的域对象(Things)不受关系信息的干扰。但是,如果看起来合适,将java.util.List直接添加到类Thing(或其子类)中会更简单。List变量可以保留为空,并且仅在需要建立关系时使用java.util.ArrayList进行填充。

1
在每个“接收者”中保留对“SocketManager”的引用。这样,您就可以避免使用映射(需要更多的RAM,速度较慢,并且不添加任何价值)。
在“SocketManager”中,保持“Receipient”的列表。当您添加和删除“接收者”时,请更新指向“SocketManager”的指针。
在“SocketFacade”中,您需要一个映射,它以“SocketUserId”为参数并返回“SocketManager”。该映射通过查询映射中的ID进行填充。在所有管理器都存在之后,将接收者添加到每个管理器中。这需要两个SQL查询。
使用任何ORM工具都非常容易映射此内容。

1
在我看来,将SocketManager放入Recipient中并不是很好的做法。这样一来,Recipient就会向SocketFacade的客户端/调用者公开SocketManager。这背后的问题是“Recipient需要知道哪个SocketManager负责他”。根据我的理解,这并不是期望的结果,应该隐藏在facade后面。这只是facade设置的问题。到目前为止,我理解得对吗? - rudolfson
基本上,我想将提供给SocketFacade#sendData(..)的消息分派到正确的SocketManager。这就是全部。收件人由字符串标识符标识。另一个设计标准是能够添加和删除由特定SocketManager处理的接收者(这涉及内存更改和数据库更新)。回答您的问题,最好不要让收件人知道它所关联的SocketManager的信息。 - ashitaka
@rudolfson:啊,那我误解了。我以为这就像“向SocketManager的所有接收者发送消息”。嗯,回到绘图板上。 - Aaron Digulla

1

由于SocketUserId和SocketManager之间存在1对1的关系,您是否可以在SocketMananger中添加对SocketUserId的引用,并在Recipient中添加对SocketManager的引用?

然后,在您的facade中,您将拥有一个包含收件人和SocketManager列表的Map。在sendData函数中,您将从地图中获取收件人,并从该收件人获取其SocketManager的引用。

此致

Guillaume


一开始我对将SocketManager放入Map中感到担忧,因为它底层运行着2个线程和一个Socket通道。我计划持有一个List<SocketManager>,这样方便我进行迭代并调用shutdown。你觉得呢?是的,SocketManager将引用一个SocketUserId(1-1)。由于一个接收者只与一个SocketManager相关联,那么我为什么需要一个SocketManager列表呢?根据你的想法,我只需要Map<RecipientKey,Recipient>。我理解你的意思了吗? - ashitaka
1
@ashitaka:嗯?从你的例子来看,SocketManager 到 Recipient 是一对多关系! - Aaron Digulla
@AaronDigulla 我犯了一个错误。我本想说的是,“因为一个SocketUserId只与一个SocketManager相关……”。从SocketManager到Recipient是1:N。 - ashitaka
是的。从我的角度来看,SocketManager列表并不是必需的,但正如您所说,您可能想管理socketManager(启动、停止、获取当前状态、获取一些有关流量的信息...)。 - PATRY Guillaume

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接