Implementing bidirectional relations between objects of the same class
I have to implement a class whose instances have a bidirectional relation to each other. For example I have the class FooBar
which should offer the metho开发者_如何学God sameAs(FooBar x)
and maintain a Set for each instances containing its equivalent instances. So if I call foo.sameAs(bar)
, the Set in foo
should contain bar
and vice versa. Invoking bar.sameAs(foo)
doesn't work, of course.
For clarifiction: the instances of this class are only semantically equal. equals
should still return false
.
The solutions I've come up with is either to implement a private method internalSameAs(FooBar x)
which is invoked from sameAs(FooBar x)
or to use a static method sameAs(FooBar x, FooBar y)
.
Solution 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);
}
}
Solution 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);
}
}
Which one would you prefer and why? Or is there another way I didn't think about?
The naming you've used is confusing. sameAs
sounds as though it's a test which should return a boolean, but from your code it seems it would be more appropriately named declareSameAs
. When you call foo.sameAs(bar)
, you're declaring that foo
and bar
are the same, not doing a test, correct?
The problem is that with your code you can declare
x.sameAs(y);
y.sameAs(z);
but it won't be the case that x is the same as z, which is presumably not what you want (and if it is what you want, you definitely need to change the method name).
It seems to me you want to divide your instances into sets, and have each instance keep a reference to the set it's in (not to a separate set internal to the instance). When you make a new declaration that two instances are the same, you need to combine the sets, and ensure all affected instances have a reference to the combined set.
are you flexible with the data structures to be used? If so you could use a Multimap
(from Guava Collections) that is static amongst all the instances of the class FooBar
. In that Multimap
you can have the keys as FooBar
references (or a unique id if you have one) and the values would be the references (or id.s) of the FooBar
s that have the sameAs
relation.
Maybe there's a different way: sameAs
sounds pretty similiar to equals
. If we do not need equals
for something else, then I'd simply implement the equals
method on FooBar
so that we simply do a
if (foo.equals(bar))
System.out.println("We're equal (aka: 'equivalent/the same')");
In this case, we do not need any set - just a rule to determine, if two instances are equal.
You could store the sameness information in a separate datastructure outside of those classes. A central map could do the job:
HashMap<FooBar, Set<FooBar>> sameFooBars;
If you have "same" objects, simply add them to the map:
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);
}
And the test:
public static boolean isSame(FooBar foo1, FooBar foo2) {
if (getMap().get(foo1) == null)
return false;
return getMap().get(foo1).contains(foo2);
}
Do you really need to maintain a list of equivalences in ALL objects? If possible I would separate the set of equivalences from the objects themselves. This will be easier to maintain.
Then you can use the multimap of @posdef or more simply a Map> to stay with standard JAVA API.
Your "bidirectional" samesAs(...)
method sounds like Object.equals(...)
, which, according to javadoc is a "an equivalence relation on non-null object references". If this is what you want, then you just have to override equals
in your class.
I'm a bit lost when you say that "FooBar
shouldmaintain a Set for each instances containing its equivalent instances". If you want to build equivalent classes for FooBar
objects, then I think it's a good idea to use a java Collection
to represent them, and more precisely a Set
.
Here is a quickly hacked example:
public class FooBar {
@Override
public boolean equals(Object other) {
// do whatever fancy computation to determine if
// the object other is equal to this object
}
}
and for the equivalent class:
@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;
}
}
"same as" but not "equal to" sounds like you should be using Comparable
.
I think it makes more sense to implement compareTo()
or sameAs()
as an instance method rather than a static since you will always need two real instances to do any comparison.
Sounds like what you want are to separate the equivalence groups from the object instances.
Make a Map<FooBar, Set<FooBar>> and note that when you lookup an object the set will include itself.
精彩评论