如何避免在 child 类 中创建无用的 pass-through 构造函数只是为了将参数传递给 "super()"?

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

在Java中,据我所知,childclass 继承一个有参数的构造函数。

例如

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
}

修复它的唯一方法是在 Child class:

中创建一个无用的 pass-through 构造函数
public class Child extends Parent {
    public Child(int x) {
         super(x);
    }
}

问题:

如果我有一个复杂的 subclasses 层次结构,有 6-10 个 subclasses,添加这样一个无意义的 pass-through 参数为 的构造函数subclasses 中的每一个似乎都是个坏主意!

问题:

对于大型 class 层次结构,有没有办法避免这个问题?

注:

我知道一个参数的解决方案(有一个 setter,必须与构造函数分开调用)。但是这个解决方案有几个很大的缺点,因此不被接受。

你在 Parent 上只有一个接受参数的构造函数这一事实告诉我你不能创建没有所述 int 的 Parent。通过创建 Child,您通过继承也创建了 Parent。因为我们需要一个 int 来创建 ParentChild 必须从某个地方提供它,无论是从它自己的构造函数参数还是其他一些静态值。

如果您不想在 Parent 的每个子类中都提供传递构造函数,则需要为其提供无参数构造函数并定义其含义。这可以是您已有的构造函数的补充,也可以替换它,这取决于您。但是无参数构造函数是你做你想做的事情所必需的。

大约 15 年前,我遇到了与您类似的问题,具有非常广泛(但只是稍微深)的层次结构(是的,伙计们,有一个 原因 它是原来的样子)。但是由于它们都是从我们定期需要向其添加更多信息的基础派生的,因此很快就会发现向基础构造函数添加参数非常痛苦。

默认情况下不继承构造函数是件好事,但有时您想要继承它们。有时我想要一个可以添加到 class 的编译时注释来告诉编译器 "auto-generate constructors for any of super's constructors I don't explicitly implement"。但是我们没有它,如果 JSR 完全成功的话,它需要数年才能完成...

没有那个神奇的注释,我们使用的解决方案正是@psabbate :

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.

...但参数具有特定的类型层次结构 classes(不是 Map)。

为了完整性,举个例子:

// 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());
    }
}

对我来说甚至 "least bad." 具有表示层次结构所需的基本信息的类型是有表现力的。

如果子class 需要比标准参数(为我们准备的)更多的信息,您可以选择第二个参数 class 类型作为第二个参数(但是你在树中有两种类型的构造函数)或在参数 class 层次结构中使用继承;我们使用了后者:

// 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());
    }
}

在我的案例 IIRC 中,我们只有三个参数 classes(树中特定分支的标准 1 和 2 个子参数)跨越层次结构中的 30 个主要 classes。

最后一点:对我们来说,在构造它时,我们需要向参数 class 提供一组核心内容,这些内容没有变化,并且有八个合理的选项,基本默认值,但您可以覆盖。为了避免参数 classes 中的构造函数激增,我们最终对它们做了一些穷人的构建器模式("poor man's" 因为我们让 classes 成为它们自己的构建器而不是而不是将其分离出来;只是没有必要 严格对待它,更改的内容具有廉价的默认值,因此实例始终处于有效状态,即使在构建时也是如此) .所以我们的构造看起来像这样:

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

使用工厂方法和反射:

public class A {
    protected A() {
        // No longer do initialization in constructor
        // initialize in init(), which is guaranteed to be called
    }

    protected A init(int i) {
        // do all initialization with i in here
        return this;
    };

    public static <T extends A> T create(Class<T> clazz, int i) {
        try {
            return (T) clazz.newInstance().init(i);
        } catch (InstantiationException | IllegalAccessException e) {
            throw new IllegalArgumentException("not going to happen", e);
        }
    }
}

然后创建实例:

B b = A.create(B.class, 86);

根据您想要的学科级别,子classes 可以很简单:

public class B extends A {
    // nothing special needed
}

但允许直接实例化 B b = new B() 不需要 int,或者您可以收紧一切以防止这种情况发生:

public class B extends A {
    protected B() {
        super();
    }
    // nothing else special needed
}

这需要通过工厂方法进行所有实例化。

可以使用您的 IDE.

轻松重构调用点处的代码

如果您的基础 class 更改为需要 more/different 初始化变量,您只需更改工厂方法(子 classes 中没有任何更改),或者您可以添加一个新的工厂方法,保留旧的工厂方法以实现向后兼容性。