tags:

views:

72

answers:

6

I have a class with some private final fields:

public class ClassA {
  private final Object field1;
  private final Object field2;
  ...
}

The class has several different constructors, with a variety of arguments:

public ClassA(Object arg1, Object arg2);
public ClassA(int arg1, String arg2, boolean arg3);

These constructors calculate the values to put in the final fields.

Ideally, I would like to do something like this:

public ClassA(Object arg1, Object arg2) {
  ... // error check params
  Object value1 = ... // calculate value 1 based on args
  Object value2 = ... // calculate value 2 based on args

  init(value1, value2);
}

public ClassA(int arg1, String arg2, boolean arg3) {
  ... // error check params
  Object value1 = ... // calculate value 1 based on args
  Object value2 = ... // calculate value 2 based on args

  init(value1, value2);
}

private void init(Object arg1, Object arg2) {
  ... // error checks on combination of calculated arg1 and arg2 values
  ... // in reality there are more than 2 args, and this logic is fairly complex
  field1 = arg1;
  field2 = arg2;
  ... // other common initialization code, depends on field1 and field2
}

In order to reuse the assignments and common initialization code. However, since the fields are final, they can only be assigned in the constructor calls.

Can't get away from using public constructors to make objects the in this case, so any type of factory methods aren't possible. It would be nice to keep the combination error checks on the arg1, arg2 values in the same block as the assignments of them to the ClassA fields. Otherwise I would split init() into 2 functions, one pre-assignment of final fields and one post assignment of final fields, and make my constructors look like:

public ClassA(int arg1, String arg2, boolean arg3) {
  ... // error check params
  Object value1 = ... // calculate value 1 based on args
  Object value2 = ... // calculate value 2 based on args

  preinit(value1, value2);  // error checks combination of values
  field1 = value1;
  field2 = value2;
  postinit();  // other common initialization code
}

Suggestions? Is there any way to avoid this, or am I stuck splitting the init() function?

+1  A: 

Instead of having a separate init method, you can instead chain constructors together. So, calculate field and field2, then call a constructor that initializes them. Basically, replace init with:

private classA(Object arg1, Object arg2) {
  ... // error checks on combination of calculated arg1 and arg2 values
  ... // in reality there are more than 2 args, and this logic is fairly complex
  field1 = arg1;
  field2 = arg2;
  ... // other common initialization code, depends on field1 and field2
}

Of course, there can only be 1 constructor with 2 Objects as parameters.

Justin Ardini
+2  A: 

Why don't you create the objects value1 and value2 in a static factory method? Then you could have distinct factory methods replacing the current constructors, and a single (private) constructor doing what is now done in init:

public static createWithObjectParams(Object arg1, Object arg2) {
  ... // error check params
  Object value1 = ... // calculate value 1 based on args
  Object value2 = ... // calculate value 2 based on args

  return new ClassA(value1, value2);
}

public static createWithPrimitiveParams(int arg1, String arg2, boolean arg3) {
  ... // error check params
  Object value1 = ... // calculate value 1 based on args
  Object value2 = ... // calculate value 2 based on args

  return new ClassA(value1, value2);
}

private ClassA(Object arg1, Object arg2) {
  ... // error checks on combination of calculated arg1 and arg2 values
  ... // in reality there are more than 2 args, and this logic is fairly complex
  field1 = arg1;
  field2 = arg2;
  ... // other common initialization code, depends on field1 and field2
}
Péter Török
I have to use constructors to build the objects, unfortunately.
Tom Tresansky
@Tom, could you explain why?
Péter Török
@Peter Torok: politics. Its not under my control.
Tom Tresansky
A: 

Use static factory methods and a simple private constructor, which just assigns the values to the fields, and if necessary does other initializations.

starblue
I have to use constructors to build the objects, unfortunately.
Tom Tresansky
+1  A: 

You can push everything down to a 2 arg constructor, so that it is the only constructor that actually initializes the final fields and does validation.

E.g.

public ClassA(int arg1, String arg2, boolean arg3) {
   this(deriveO1(arg1, arg2, arg3));
   this(deriveO2(arg1, arg2, arg3));
}

public ClassA(Object arg1, Object arg2) {
   field1 = arg1;
   field2 = arg2;
   // do initialization checks, you can have this in a separate method, or check directly here.   
}

If You don't want the two Object version being called directly (e.g. say you want to do pre-validation before assignment) then declare a "dummy" argument. It feels a bit wrong, but it's really the only choice if you want to have public and private constructors with semantically the same arguments (they have to be syntacticly distinct.)

public ClassA(int arg1, String arg2, boolean arg3) {
   Object o1 = ...; 
   Object o2 = ...;
   this(o1, o2, false);
}

public ClassA(Object arg1, Object arg2) {
   this(arg1, arg2, false);
}

private ClassA(Object arg1, Object arg2, boolean dummy) {
   field1 = arg1;
   field2 = arg2;
   // do initialization checks, you can have this in a separate method, or check directly here.   
}
mdma
this does't compile. If you call other constructor, it has to be on the first line
nanda
D'oh of course. Please insert more coffee.
mdma
+1  A: 

First, I would try to look at builder patter, but if you insist to make it as constructor and the fields should be final, use private constructor:

public class ClassA {
    private final Object field1;
    private final Object field2;

    public ClassA(Object arg1, Object arg2) {
        this(calc1(arg1, arg2), calc2(arg1, arg2), true);
    }

    public ClassA(int arg1, String arg2, boolean arg3) {
        this(calc1(arg1, arg2, arg3), calc2(arg1, arg2, arg3), true);
    }

    private ClassA(Object arg1, Object arg2, boolean a) {
        field1 = arg1;
        field2 = arg2;
    }

    private static Object calc1(int arg1, String arg2, boolean arg3) {
        return ... // calculate value 1 based on args
    }

    private static Object calc2(int arg1, String arg2, boolean arg3) {
        return ... // calculate value 2 based on args
    }

    private static Object calc1(Object arg1, Object arg2) {
        return ... // calculate value 1 based on args
    }

    private static Object calc2(Object arg1, Object arg2) {
        return ... // calculate value 2 based on args
    }

}
nanda
Why pass *all* the logic to the private constructor, instead of just the common bits?
Justin Ardini
I edited it with a better solution
nanda
Actually I would only recommend a Builder if and only if most of the parameters are optional. BTW, this is NO Builder pattern. Static factories like Péter Török suggested are the best solution in my opinion. Easy to understand and implement.
Helper Method
A: 

Don't know if this is an option, but you could use Dependency Injection, i.e. you create the objects you're fields referring to somewhere else and just pass them in in the constructor. This makes your class much more easy to test and much more flexible.

public class ClassA {
  private final Object field1;
  private final Object field2;

  public Class A(Object field1, Object field2) {
        this.field1 = field1;
        this.field2 = field2;
  }
}


public class AppFactory {
    private AppFactory() {}

    public static ClassA newInstance(Object arg1, Object arg2) {
        // create the fields there
        ...

        // pass them in
        return new ClassA(field1, field2);         
    }

    public static ClassA newInstance(int arg1, String arg2, boolean arg3) {
        // create fields there, in a different way

        // pass them in
        return new ClassA(field1, field2);
    }
}

That way, you could:

  • Refer to the constructor parameters by their supertypes.
  • In the factory, return subtypes of ClassA
  • Pass in mock objects (dummy objects) for the fields, in order to test the class in isolation
  • many more benefits
Helper Method