Программирование отношений "один ко многим"

Поэтому я удивлен, что выполнение поиска в google и stackoverflow не возвращает больше результатов.

В программировании OO (я использую java), как вы правильно реализуете отношения "один ко многим"?

У меня есть класс Customer и class Job. Мое приложение предназначено для фиктивной компании, которая завершает работу для клиентов. Моя текущая реализация такова, что класс Job не имеет ничего общего с классом Customer, вообще нет ссылки на него. Класс Customer использует коллекцию и методы для хранения, извлечения и изменения информации о заданиях, которые были назначены и/или завершены для клиента.

Вопрос в том, что, если я хочу узнать, для какого клиента был выполнен конкретный Job? Я нашел эту статью только в следующих словах: http://www.ibm.com/developerworks/webservices/library/ws-tip-objrel3/index.html.

В соответствии с реализацией автора я хотел бы, чтобы конструктор Job принял параметр Customer и сохранил его, чтобы я мог его получить. Однако я не вижу никакой гарантии, что эта модель может быть согласованной. Не существует никаких попыток установить связанного клиента для работы в качестве клиента, для которого работа не была, и добавить рабочие места для клиентов, которые были сделаны для кого-то другого. Любая помощь по этому поводу будет оценена.

Ответ 1

Нет 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);
   }
}

В действительности вы не будете часто моделировать эти отношения в своих классах. Вместо этого вы будете искать всех детей для родителя в каком-то репозитории.

Ответ 2

Возможно, вы не ожидали ответа на сложный (и нулевой код), но нет решения для сборки вашего защищенного от бомбы API так, как вы его намереваетесь. И это не потому, что парадигма (OO) или платформа (Java), а только потому, что вы сделали неправильный анализ. В транзакционном мире (каждая система, которая моделирует проблемы реальной жизни и их эволюцию с течением времени является транзакционной). Этот код когда-нибудь сломается:

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

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

поскольку во время доступа к j1.someProperty j1 и j2 не могут существовать:)

TL; DR

Длинный ответ на это - неизменность, а также вводит понятия жизненного цикла и транзакций. Все остальные ответы говорят вам, как это сделать, вместо этого я хочу изложить, почему. Отношение "один ко многим" имеет две стороны

  • имеет много
  • принадлежит

Ваша система согласована, если Customer A имеет Job B, Job B принадлежит Customer A. Вы можете реализовать это несколькими способами, но это должно произойти в транзакции, т.е. Сложное действие, сделанное из простых, и система должна быть недоступна до завершения транзакции. Это кажется слишком абстрактным и не связанным с вашим вопросом? Нет, это не так. Транзакционная система гарантирует, что клиенты могут получить доступ к системным объектам только в том случае, если все эти объекты находятся в допустимом состоянии, поэтому только в том случае, если вся система согласована. Из других ответов вы видите объем обработки, необходимый для решения некоторых проблем, поэтому гарантия стоит за счет: производительность. Это простое объяснение, почему Java (и другие языки OO общего назначения) не могут решить вашу проблему из коробки.

Конечно, язык OO может использоваться как для моделирования мира транзакций, так и для его доступа, но необходимо проявлять особую осторожность, необходимо установить некоторые ограничения и создать специальный стиль программирования для разработчиков-клиентов. Обычно транзакционная система предлагает две команды: поиск (aka запрос) и блокировка. Результат запроса неизменный: это фотография (то есть копия) системы в тот самый момент, когда она была взята, а изменение фотографии явно не влияет на реальный мир. Как можно изменить систему? Обычно

  • заблокировать систему (или ее части), если/при необходимости
  • найти объект: возвращает копию (фотографию) реального объекта, который можно читать и записывать локально
  • изменить локальную копию
  • передать измененный объект, то есть позволить системе обновлять свое состояние на основе предоставленного ввода
  • отказаться от ссылки на (теперь бесполезные) локальные объекты: система изменилась, поэтому локальная копия не обновлена.

(Кстати, вы видите, как концепция жизненного цикла применяется к локальным и удаленным объектам?)

Вы можете использовать модификаторы Set s, final и т.д., но пока вы не вводите транзакции и неизменность, ваш дизайн будет иметь недостаток. Обычно приложения Java поддерживаются базой данных, которая обеспечивает функциональные возможности транзакций, и часто БД сочетается с ORM (например, Hibernate) для написания объектно-ориентированного кода.

Ответ 3

Вы можете убедиться, что дубликатов нет, используя Установить реализацию, например HashSet, вместо использования другой структуры данных. Вместо добавления Job к клиенту создайте окончательный внутренний класс в классе Job, который имеет частный конструктор. Это гарантирует, что внутренний класс оболочки может быть создан только объектом задания. Сделайте конструктор Job в jobID и клиентом в качестве параметра. Чтобы поддерживать согласованность - если клиент - это Null throw Exception, поскольку не должно создаваться фиктивные задания.

В добавить метод Customer, проверьте, имеет ли Job, завернутый JobUnit, тот же идентификатор клиента, что и его собственный идентификатор, если не выбрасывать исключение.

При замене клиента в классе Job удалите JobUnit, используя метод, предоставленный классом Customer, и добавьте себя к новому клиенту и измените ссылку клиента на нового клиента. Таким образом, вы можете лучше рассуждать с кодом.

Здесь может выглядеть ваш класс клиента.

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 и удалите метод установить или заменить.

Ограничение для назначения клиента для задания должно храниться в базе данных, а запись базы данных должна использоваться как контрольная точка. Что касается добавления рабочих мест к клиенту, которые были сделаны для кого-то другого, вы можете либо проверить ссылку на клиента в задании, чтобы убедиться, что клиент, к которому добавляется задание, является тем же самым, что он имеет или даже лучше - просто удалить любая ссылка в клиенте для Job, и это упростит для вас.

Ответ 4

Если объект 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
}

... если право собственности наоборот, просто замените клиента на работу.

Идея состоит в том, чтобы владелец отношений поддерживал согласованность. Двунаправленные отношения обычно подразумевают, что управление согласованностью находится в обоих объектах.

Ответ 5

Сделайте правильную функцию setter, которая поддерживает согласованность. Например, всякий раз, когда вы создаете задание, вы предоставляете клиента в конструкторе. Затем конструктор заданий добавляет себя в список рабочих мест клиентов. Или, когда вы добавляете работу к клиенту, функция добавления должна проверять, что клиент задания является клиентом, к которому он добавлен. Или какая-то комбинация этих и подобных вещей с тем, что вам подходит.

Ответ 6

Просто реализуйте какую-то коллекцию в объекте, у которого есть другие объекты Например, в клиенте вы можете сказать:

private List<Job> jobs; 

то, используя геттеры и сеттеры, вы можете добавить задания заданий в этот список. Это основной материал OO, я не думаю, что вы искали достаточно в Интернете. есть много информации по этим предметам.

Btw, вы можете использовать всевозможные коллекции (Sets, Lists, Maps)

Ответ 7

Я знаю, что это поздно, но я думаю, что другой путь к этому - это посмотреть на проблему немного по-другому. Поскольку клиент имеет коллекцию всех заданий, назначенных или завершенных для клиента, вы можете считать класс работы подклассом клиента с дополнительной информацией о том, чтобы все задания были заполнены клиентом. Тогда вам нужно будет поддерживать идентификатор клиента в основном классе, и он будет унаследован. Эта конструкция гарантирует, что каждое задание может быть связано с клиентом. Также, если для клиента вы хотите узнать, сколько рабочих мест присутствует, что тоже будет получено.

Ответ 8

Мне жаль, что я знаю, что это очень поздно, но я столкнулся с аналогичной проблемой, когда я считаю, что лучшим решением является следовать модели наследования. Подумайте о работе как о том, что рабочие места выполняются/оцениваются конкретным клиентом. Таким образом, в этом случае Клиент будет супер-классом с заданием Job (Lets call is customer job), являющимся подклассом, поскольку Job не может существовать без клиента. У клиента также будет список рабочих мест в первую очередь для удобства сбора данных. Интуитивно это не имеет смысла, поскольку работа и клиент, похоже, имеют какое-либо отношение, однако, как только вы увидите, что Job не может существовать без клиента, он просто становится расширением клиента.