Question

I'm encountering a couple awkward situations that seem, in some sense, dual to each other, and was wondering if anyone had any thoughts about how to handle them cleanly.

External initialization

class Human {
   Collection <Human> nextDoorNeighbors;
}

class Neighborhood {
   Collection <Human> humans;
   Neighborhood() {
     // Make humans
     // Then, let them all know who their neighbors are.
   }
}

This is awkward because the humans (in this situation) never have to change their next-door neighbors once they've been set up (they are effectively write-once), but this setup can't be done in the Human constructor because the neighbors that need to be put in the nextDoorNeighbors collection don't all exist when the human is constructed.

Holding something for another

Suppose I want to be able to store Humans in a tree-based map. To do so, the Human has to hold a Comparable ID, even if that isn't logically significant to the concept of a Human. The Human constructs this ID, but it never uses it. Only the map uses it (or even should use it).

Was it helpful?

Solution

In the first case, maybe the awkwardness is an indication that neighbours shouldn't be a property of Human. Perhaps the Neighbourhood object should be a property of Human, and a method like human.getNeighbours() can be used to get the actual neighbours when they are needed. Then having a neighbours property becomes a private performance issue for the getNeighbours() method.

In the second case, how is your tree-based map providing a structure if the Human is inherently unstructurable? What's the map for if the ID is irrelevant to the human? Typically an ID is relevant, and is used by the class that has it to ensure that it's uniquely identifiable, but if it's genuinely not required, you can use a separate class, like a HumanKey, to wrap the Human for the map.

OTHER TIPS

I don't really understant what your question is.. Because it's not explicit..

But for the id you can have a static variable in the human class that you will increment in the human constructor and another variable wich will contain the id

It would be something like this

class Human 
{
   private static int humansCounter=0;
   final public int id;

   public Human()
   {
      id=humansCounter++;
   }

}

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top