13

所以我很惊讶在 google 和 stackoverflow 上进行搜索并没有返回更多结果。

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

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

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

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

4

8 回答 8

13

没有 100% 万无一失的方法来保持完整性。

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

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

class Parent {

  private Collection<Child> 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);
   }
}

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

于 2012-04-10T11:02:48.583 回答
5

也许您没想到会有一个复杂(且零代码)的答案,但没有解决方案可以按照您的意图构建防弹 API。这不是因为范式(OO)或平台(Java),而只是因为您进行了错误的分析。在事务世界中(每个模拟现实生活问题及其随时间演变的系统都是事务性的)此代码在某些时候中断:

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

// access
j2.setSomeProperty(j1.someProperty);

因为当时j1.someProperty被访问了,j1甚至j2不存在:)

TL;博士

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

  1. 有很多
  2. 属于

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

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

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

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

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

于 2012-04-10T16:28:40.983 回答
2

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

在 Customer的add方法中,查看JobUnit包裹的Job是否与自己的id相同的customer ID,如果没有则抛出异常。

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

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

public class Customer {

    Set<JobUnit> jobs=new HashSet<JobUnit>();    
    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;
    }

}

和工作类:

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 并删除方法来设置替换它。

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

于 2012-04-10T14:16:52.543 回答
1

如果 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
}

...如果所有权是相反的,只需用客户代替工作。

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

于 2012-04-10T10:09:23.523 回答
0

制作一个保持一致性的适当的 setter 函数。例如,每当您创建作业时,您都会在构造函数中提供客户。然后,作业构造函数将自己添加到客户的作业列表中。或者,每当您向客户添加工作时,添加功能必须检查工作的客户是否就是要添加到的客户。或者将这个和类似的东西结合起来以满足您的需求。

于 2012-04-10T10:06:42.470 回答
-1

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

private List<Job> jobs; 

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

顺便说一句,您可以使用各种集合(集合、列表、地图)

于 2012-04-10T10:27:51.543 回答
-1

我知道这已经晚了,但我认为另一种方法是从不同的角度看待问题。由于客户拥有由客户分配或为客户完成的所有工作的集合,因此您可以将工作类视为客户的子类,其中包含客户完成所有工作的额外信息。然后你只需要在主类中维护客户 ID,它就会被继承。这种设计将确保每个工作都可以链接到一个客户。此外,如果您想知道客户有多少工作,那么您也会得到。

于 2016-10-18T20:48:55.937 回答
-1

很抱歉,我知道这已经很晚了,但我遇到了类似的问题,我觉得最好的解决方案是遵循继承模型。将工作视为由特定客户完成/分配的工作。因此,在这种情况下,客户将是一个超类,而 Job(Lets call is customer job) 是一个子类,因为没有客户就无法存在 Job。客户还会有一个作业列表,主要是为了便于获取数据。直觉上这是没有意义的,因为 Job 和 Customer done 似乎有任何关系,但是一旦你看到 Job 没有客户就无法存在,它就变成了客户的延伸。

于 2016-11-08T10:00:48.973 回答