简体   繁体   English

编程一对多关系

[英]Programming a one-to-many relationship

So I am surprised that doing a search on google and stackoverflow doesn't return more results.所以令我惊讶的是,在 google 和 stackoverflow 上进行搜索并没有返回更多结果。

In OO programming (I'm using java), how do you correctly implement a one-to-many relationship?在 OO 编程中(我使用的是 java),如何正确实现一对多关系?

I have a class Customer and class Job .我有一个 class Customer和 class Job My application is for a fictious company that completes jobs for customers.我的申请是针对一家为客户完成工作的虚构公司。 My current implementation is so that the Job class doesn't have anything to do with the Customer class, there is no reference to it at all.我目前的实施是Job class 与Customer class 没有任何关系,根本没有引用它。 The Customer class uses a collection and methods to hold, retrieve and modify information about the Jobs that have been assigned by and/or completed for a customer. Customer class 使用集合和方法来保存、检索和修改有关已由客户分配和/或为客户完成的工作的信息。

The question is, what if I'd want to find out for which customer a particular Job has been done?问题是,如果我想找出为哪个客户完成了特定Job怎么办? I've only found this article that's relevant: http://www.ibm.com/developerworks/webservices/library/ws-tip-objrel3/index.html .我只找到了这篇相关的文章: http://www.ibm.com/developerworks/webservices/library/ws-tip-objrel3/index.html

According to the implementation of the author, I would let the Job constructor take a Customer parameter, and store it so I can retrieve it.根据作者的实现,我会让Job构造函数接受一个Customer参数,并存储它以便我可以检索它。 However, I see no guarantee at all that this model can be consistent .但是,我完全看不到这个 model 可以保持一致的保证。 There are no restirctions to set the related customer for a job as a customer that the job was not for, and add jobs to customers that were done for someone else.没有限制将某项工作的相关客户设置为该工作不针对的客户,并向为其他人完成的客户添加工作。 Any help on this would be appreciated.对此的任何帮助将不胜感激。

There's no 100% surefire way to maintain the integrity.没有 100% 万无一失的方法来保持完整性。

The approach which is usually taken is to use one method to construct the relationship, and construct the other direction in that same method.通常采取的做法是用一种方法构造关系,用同样的方法构造另一个方向。 But, as you say, this doesn't keep anyone from messing with it.但是,正如您所说,这并不能阻止任何人弄乱它。

The next step would be to make some of the methods package-accessible, so that at least code which has nothing to do with yours can't break it:下一步是使一些方法包可访问,这样至少与您的方法无关的代码不会破坏它:

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

In reality, you won't often model this relationship in your classes.实际上,您不会经常在课堂上 model 这种关系。 Instead, you will look up all children for a parent in some kind of repository.相反,您将在某种存储库中查找父项的所有子项。

Maybe you didn't expect a complex (and zero-code) answer, but there is no solution to build your bombproof API the way you intend it .也许您没想到会得到一个复杂(和零代码)的答案,但是没有按照您的预期方式构建防弹 API 的解决方案 And it's not because the paradigm (OO) or the platform (Java), but only because you made a wrong analysis.这不是因为范式(OO)或平台(Java),而只是因为您进行了错误的分析。 In a transactional world (every system that models real life problems and their evolution over time is transactional ) This code will ever break at some point:在事务世界中(每个模拟现实生活问题及其随时间演变的系统都是事务性的)此代码将在某个时刻中断

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

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

because at the time j1.someProperty is accessed, j1 and j2 could not even exist:)因为在访问j1.someProperty时, j1j2甚至不存在:)

TL;DR长话短说

The long answer to this is immutability , and it also introduces the concepts of life cycle and transactions .对此的长答案是不变性,它还引入了生命周期事务的概念。 All other answers tell you how to do it, instead I want to outline why .所有其他答案都会告诉您如何做,而我想概述原因 A one-to-many relationship has two sides一对多关系有两个方面

  1. has many有很多
  2. belongs to属于

Your system is consistent as long as if Customer A has Job B , the Job B belongs to Customer A .只要客户A有工作B ,工作B属于客户A ,您的系统就是一致的。 You can implement this in a number of ways, but this must happen in a transaction , ie a complex action made of simple ones, and the system must be unavailble until the transaction has finished execution.您可以通过多种方式实现它,但这必须发生在事务中,即由简单操作组成的复杂操作,并且在事务执行完成之前系统必须不可用。 Does this seem too abstract and unrelated to your question?这看起来是否太抽象并且与您的问题无关? No, it isn't:) A transactional system ensures that clients can access system's objects only if all these objects are in a valid state , hence only if the whole system is consistent.不,它不是:)事务系统确保只有当所有这些对象都在有效的 state中时,客户端才能访问系统的对象,因此只有在整个系统一致的情况下。 From other answers you see the amount of processing needed to solve some problems, so that guarantee comes at a cost: performance .从其他答案中,您可以看到解决某些问题所需的处理量,因此保证是有代价的: performance This is the simple explanation why Java (and other general purpose OO languages) can't solve your problem out of the box .这就是为什么 Java(和其他通用 OO 语言)无法开箱即用地解决您的问题的简单解释。

Of course, an OO language can be used to both model a transactional world and accessing it, but special care must be taken, some constraints must be imposed and a special programming style be required to client developers.当然,OO 语言既可用于 model事务世界又可访问它,但必须特别小心,必须施加一些约束,并且客户端开发人员需要特殊的编程风格。 Usually a transactional system offers two commands: search (aka query) and lock .通常事务系统提供两个命令:搜索(又名查询)和锁定 The result of the query is immutable : it's a photo (ie a copy) of the system at the very specific moment it was taken, and modifying the photo has obviously no effect on the real world.查询的结果是不可变的:它是系统在拍摄的特定时刻的照片(即副本),修改照片显然对现实世界没有影响。 How can one modify the system?如何修改系统? Usually通常

  1. lock the system (or parts of it) if/when needed如果/需要时锁定系统(或部分系统)
  2. locate an object: returns a copy (a photo) of the real object which can be read and written locally locate an object:返回真实object的副本(一张照片),可在本地读写
  3. modify the local copy修改本地副本
  4. commit the modified object, ie let the system update its state based on provided input提交修改后的 object,即让系统根据提供的输入更新其 state
  5. discard any reference to (now useless) local objects: the system has changed changed, so the local copy isn't up to date.丢弃对(现在无用的)本地对象的任何引用:系统已更改,因此本地副本不是最新的。

(BTW, can you see how the concept of life cycle is applied to local and remote objects?) (顺便说一句,你能看出生命周期的概念是如何应用于本地和远程对象的吗?)

You can go with Set s, final modifiers and so on, but until you introduce transactions and immutability, your design will have a flaw.您可以使用Setfinal修饰符等 go,但在引入事务和不变性之前,您的设计会有缺陷。 Usually Java applications are backed by a database, which provides transactional functionalities, and often the DB is coupled with an ORM (such as Hibernate) to write object oriented code.通常 Java 应用程序由提供事务功能的数据库支持,并且通常 DB 与 ORM(例如 Hibernate)耦合以编写面向 object 的代码。

You can ensure that there are no duplicates by using a Set implementation like HashSet instead of using other data-structure.您可以通过使用像HashSet这样的Set实现而不是使用其他数据结构来确保没有重复项。 And instead of adding Job to a customer, create an final inner class in Job class that has private constructor.而不是将作业添加到客户,而是在具有私有构造函数的作业 class 中创建最终内部 class。 That ensure that the wrapper inner class can only be created by a job object. Make you Job constructor take in jobID and customer as parameter.确保包装器内部 class 只能由作业 object 创建。使您的作业构造函数以jobID和 customer 作为参数。 To maintain consistency -if customer is Null throw Exception as dummy jobs shouldn't be created.为了保持一致性 - 如果客户是 Null,则抛出异常,因为不应创建虚拟作业。

In add method of Customer, check to see if the Job wrapped by JobUnit has the same customer ID as the its own id, if not throw Exception.在Customer的add方法中,检查JobUnit包裹的Job是否和自己的id有相同的customer ID,如果不一致则抛出Exception。

When replacing a customer in Job class remove the JobUnit using the method provided by Customer class and add itself to the new customer and change the customer reference to the newly passed customer.当替换工作 class 中的客户时,使用客户 class 提供的方法删除JobUnit并将其自身添加到新客户并将客户引用更改为新传递的客户。 That way you can reason with your code better.这样你就可以更好地推理你的代码。

Here's what your customer class might look like.这是您的客户 class 的样子。

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

}

And the Job Class:和工作 Class:

public class Job {
Customer customer;
private Long id;

final JobUnit unit;最后的 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;
    }
 }
}

But one thing I'm curious about is why do you even need to add jobs to a customer object?但是我很好奇的一件事是为什么你甚至需要为客户 object 添加工作? If all you want to check is to see which customer has been assigned to which job, simply inspecting a Job will give you that information.如果您只想检查哪个客户已分配给哪个工作,只需检查工作即可为您提供该信息。 Generally I try not to create circular references unless unavoidable.通常我尽量不创建循环引用,除非不可避免。 Also if replacing a customer from a job once its been created is not necessary, simply make the customer field Final in the Job class and remove method to set or replace it.此外,如果不需要在创建工作后从工作中替换客户,只需将工作中的客户字段设置为 Final class并删除设置替换它的方法。

The restriction for assigning customer for a job should be maintained in database and the database entry should be used as a checking point.应在数据库中维护为工作分配客户的限制,并且应将数据库条目用作检查点。 As for adding jobs to customer that were done for someone else, you can either check for customer reference in a job to ensure that the customer to which a job is being added is the same one it holds or even better- simply remove any reference in customer for Job and it will simplify things for you .至于向为其他人完成的客户添加工作,您可以检查工作中的客户参考以确保要添加工作的客户与它持有的客户相同,或者甚至更好 -只需删除任何参考工作的客户,它会为你简化事情

If the Customer object owns the relationship then you can possibly do it this way:如果客户 object 拥有关系,那么您可以这样做:

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
}

...if the ownership is the other way around, just substitute customer for job. ...如果所有权是相反的,只需用客户代替工作。

The idea is to have the owner of the relationship maintain consistency.这个想法是让关系的所有者保持一致性。 Bi-directional relationships generally imply that the consistency management sits in both entities.双向关系通常意味着一致性管理位于两个实体中。

Make a proper setter-function that maintains consistency.制作一个适当的 setter-function 来保持一致性。 For instance, whenever you create a job, you supply the customer in the constructor.例如,每当您创建一个工作时,您都会在构造函数中提供客户。 The job constructor then adds itself to the customer's list of jobs.然后,作业构造器将自己添加到客户的作业列表中。 Or whenever you add a job to a customer, the add function has to check that the job's customer is the customer it's being added to.或者每当您向客户添加工作时,添加 function 必须检查工作的客户是否是它要添加到的客户。 Or some combination of this and similar things to what suits your needs.或者这个和类似的东西的某种组合以满足您的需要。

Just implement some sort of collection in the object that has the other objects For example in customer you could say:只需在具有其他对象的 object 中实现某种集合例如在客户中你可以说:

private List<Job> jobs; 

then by using getters and setters you can add values jobs to this list.然后通过使用 getter 和 setter,您可以将值作业添加到此列表中。 This is basic OO stuff, I don't think you searched enough on the inte.net.这是基本的 OO 内容,我认为您在 inte.net 上搜索得不够多。 there is a lot of info available on these subjects.有很多关于这些主题的信息。

Btw, you can use all sort of collections (Sets, Lists, Maps)顺便说一句,您可以使用各种 collections(集合、列表、地图)

I know this is late but I think another way to this would be to look at the problem a bit differently.我知道这已经晚了,但我认为另一种方法是以不同的方式看待问题。 Since customer holds a collection of all jobs assigned by or completed for a customer, you could consider the job class to be a sub class of customer with extra information of having all the jobs completed by the customer.由于客户持有由客户分配或为客户完成的所有工作的集合,因此您可以将工作 class 视为客户的子 class,并提供有关客户已完成所有工作的额外信息。 Then you would only have to maintain customer id in the main class and it would be inherited.然后你只需要在主 class 中维护客户 ID,它就会被继承。 This design would ensure that each job can be linked to a customer.这种设计将确保每个工作都可以链接到一个客户。 Also if for a customer you want to find out how many jobs are present that too also would be got.此外,如果您想要为客户找出有多少工作,那也可以得到。

I am sorry I know this is very late but I have come across a similar problem where I feel the best solution is to follow a inheritance model. Think of job as being jobs done/asisgned by a particular customer.很抱歉,我知道这已经很晚了,但我遇到了一个类似的问题,我觉得最好的解决方案是关注 inheritance model。将工作视为由特定客户完成/分配的工作。 So in that case the Customer would be a super class with the Job(Lets call is customer job) being a sub class since a Job cannot exists without a customer.因此,在那种情况下,客户将是一个超级 class,而工作(让我们打电话给客户工作)是一个子 class,因为没有客户就不能存在工作。 A customer would also have a list of jobs primarily for ease of data fetching.客户还会有一个工作列表,主要是为了便于数据获取。 Intutively this does not make sense since Job and Customer done seem to have any relation, however once you see that Job cannot exist without a customer, it just becomes an extension of customer.直觉上这是没有意义的,因为 Job 和 Customer done 似乎有任何关系,但是一旦你看到 Job 不能没有客户而存在,它就变成了 customer 的延伸。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM