编程一对多关系

所以我很惊讶在谷歌和stackoverflow上搜索不会返回更多结果。

在OO编程中(我使用的是java),你如何正确地实现一对多关系?

我有一个class级Customer和class级Job 。 我的申请是为一家为客户完成工作的虚构公司。 我当前的实现是这样的, Job类与Customer类没有任何关系,根本没有对它的引用。 Customer类使用集合和方法来保存,检索和修改有关已为客户分配和/或完成的作业的信息。

问题是,如果我想知道某个特定Job客户是做什么的,该怎么办? 我只发现了相关的这篇文章: http : //www.ibm.com/developerworks/webservices/library/ws-tip-objrel3/index.html 。

根据作者的实现,我会让Job构造函数接受一个Customer参数,然后存储它以便我可以检索它。 但是,我完全不能保证这个模型可以保持一致 。 没有为工作的相关客户设置工作作为该工作不适用的客户,并为其他人完成的客户添加工作。 任何有关这方面的帮助将不胜感激。

没有100%可靠的方法来维护完整性。

通常采用的方法是使用一种方法来构建关系,并在同一方法中构建另一个方向。 但是,正如你所说,这并不能阻止任何人搞乱它。

下一步是使一些方法可以访问包,这样至少与你的无关的代码不能破坏它:

 class Parent { private Collection children; //note the default accessibility modifiers void addChild(Child) { children.add(child); } void removeChild(Child) { children.remove(child); } } class Child { private Parent parent; public void setParent(Parent parent){ if (this.parent != null) this.parent.removeChild(this); this.parent = parent; this.parent.addChild(this); } } 

实际上,您不会经常在课堂上模拟这种关系。 相反,您将在某种存储库中查找所有子项以查找父项。

也许你没有想到一个复杂的(和零代码)答案,但没有解决方案来按照你想要的方式构建你的防弹API 。 并不是因为范例(OO)或平台(Java),而是因为你做了错误的分析。 在事务世界中(每个模拟现实生活问题及其随时间演变的系统都是事务性的 )这段代码在某些时候会破坏

 // create Job j1 = ... Job j2 = ... ... // modify j1.doThis(); ... // access j2.setSomeProperty(j1.someProperty); 

因为在访问j1.somePropertyj1j2甚至不存在:)

TL; DR

对此的长期答案是不变性 ,它还介绍了生命周期交易的概念。 所有其他答案告诉你如何做 ,而我想概述为什么 。 一对多关系有两个方面

  1. 有很多
  2. 属于

只要客户A有工作B ,工作B属于客户A ,您的系统就是一致A 。 您可以通过多种方式实现此目的,但这必须在事务中发生,即由简单事件组成的复杂操作,并且在事务执行完毕之前系统必须是不可用的。 这看起来太抽象而且与你的问题无关吗? 不,它不是:)事务系统确保客户端只有在所有这些对象都处于有效状态时才能访问系统的对象,因此只有整个系统是一致的。 从其他答案中,您可以看到解决某些问题所需的处理量,因此保证会产生成本: 性能 。 这就是为什么Java(和其他通用OO语言)无法解决您的问题的简单解释。

当然,OO语言可用于模拟事务世界和访问它,但必须特别小心,必须施加一些约束并且客户端开发人员需要特殊的编程风格。 通常,事务系统提供两个命令: 搜索 (aka查询)和锁定 。 查询的结果是不可变的 :它是系统在拍摄的特定时刻的照片(即副本),修改照片显然对现实世界没有影响。 如何修改系统? 平时

  1. 如果/需要,锁定系统(或部分系统)
  2. 找到一个对象:返回一个可以在本地读取和写入的真实对象的副本 (照片)
  3. 修改本地副本
  4. 提交修改后的对象,即让系统根据提供的输入更新其状态
  5. 丢弃对(现在无用的)本地对象的任何引用:系统已更改已更改,因此本地副本不是最新的。

(顺便说一句,你能看到生命周期的概念如何应用于本地和远程对象吗?)

您可以使用Set s, final修饰符等,但在您引入事务和不变性之前,您的设计将存在缺陷。 通常,Java应用程序由数据库提供支持,数据库提供事务function,并且DB通常与ORM(例如Hibernate)耦合以编写面向对象的代码。

您可以通过使用像HashSet这样的Set实现而不是使用其他数据结构来确保没有重复项。 而不是将Job添加到客户,在Job类中创建具有私有构造函数的最终内部类。 这确保了包装器内部类只能由作业对象创建。 让Job构造函数接受jobID和customer作为参数。 为了保持一致性 – 如果客户是Null抛出exception,则不应创建虚拟作业。

在Customer的添加方法中,检查JobUnit包装的Job是否具有与其自己的id相同的客户ID,如果不是则抛出Exception。

替换Job类中的客户时,使用Customer类提供的方法删除JobUnit ,并将其自身添加到新客户,并将客户引用更改为新传递的客户。 这样你可以更好地推理你的代码。

这是您的客户类可能是什么样子。

 public class Customer { Set jobs=new HashSet(); private Long id; public Customer(Long id){ this.id = id; } public boolean add(JobUnit unit) throws Exception{ if(!unit.get().getCustomer().id.equals(id)) throw new Exception(" cannot assign job to this customer"); return jobs.add(unit); } public boolean remove(JobUnit unit){ return jobs.remove(unit); } public Long getId() { return id; } } 

工作class:

 public class Job { Customer customer; private Long id; 

最终JobUnit单位;

 public Job(Long id,Customer customer) throws Exception{ if(customer==null) throw new Exception("Customer cannot be null"); this.customer = customer; unit= new JobUnit(this); this.customer.add(unit); } public void replace(Customer c) throws Exception{ this.customer.remove(unit); c.add(unit); this.customer=c; } public Customer getCustomer(){ return customer; } /** * @return the id */ public Long getId() { return id; } public final class JobUnit{ private final Job j; private JobUnit(Job j){ this.j = j; } public Job get(){ return j; } } } 

但有一点我很好奇,为什么你甚至需要为客户对象添加工作? 如果要检查的是要查看哪个客户已分配到哪个作业,只需检查作业即可获得该信息。 一般来说,除非不可避免,否则我尽量不创建循环引用。 此外,如果不需要在创建作业时替换客户,只需在Job类中创建客户字段Final并删除设置替换它的方法。

应该在数据库中维护为作业分配客户的限制,并且应该将数据库条目用作检查点。 至于为客户添加为其他人完成的工作,您可以检查工作中的客户参考,以确保添加工作的客户与其拥有的工作相同,甚至更好 – 只需删除任何参考客户的工作,它将简化你的事情

如果Customer对象拥有该关系,那么您可以这样做:

 Job job = new Job(); job.setStuff(...); customer.addJob(Job job) { this.jobs.add(job); job.setCustomer(this); //set/overwrite the customer for this job } //in the job class public void setCustomer(Customer c) { if (this.customer==null) { this.customer = c; } // for the else{} you could throw a runtime exception } 

…如果所有权是相反的方式,只需替代客户的工作。

这个想法是让关系的所有者保持一致性。 双向关系通常意味着一致性管理位于两个实体中。

做一个适当的setterfunction,保持一致性。 例如,无论何时创建作业,都要在构造函数中提供客户。 然后,作业构造函数将自己添加到客户的作业列表中。 或者,无论何时向客户添加作业,添加function都必须检查作业的客户是否是要添加到的客户。 或者这个和类似的东西的组合,以满足您的需求。

只需在具有其他对象的对象中实现某种集合,例如在客户中,您可以说:

 private List jobs; 

然后通过使用getter和setter,您可以将值作业添加到此列表中。 这是基本的OO东西,我认为你没有在互联网上搜索得足够多。 有很多关于这些主题的信息。

顺便说一句,你可以使用所有类型的集合(集合,列表,地图)

我知道这已经很晚了,但我认为另一种方法是稍微改变一下这个问题。 由于客户持有由客户分配或完成的所有工作的集合,因此您可以将工作类视为客户的子类,其中包含客户完成所有工作的额外信息。 然后,您只需要在主类中维护客户ID,它就会被inheritance。 此设计将确保每个作业都可以链接到客户。 此外,如果对于客户,您想要了解有多少工作也可以获得。

对不起,我知道这已经很晚了,但我遇到了类似的问题,我认为最好的解决方案是遵循inheritance模型。 将工作视为由特定客户完成/工作的工作。 因此,在这种情况下,客户将是一个超级类,其中Job(让我们称之为客户工作)是一个子类,因为如果没有客户,Job就不能存在。 客户还可以拥有一份工作列表,主要是为了便于数据获取。 直觉上这是没有意义的,因为工作和客户完成似乎有任何关系,但是一旦你看到没有客户就不能存在工作,它只是成为客户的延伸。