I have an approach that I think is rather clean if the objects themselves need to be aware of the networking. Note that this approach will not work if you concurrently instantiate instances (since each thread will have its own copy of the static
fields).
class Foo {
// instance fields
public Foo(/* constructor args */) {
// set instance up
network(this);
}
public boolean isNeighbor(Foo that) {
return areNeighbors(this, that);
}
// static field for tracking edges between neighbors, maybe a
private static Map<Foo, List<Foo>> neighborhood = new HashMap<>();
private static void network(Foo me) {
myNeighbors = new ArrayList<>();
for (Map.Entry<Foo, List<Foo>> x : neighborhood.entrySet()) {
Foo n = x.getKey();
if (/* use me and n's fields to determine whether they are neighbors */) {
myNeighbors.add(n);
x.getValue().add(me);
}
}
neighborhood.put(me, myNeighbors);
}
public static boolean areNeighbors(Foo a, Foo b) {
return neighborhood.get(a).contains(b);
}
}
This approach makes it so that each instance can determine their neighbors without actually knowing their neighbors ahead of time and without using an external class. If an instance's neighbors cannot be inferred from internal state, this approach could be combined with the approach of generating unique IDs (hashes?) for each instance:
class Bar {
// instance fields
public Bar(/* constructor args */, int... neighborIndices) {
// set instance up
network(this, neighborIndices);
}
@Override
public int hashCode() {
return /* a unique value based upon this object's state */;
}
public boolean isNeighbor(Bar that) {
return areNeighbors(this, that);
}
private static Map<Integer, Bar> lookup = new HashMap<>();
private static Map<Bar, List<Integer>> neighbors = new HashMap<>();
private static void network(Bar me, int[] neighbors) {
lookup.put(me.hashCode(), me);
List<Integer> neighborList = new ArrayList<>();
for (int i : neighbors) {
neighborList.add(i);
}
neighbors.put(me, neighborList);
}
public static boolean areNeighbors(Bar a, Bar b) {
return neighbors.get(a).contains(b.hashCode());
}
}
Naturally, if the neighbor relationships are not associative, it is trivial to modify the first example to be a digraph.