Java - 强制子类在构造函数之后调用超级方法

问题描述 投票:0回答:5

我希望一堆子类在完成构造函数后调用超级方法,如下所示:

public abstract class Superclass {

    ...

    public Superclass(...) {
        ...    // do stuff before initializing subclass
    }

    protected void dispatch() {     //method to be called directly after creating an object
        doStuff();
        ...
    }

    public abstract void doStuff();
}

public class Subclass extends Superclass {

    ...

    public Subclass(...) {
        super(...);     //has to be the first line
        ...             //assign variables etc.
        dispatch();     //has to be called after variables are assigned etc.
    }

    public void doStuff() {
        //do stuff with assigned variables etc.
    }
}

dispatch()
函数包含创建对象后与其相关的一系列操作,这些操作必须适用于所有子类。我无法将此函数移动到超级构造函数中,因为它调用需要已分配变量的子类中的方法。但由于
super()
需要成为子构造函数的第一行,所以在调用超级构造函数之前我无法设置任何变量。

它会像现在一样工作,但我发现在每个子类的构造函数末尾调用

dispatch()
是一个不好的概念。有没有更优雅的方法来解决这个问题?或者我应该彻底重新思考我的概念吗?

java inheritance constructor
5个回答
6
投票

您的请求违反了多项 Java 最佳实践,例如:

  • 不要在构造函数中进行复杂的配置,仅填充私有(最终)成员变量,并且仅进行非常基本的一致性检查(如果有的话)。

  • 不要从构造函数中调用

    non private
    non final
    方法,即使是间接调用也不行。

所以我强烈建议考虑一下你的课程设计。很可能,您的班级太大并且责任太多。


3
投票

它会像现在一样工作,但我发现调用它是一个坏概念 在每个子类构造函数的末尾都有dispatch()。有没有 更优雅的方法来解决这个问题?或者我是否应该彻底重新思考我的 概念?

正如 Timothy Truckle 所强调的,你的构造函数逻辑太复杂了。

使用模板方法初始化子类实例,可以让事情变得更简单,达到你的目的。 请注意,您已经使用此模式与

doStuff()

子类构造函数确实是您的问题:您希望减少每个子类中所需的强制性样板,并使其具有更好的可读性和可维护性。
因此,在超类中引入一个新的模板方法,并从超类的构造函数中调用它。
此方法将执行与构造函数相同的操作,但可以以更灵活的方式调用。
dispatch()
这是一种人工方法,只是为了不需要技巧而引入。
整个逻辑可以从超类构造函数中编排。

超级类可能看起来像:

public abstract class Superclass {

    ...

    public Superclass(...) {
        ...    // do stuff before initializing subclass
        init();
        doStuff();
    }

    public abstract void init();

    public abstract void doStuff();
}

并在子类中,替换:

public Subclass(...) {
    super(...);     //has to be the first line
    ...             //assign variables etc.
    dispatch();     //has to be called after variables are assigned etc.
}

作者:

public Subclass(...) {
    super(...);   // let the super constructor to orchestrate the init logic  
}

public void init(){
    // move the constructor logic here
}

结果要简单得多,因为这种设计将与子类的初始化“算法”相关的职责集中在一个地方:超类构造函数。


关于您的评论:

这确实看起来比我做的要优雅得多。谢谢你!编辑: 只是注意到,这不适用于具有不同的子类 构造函数参数。知道如何解决这个问题吗?

有了这样的需求,为了让事情简单明了,就得分两步来做:

  • 实例化对象
  • 调用引用的
    init()
    方法。

它可能看起来像:

SuperClass o = new Subclass(argFoo, argBar); 
o.init();

这种方式的问题是你不确定

init()
方法是否被调用。您可以添加一个标志,每次在对象上调用方法时都会检查该标志。但确实很麻烦而且容易出错。避免这种情况。
为了改进这一点,我可能会使用包装模式。
您还可以使用拦截器/方面。但这不是一个好的用例:初始化处理不是横向的,并且确实与对象行为相关。保持其可见性更有意义。

使用包装纸,它可能看起来像:

SuperClass o = new MyWrapper(new Subclass(argFoo, argBar));

其中

MyWrapper
SuperClass
的子类,并包装
SuperClass
对象的实例:

public class MyWrapper implements SuperClass{

   private SuperClass wrapped;

   public MyWrapper (SuperClass wrapped){
       this.wrapped = wrapped;
       this.wrapped.init();
   }

   // then delegate each superclass method to the wrapped object
   public void doStuff(){
       this.wrapped.doStuff();
   }

  // and so for...

}

0
投票

Lorelorelore 是正确的,如果您确实需要在调用方法时完成任何子类实例化。否则,你可以做你所拥有的。如果其他人需要使用该代码,我建议添加足够的注释。


0
投票

您可以通过提供static方法来

抽象
超类的使用,该方法将执行您想要的代码,但也会检查它是否已正确设置:

public abstract class SuperClass{
    private boolean instantiated;

    public SuperClass(...){
        ...
    }

    public abstract void doStuff();

    private void dispatch(){
        if(!instantiated){
            instantiated = true;
            doStuff();
        }
    }

    public static void executeActionOnSuperClass(SuperClass s){
        s.dispatch(); // call instantiation if not already done
        s.executeAnAction();
    }
}

以及子类:

public class SubClass extends SuperClass{
    public SubClass(...){
        super(...);
    }

    public void doStuff(){
         ...
    }
}

然后可以像这样执行:

SuperClass.executeAnActionOnSuperClass(new SubClass(...));

尽管这主要是一种反模式,应该谨慎使用。


0
投票

我遇到了同样的问题,工厂强制执行额外的初始化逻辑

internal class ExpenseFactory
{
    internal static Expense CreateExpense(ExpenseArguments args)
    {
        Expense? expense = null;
        switch (args.ExpenseType)
        {
            case ExpenseType.EQUAL:
                expense = new EqualExpense(Guid.NewGuid().ToString(), args.Amount, args.PaidBy, args.SharedBy);
                break;
            case ExpenseType.EXACT:
                expense = new ExactExpense(Guid.NewGuid().ToString(), args.Amount, args.PaidBy, args.SharedBy, args.ExactAmounts);
                break;
            case ExpenseType.PERCENT:
                expense = new PercentExpense(Guid.NewGuid().ToString(), args.Amount, args.PaidBy, args.SharedBy, args.Percentages);
                break;
            default:
                throw new ArgumentException("Ivalid expense arguments");
        }

        expense.InitSplits(); // additional init logic implemented by each subclass and declared as abstract in parent class Expense 
        return expense;
    }
}

我认为工厂方法是复杂初始化逻辑所属的地方。完整的代码示例可以在这里找到

© www.soinside.com 2019 - 2024. All rights reserved.