开发者

final roadblock with builder

I have a graph (g) that uses a builder ( J Bloch style ). The graph needs to be reversed to run certain statistics that are then cached for reports and analysis algorithms to access.

So, graph g defines the following reference variables:

private final Builder savedBuilder; // save builder for clone build with same properties.
private final Graph   gPrime;       // must reverse populated graphs BEFORE cache of stats

Note: gPrime refers to an identical graph except it is populated from the revesed g. gPrime.gPrime should refer to g, since g is the reverse of gPrime.

and the builder build method:

public Graph build() {
  Graph g = new Graph(this);
  g.gPrime.gPrime = g;
  return g;
}
开发者_运维问答

and the constructor that takes the builder:

private Graph (Builder builder){  // 'this' used for clarity
  this.gType        = builder.gType;
  this.dropOrphans  = builder.dropOrphans;
  this.fileHandle   = builder.fileHandle;       
  this.nodes        = builder.nodes;            
  this.edges        = builder.edges;            
  this.delimiter    = builder.delimiter;
  this.mapCapacity  = builder.mapCapacity;       
  this.mapLoadFactor    = builder.mapLoadFactor;
  this.savedBuilder = builder;  // save builder for cloning

  emptyGraph();     // build empty structure for data in this graph
  if (this.fileHandle == null) {           // no file data
    if (this.nodes == 0) {         // no sizing info
    ;                  // nothing else to do - - - empty graph
    } else {                    // we have # of nodes
      if ( this.edges == 0) {            // just an edge-less integer graph)
      populateEdgeless(nodes)   ;
      } else {                 // randomly generated graph
    populateRandom(nodes, edges);
      }
    }
  } else {                 // populate from file
    populateFromFile();
  }

  // To create empty graph to transpose our values into,
  // we need to clear out builder settings that would populate a new graph.
  savedBuilder.fileHandle = null;
  savedBuilder.nodes = 0;
  savedBuilder.edges = 0;

  // otherwise, everything the same, so just pass modified builder to constructor
  // save the reference to this graph ( ready for the reversal method to use )
  this.gPrime = new Graph(savedBuilder);

)

Once again. Two graph objects, with the gPrime in each referencing the other, is the goal.

Sequence is: build g - - - populate g - - - reverse g into an empty graph with the same characteristics as g

So, here is the problem I don't quite understand.

If I assign g to gPrime.gPrime, either in the build after g us built or in the bottom of the constructor, I get an error message stating that gPrime is final. Eclipse indicates it's the first gPrime that is in question - - - which is true - - - it is final and has been assigned. But gPrime.gprime ( with the emphasis on the second gPrime ) has not been assigned yet. ( I searched the entire program. )

I also tried putting the assignment at the bottom of the reverse method. Same thing.

I also tried g.gPrime.gPrime in the builder. Same thing.

It's almost as if the compiler is confused about which gPrime is receiving the assignment.

I am sure there is something I am not seeing or understanding - - - but - - - don't know how to make this happen.

I can make this work if I take out the final, but I am trying to get to immutable.


You need circular dependencies that are immutable. You have to implement it so that when you Build A (in the constructor of A) you have to call the constructor of B with this.

Here is the code with Builders (You have to make sure that the whole building process does not escape from the current thread):

public class A {

  private final B b_;
  private final String name_;

  private A(Builder b) {
    b_ = b.bB_.a(this).build();
    name_ = b.name_;
  }

  public String name() {
    return name_;
  }

  public B b() {
    return b_;
  }

  @Override
  public String toString() {
    return "[" + name_ + ": " + b_.name() + " ]";
  }

  public static class Builder {

    private B.Builder bB_;
    private String name_;

    public Builder bB(B.Builder bB) {
      bB_ = bB;
      return this;
    }

    public Builder name(String arg) {
      name_ = arg;
      return this;

    }

    public A build() {
      return new A(this);
    }
  }

}

Class B:

public class B {

  private final A a_;
  private final String name_;

  private B(Builder b) {
    a_ = b.a_;
    name_ = b.name_;
  }

  public String name() {
    return name_;
  }

  @Override
  public String toString() {
    return "[" + name_ + ": " + a_.name() + " ]";
  }

  public static class Builder {

    private A a_;
    private String name_;

    public Builder a(A a) {
      a_ = a;
      return this;

    }

    public Builder name(String arg) {
      name_ = arg;
      return this;

    }

    public B build() {
      return new B(this);
    }
  }

}

How to use it:

public class Main {

  public static void main(String[] args) {
    A.Builder aBl = new A.Builder().name("I am A1");
    B.Builder bBl = new B.Builder().name("I am B1");

    A a = aBl.bB(bBl).build();

    System.out.println(a);
    System.out.println(a.b());

  }

}

^
^
^
If you want to have single class and two objects in circular dependency):

public class A {

  private final A other_;
  private final String name_;

  private A(Builder b) {
    if (b.otherBulder_ != null) {
      other_ = b.otherBulder_.otherInstance(this).build();
    } else {
      other_ = b.otherInstance_;
    }
    name_ = b.name_;
  }

  @Override
  public String toString() {
    return "[" + name_ + ": " + other_.name() + " ]";
  }

  public String name() {
    return name_;
  }

  public A other() {
    return other_;
  }

  static class Builder {

    private Builder otherBulder_;
    private A otherInstance_;
    private String name_;

    Builder name(String name) {
      name_ = name;
      return this;
    }

    Builder otherBuilder(Builder other) {
      otherBulder_ = other;
      return this;
    }

    Builder otherInstance(A instance) {
      otherInstance_ = instance;
      return this;
    }

    A build() {
      return new A(this);
    }
  }

  public static void main(String[] args) {
    Builder a1B = new Builder().name("A1");
    Builder a2B = new Builder().name("A2");

    A a = a1B.otherBuilder(a2B).build();
    System.out.println(a);
    System.out.println(a.other());

  }
}


I think my answer is just a simplified version of Op De Cirkel's answer, tailored to your specific needs. The idea is to have the gPrime instance in the builder, so in the constructor, if the builder has a non-null gPrime, use it, otherwise build it:

private Graph(Builder builder){


  /* .... setup code omitted....*/

  if (builder.gPrime == null){
     savedBuilder.gPrime = this;
     this.gPrime = new Graph(savedBuilder);
  }else{
     this.gPrime = builder.gPrime;
  }

}

this.gPrime.gPrime = this will not work anywhere because gPrime is a instance final field and can only be initialized when declared or in constructor for that instance. What this.gPrime.gPrime = this is doing is initializing the final field for other instance, which violates the "finalness" of gPrime


You can't initialize an immutable circular structure.

You can have it effectively immutable by changing gPrime to not be final, and making sure you always set it to a value before it is used.

Thanks to Op De Cirkel's answer:

You can use two builders, or a switch in the functionality of your builder, where you have a builder that holds a reference to the Graph object that you are currently constructing:

public class Graph {

    private final Graph gPrime;
    private final String name_;

    private Graph(PrimeBuilder b) {
        gPrime = b.g;
        name_ = b.name_;
    }

    private Graph(Builder b) {
        gPrime = new PrimeBuilder(this).name("gPrime").build();
        name_ = b.name_;
    }

    public String name() {
        return name_;
    }

    public Graph gPrime() {
        return gPrime;
    }

    @Override
    public String toString() {
        return "I am " + name_ + ", my gPrime is " + gPrime.name();
    }

    public static class PrimeBuilder {

        private Graph g;
        private String name_;

        public PrimeBuilder(Graph g) {
            this.g = g;
        }

        public PrimeBuilder name(String arg) {
            name_ = arg;
            return this;

        }

        public Graph build() {
            return new Graph(this);
        }
    }

    public static class Builder {

        private String name_;

        public Builder name(String arg) {
            name_ = arg;
            return this;
        }

        public Graph build() {
            return new Graph(this);
        }
    }

}

Usage example:

public class Main {

  public static void main(String[] args) {
    Graph g = new Graph.Builder().name("g").build();

    System.out.println(g);
    System.out.println(g.gPrime());

  }

}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜