DVK DVK - 2 months ago 9
Java Question

How can I avoid creating useless pass-through constructors in child classes just to pass arguments to "super()"?

In Java, as far as I'm aware, a child class does NOT inherit a constructor that has arguments.

E.g.

public class Parent {
public Parent(int x) {
DoSomethingWithX(x);
}
}

public class Child extends Parent {
// Compile fails with "Implicit super constructor Parent() is undefined
// for default constructor. Must define an explicit constructor
}


The only way to fix it is to create a useless pass-through constructor in Child class:

public class Child extends Parent {
public Child(int x) {
super(x);
}
}


Problem:



If I have a complex hierarchy of subclasses, with 6-10 subclasses, adding such a meaningless pass-through constructor with arguments to every single one of subclasses seems to be a bad idea!


  • The code plain out looks stupid (10 copies of same method in 10 classes)

  • Worse, as with ANY code repetition, the code becomes fragile - any change (e.g. adding 1 more parameter) needs to be made in 10 places instead of one.



Question:



Is there a way to avoid this issue for a large class hierarchy?

Note:



I'm aware of one solution (have a setter, that has to be called separate from constructor) for the parameters. But this solution has several big downsides and isn't acceptable because of them.

Answer

Something like 15 years ago I had a similar problem to yours with a very broad (but only slightly deep) hierarchy (and yes, folks, there was a reason it was the way it was). But since they all derived from a base that we periodically needed to add more information to, it was quickly apparent that adding parameters to the base constructor was prohibitively painful.

Not inheriting constructors by default is a good thing, but sometimes you want to inherit them. I've sometimes wanted a compile-time annotation I could add to a class to tell the compiler "auto-generate constructors for any of super's constructors I don't explicitly implement". But we don't have it, and a JSR would take years to go through if it were successful at all...

Not having that magic annotation, the solution we used was exactly the one @psabbate mentioned in his/her comment:

what if your constructors receive a Map, or a CustomClass class that contains every parameter you could possible need. That way if you need to change parameters and arguments you can only change the class you are interested about.

...but with a specific type hierarchy for the parameters classes (not Map).

For completeness, an example:

// The standard parameters needed
class StandardParams {
    private String thisArg;

    public StandardParams(String thisArg) {
        this.thisArg = thisArg;
    }

    public String getThisArg() {
        return this.thisArg;
    }
}

// The base class
class Base {
    public Base(StandardParams args) {
        System.out.println("Base: " + args.getThisArg());
    }
}

// A standard subclass
class Sub1 extends Base {
    public Sub1(StandardParams args) {
        super(args);
        System.out.println("Sub1 thisArg: " + args.getThisArg());
    }
}

To me it's not even "least bad." Having a type that represents the base information that the hierarchy needs is expressive.

If a subclass needs more information than the standard parameters (which came up for us), you have the option of a second parameters class type that you use as a second parameter (but then you have two types of constructors in the tree) or using inheritance in the parameters class hierarchy; we used the latter:

// Extended parameters (naturally you make these names meaningful)
class ExtendedParams extends StandardParams {
    private String thatArg;

    public ExtendedParams(String thisArg, String thatArg) {
        super(thisArg);
        this.thatArg = thatArg;
    }

    public String getThatArg() {
        return this.thatArg;
    }
}

// A subclass requiring extended parameter information
class Sub2 extends Base {
    public Sub2(ExtendedParams args) {
        super(args);
        System.out.println("Sub2 thisArg: " + args.getThisArg());
        System.out.println("Sub2 thatArg: " + args.getThatArg());
    }
}

In my case IIRC, we only had three parameters classes (the standard one and two subs for particular branches in the tree) across something like 30 main classes in the hierarchy.

A final note: For us, there was a core set of things we needed to provide to the parameters class when constructing it which didn't vary and there were something like eight options that had reasonable, basic defaults but you could override. To avoid an explosion of constructors in the parameter classes, we ended up doing a bit of a poor man's builder pattern on them ("poor man's" because we made the classes their own builders rather than separating it out; it just wasn't necessary to be that rigorous about it, the things that changed had inexpensive defaults and so the instance was always in a valid state even when being built). So construction for us looked something like this:

Thingy t = new Thingy(
    new ThingyParams(basic, construction, info)
    .withAnswer(42)
    .withQuestion("Life, the Universe, and Everything")
);
Comments