Как предотвратить цикл в двунаправленной взаимосвязи

#java #entity-relationship #foreign-key-relationship #entities #bidirectional

#java #сущность-отношение #foreign-key-relationship #сущности #двунаправленный

Вопрос:

Допустим, у нас есть следующие две сущности.

Библиотека

 public class Library
{
    private Collection<Book> books = new ArrayList<Book>();

    public boolean add(Book book)
    {
        return books.add(book);
    }
    public boolean remove(Book book)
    {
        return books.remove(book);
    }
}
  

Книга

 public class Book
{
    private Library library;
    public setLibrary(Library library)
    {
        this.library = library;
    }
}
  

Это двунаправленная связь, которая, скорее всего, довольно быстро разорвется. Даже если вы добавляете книгу в библиотеку, библиотека книги не обновляется. И даже если вы обновляете библиотеку книги, библиотека не обновляется.

Итак, как бы вы правильно поддерживали эти двунаправленные отношения? Я мог бы, конечно, просто поставить add remove в setLibrary и наоборот, но это, насколько я вижу, привело бы к циклу, который, вероятно, закончился бы на StackOverflowError .

Ответ №1:

Определите «владельца» для отношения. Библиотека «владеет» книгой или книга владеет библиотекой?

Ответ на этот вопрос говорит вам, кто обновляет объект «slave».

Я чувствую, что библиотека владеет книгой, поэтому код «добавить книгу в библиотеку» должен перейти в библиотеку, которая затем обновит обратные ссылки.

Комментарии:

1. Хорошо, так вот как это обычно делалось бы тогда? Значит, в этом случае Book может быть немым, и вы будете вызывать только setLibrary в Library классе?

2. @Svish: Да, это идея, вы можете добавлять книги только в библиотеку, которая затем устанавливает библиотеку книги. Вы не можете напрямую установить библиотеку в книге.

3. ДА. Это обходной путь, потому что предложение veredesmarald работает слишком медленно на современных компьютерах. Если вопрос «эта книга уже есть в библиотеке» будет иметь сложность O (1) (= требует одинакового времени, независимо от размера библиотеки), мы могли бы позволить добавить код с обеих сторон.

4. Имеет смысл. Думаю, тогда я сделаю это таким образом 🙂

Ответ №2:

Хотя я бы рекомендовал метод определения владельца отношений Аарона, альтернативой, которая позволяет set / add / remove методам оставаться там, где они есть, является также определение соответствующих has методов ( hasBook в Library и hasLibrary в Book ). Перед вызовом метода add or remove на другом конце отношения сначала запрашивайте, существует ли он уже, и если он существует, не вызывайте его.

Комментарии:

1. Это было бы идеальным решением, если бы проверка была быстрой. Современные компьютеры все еще слишком медленные для этого:-(

2. @Aaron Digulla: Я согласен, именно поэтому я сразу сказал, что предпочитаю ваш подход, но я все еще чувствовал, что стоит указать на это как на вариант.

3. Да, думаю, я сделаю то, что предлагает @Aaron, но это тоже был хороший момент, который я мог бы использовать при других обстоятельствах 🙂

Ответ №3:

Я что, тупой?

 public boolean add(Book book)
{
   book.setLibrary(this);
   return books.add(book);
}
public boolean remove(Book book)
{
   book.setLibrary(null);
   return books.remove(book);
}
  

Комментарии:

1. Нет. Проблема в том, что вы в дополнение к этому делаете this.books.remove(this);this.books = books; this.books.add(this); в setLibrary классе.

Ответ №4:

В предлагаемом вами решении не могли бы вы проверить ссылки на равенство, чтобы избежать цикла?

 public boolean add(Book book)
{
   if(book.getLibrary != this) book.setLibrary(this);
   return books.add(book);
}
  

Комментарии:

1. Это не работает: если книга уже была в другой библиотеке, ссылка на библиотеку в книге не обновлялась бы, и в итоге вы получили бы книгу, которая есть в нескольких библиотеках (с точки зрения библиотеки), но книга будет думать, что это только в одной из них.