Реализация двунаправленных отношений между объектами одного класса - PullRequest
1 голос
/ 18 мая 2011

Мне нужно реализовать класс, экземпляры которого имеют двунаправленное отношение друг к другу.Например, у меня есть класс FooBar, который должен предлагать метод sameAs(FooBar x) и поддерживать Set для каждого экземпляра, содержащего его эквивалентные экземпляры.Поэтому, если я позвоню foo.sameAs(bar), Set в foo должен содержать bar и наоборот.Вызов bar.sameAs(foo), конечно, не работает.

Для ясности: экземпляры этого класса семантически семантически равны.equals все равно должен возвращать false.

Решения, которые я придумала, - это реализовать закрытый метод internalSameAs(FooBar x), который вызывается из sameAs(FooBar x), или использовать статический метод sameAs(FooBar x, FooBar y).

Решение 1:

class FooBar {
    Set<FooBar> sameAs = new HashSet<FooBar>();

    public void sameAs(FooBar x) {
        this.internalSameAs(x);
            x.internalSameAs(this);
        }

        public void internalSameAs(FooBar x) {
            sameAs.add(x);
        }
    }

Решение 2:

class FooBar {
    Set<FooBar> sameAs = new HashSet<FooBar>();

    public static void sameAs(FooBar x, FooBar y) {
        x.sameAs.add(y);
        y.sameAs.add(x);
    }
}

Какой вариант вы бы предпочли и почему?Или есть другой способ, о котором я не думал?

Ответы [ 7 ]

2 голосов
/ 18 мая 2011

Названия, которые вы использовали, сбивают с толку. sameAs звучит так, как будто это тест, который должен возвращать логическое значение, но из вашего кода кажется, что он будет более подходящим именем declareSameAs. Когда вы звоните foo.sameAs(bar), вы заявляете, что foo и bar одинаковы, не проводите тест, верно?

Проблема в том, что с вашим кодом вы можете объявить

x.sameAs(y);
y.sameAs(z);

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

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

1 голос
/ 18 мая 2011

Вам действительно нужно вести список эквивалентностей во ВСЕХ объектах?Если возможно, я бы отделил набор эквивалентностей от самих объектов.Это будет легче поддерживать.

Тогда вы можете использовать мультикарту @posdef или, проще, карту>, чтобы остаться со стандартным API JAVA.

1 голос
/ 18 мая 2011

Может быть, есть другой способ: sameAs звучит довольно похоже на equals. Если нам не нужно equals для чего-то другого, то я просто реализую метод equals в FooBar, чтобы мы просто делали

 if (foo.equals(bar))
    System.out.println("We're equal (aka: 'equivalent/the same')");

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


Вы можете хранить информацию о одинаковости в отдельной структуре данных вне этих классов. Центральная карта может сделать эту работу:

 HashMap<FooBar, Set<FooBar>> sameFooBars;

Если у вас есть «одинаковые» объекты, просто добавьте их на карту:

 public static void addSameObjects(FooBar foo1, FooBar foo2) {
   Set<FooBar> set = getMap().get(foo1);
   if (set == null) {
     set = new HashSet<FooBar>();
     getMap().put(foo1, set);
   }
   set.add(foo2);

   // serious implementation avoid code duplication...
   set = getMap().get(foo2);
   if (set == null) {
     set = new HashSet<FooBar>();
     getMap().put(foo2, set);
   }
   set.add(foo1);
}

и тест:

public static boolean isSame(FooBar foo1, FooBar foo2) {
  if (getMap().get(foo1) == null) 
    return false;

  return getMap().get(foo1).contains(foo2);
}
1 голос
/ 18 мая 2011

Ваш «двунаправленный» метод samesAs(...) звучит как Object.equals(...), что, согласно javadoc , является «отношением эквивалентности для ненулевых ссылок на объекты». Если это то, что вы хотите, то вам просто нужно переопределить equals в вашем классе.

Я немного растерялся, когда вы говорите, что "FooBar должен поддерживать Set для каждого экземпляра, содержащего его эквивалентные экземпляры". Если вы хотите создать эквивалентные классы для FooBar объектов, то я думаю, что будет хорошей идеей использовать java Collection для их представления, а точнее Set.

Вот быстро взломанный пример:

public class FooBar {

    @Override
    public boolean equals(Object other) {
        // do whatever fancy computation to determine if 
        // the object other is equal to this object
    }

}

и для эквивалентного класса:

@SuppressWarnings("serial")
public class FooBarEquivalentClass extends HashSet<FooBar> {

    @Override
    public boolean add(FooBar e) {
        if (isEmpty())
            return super.add(e);
        else if (e.equals(iterator().next()))
            return super.add(e);
        else
            return false;
    }

}
1 голос
/ 18 мая 2011

вы гибки с структурами данных, которые будут использоваться? Если это так, вы можете использовать Multimap (из Guava Collections ), который является статическим среди всех экземпляров класса FooBar. В этом Multimap вы можете иметь ключи как FooBar ссылки (или уникальный идентификатор, если он у вас есть), а значения будут ссылками (или id.s) FooBar с sameAs. отношение.

0 голосов
/ 19 мая 2011

Похоже, что вы хотите отделить группы эквивалентности от экземпляров объекта.

Создайте карту > и обратите внимание, что при поиске объекта набор будет включать сам себя.

0 голосов
/ 18 мая 2011

"то же, что", но не "равно" звучит так, как будто вы должны использовать Comparable.

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

...