实现同一类对象之间的双向关系

时间:2011-05-18 07:27:05

标签: java class-design

我必须实现一个实例彼此具有双向关系的类。例如,我有类FooBar,它应该提供方法sameAs(FooBar x)并为包含其等效实例的每个实例维护一个Set。因此,如果我致电foo.sameAs(bar),则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 个答案:

答案 0 :(得分:2)

您使用的命名令人困惑。 sameAs听起来好像是一个应该返回一个布尔值的测试,但是从你的代码来看它似乎更恰当地命名为declareSameAs。当您致电foo.sameAs(bar)时,您声明foobar是相同的,没有进行测试,是否正确?

问题是您的代码可以声明

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

但不会出现x与z相同的情况,这可能不是你想要的(如果它是你想要的,你肯定需要更改方法名称)。

在我看来,您希望将实例划分为集合,并让每个实例保持对其所在集合的引用(到实例内部的单独集合)。当您声明两个实例相同时,需要组合这些集合,并确保所有受影响的实例都引用了组合集。

答案 1 :(得分:1)

您是否可以灵活使用要使用的数据结构?如果是这样,您可以使用Multimap(来自Guava Collections),它在类FooBar的所有实例中都是静态的。在Multimap中,您可以将键作为FooBar引用(如果有的话,可以是唯一的ID),并且值将是FooBar s的引用(或id.s)具有sameAs关系。

答案 2 :(得分:1)

您的“双向”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;
    }

}

答案 3 :(得分:1)

也许有不同的方式: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);
}

答案 4 :(得分:1)

您真的需要在所有对象中维护一个等价列表吗?如果可能的话,我会将对等集与对象本身分开。这将更容易维护。

然后你可以使用@posdef的多重图或更简单的一个Map&gt;继续使用标准的JAVA API。

答案 5 :(得分:0)

“与”相同但不“等于”听起来应该使用Comparable

我认为将compareTo()sameAs()实现为实例方法而不是静态更有意义,因为您总是需要两个真实实例来进行任何比较。

答案 6 :(得分:0)

听起来你想要的是将等价组与对象实例分开。

制作地图&lt; FooBar,Set&lt; FooBar&gt;&gt;并注意,当您查找对象时,该集将包含其自身。