简体   繁体   中英

Java - Chaining Constructors in Abstract Class

I am working on a simulation where I want to create a family of missile classes based on a GenericMissile abstract class. I would like to make sure all the common initialization happens with the GenericMissile, so that I can avoid having duplicated code spread around to the subclasses. Some parameters have identical values for all missiles, so those go in the GenericMissile. Additionally, most other parameters are common to all missiles, just with different values. Finally, there are a few optional parameters that some missiles may or may not have (and they can exist in different permutations, even for the same subclass). My question is about how I can properly chain the constructors in the abstract class to achieve this behavior. Here is a mock up of what I'm trying to do:

public abstract class GenericMissile {
    public abstract void initSpecificParams();
    private double x;
    private double y;
    private double z;
    protected int optionalParam1 = 0;
    protected int optionalParam2 = 0;
    protected int optionalParam3 = 0;



public GenericMissile(double x, double y, double z) {  // basic constructor with required params
    this.x = x;
    this.y = y;
    this.z = z;
    initSpecificParams();  // each missile type initializes common params that needs specialized values
}

// --------------------------------
// OPTION 1:  duplicate everything from basic constructor, and add optional stuff
public GenericMissile(double x, double y, double z, int optionalParam1, int optionalParam2, int optionalParam3) {
    this.x = x;  // duplicating all these assignments
    this.y = y;
    this.z = z;
    this.optionalParam1 = decode(optionalParam1);  // using "decode" as a generic representation for doing unit conversion, scaling, enum-to-enum type mappings, etc.
    this.optionalParam2 = decode(optionalParam2);
    this.optionalParam3 = decode(optionalParam3);  
    initSpecificParams();
}
... // create one constructor like this for each combo of optional Params


// -------------------------------
// OPTION 2:  duplicate everything from basic constructor, and add optional stuff
public GenericMissile(doublex, double y, double z, int optionalParam1) {
     this(x,y,z);
     initSpecificParams(optionalParam1);
}
// ... (create a GenericMissile constructor with each permutation of optionalParams, that calls the appropriate initSpecificParams(...)

//------------------------------
// OPTION 3: try to re-use the first constructor (which is tricky because of the dependence of initSpecificParams() on the optional parameters being set 
public GenericMissile(double x, double y, double z, int optionalParam1, int optionalParam2, int optionalParam3) {  // When a missile type uses optional parameter, it uses this constructor instead
   this.optionalParam1 = optionalParam1;   // I know this is illegal, but this is what I would like to do, because initSpecificParams() will check for these optional parameter values
   this.optionalParam2 = optionalParam2;   
   this.optionalParam3 = optionalParam3;  
   this(x,y,z);  // not on the first line  :(
}

}

public class MissileA extends GenericMissile {
     public MissileA(double x) {
         super(x);
     }
     // Note: three constructors with optional params, all re-using the same GenericMissile constructor (good!), which in turn calls the sub-class implementation of initSpecificParams()
     public MissileA(double x, double y, double z, int optionalParam1) {
         super(x, y, z, optionalParam1, optionalParam2, optionalParam3);
     }

 public MissileA(double x, double y, double z, int optionalParam1, int optionalParam2) {
     super(x, y, z, optionalParam1, optionalParam2, optionalParam3);
 }

  public MissileA(double x, double y, double z, int optionalParam1, int optionalParam2, int optionalParam3) {
     super(x, y, z, optionalParam1, optionalParam2, optionalParam3);
 }

 // --------------------------
 // OPTION 1:
 // Ideally, I would be able to set any optional parameters in the Generic constructor, and then use the same initSpecificParams() method regardless of which 
 // optional parameters are being used - no duplication
 public void initSpecificParams() {
     if (optionalParam1 != 0 ) {
         readSpecificParameterFile1(optionalParam1);
     } else {
         readDefaultParameterFile();
     }
     if (optionalParam2 != 0 ) {
         readSpecificParameterFile2(optionalParam2);
     } else {
         readDefaultParameterFile();
     }
     if (optionalParam3 != 0) {
         readSpecificParameterFile3(optionalParam3);
     } else {
         readDefaultParameterFile();
     }

    do_common_stuff(); // NOTE: this common initialization depends on which parameter files are loaded in the previous if-else blocks
 }

 // -----------------------------
 // OPTION 2: include these extra initSpecificParams() methods
 // If I cannot set optional params in GenericMissile constructor in front of another constructor call, I have to build separate initSpecificParams() methods to explictly 
 // pass in the optional params, instead of just setting them as instance variable values -- lots of duplication in having to do that
 public void initSpecificParams(int optionalParam1) {
     this.optionalParam1 = optionalParam1; 
     initSpecificParams();  // NOTE: no way to force subclasses to chain these initialization methods properly

 }    

 // Significant duplication
 public void initSpecificParams(int optionalParam1, int optionalParam2) {
     this.optionalParam1 = optionalParam1; 
     this.optionalParam2 = optionalParam2; 
     initSpecificParams();  // NOTE: no way to force subclasses to chain these initialization methods properly
 }    


 // Significant duplication
 public void initSpecificParams(int optionalParam1, int optionalParam2, int optionalParam3) {
     this.optionalParam1 = optionalParam1; 
     this.optionalParam2 = optionalParam2; 
     this.optionalParam3 = optionalParam3; 
     initSpecificParams();  // NOTE: no way to force subclasses to chain these initialization methods properly
 }

}

It seems like I'm forced to either:

  1. duplicate all the code in both GenericMissile constructors (with the additional parameter settings in the second one), or
  2. duplicate the initSpecificParams() methods in every subclass AND make sure they are chained together (initSpecificParams(optional_param) would have to call initSpecificParams()).

Is there some cleaner way to do this where I don't have to duplicate so much code? NOTE: I've shortened this example...in reality there are a very large number of generic parameters, common parameters with different values, and optional parameters. So lots of SLOC here.

In this case, you can simply chain the constructors the other way:

public GenericMissile(double x) {
  this(x, "");
}

public GenericMissile(double x, String optionalParam) {
  this.x = x;
  this.optionalParam = optionalParam;
  initSpecificParams();
}

But generally this kind of multi-step initialization is a code smell (as Jon Skeet says in the comments, you probably shouldn't be doing a virtual method call from a constructor anyway).

It's hard to tell from your example, but you might have better success by using composition instead of inheritance , perhaps by applying the Strategy pattern .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM