在任何派生类的构造函数之后运行方法

2022-09-01 14:34:45

假设我有一个 Java 类

abstract class Base {
    abstract void init();
    ...
}

我知道每个派生类在构造后都必须调用。当然,我可以简单地在派生类的构造函数中调用它:init()

class Derived1 extends Base {
    Derived1() {
        ...
        init();
    }
}

class Derived2 extends Base {
    Derived2() {
        ...
        init();
    }
}

但这严重违反了“不要重复自己”的原则(并且会有很多子类)。当然,调用不能进入构造函数,因为它的执行太早了。Baseinit()Base()

任何想法如何绕过这个问题?我也很高兴看到Scala的解决方案。

更新:以下是工厂方法方法的通用版本:

interface Maker<T extends Base> {
    T make();
}

class Base {
    ...
    static <T extends Base> T makeAndInit(Maker<T> maker) {
        T result = maker.make();
        result.init();
        return result;
    }
}

更新2:这个问题基本上是“你如何为构造函数使用模板方法”?答案似乎是,“你可以,但这是一个坏主意”。所以我可以做一个模板工厂(模板方法+抽象工厂)。


答案 1

避免这种情况。如果这样做,任何扩展类的类都可能决定也调用,从而使对象处于不一致状态。DerivedXinit()

一种方法是让类的客户端手动调用该方法。有一个字段,如果调用任何需要初始化的方法而不使用它,则抛出。init()initializedIllegalStateExcepion

更好的方法是使用静态工厂方法而不是构造函数:

public Derived2 extends Base {
    public static Derived2 create() {
       Derived2 instance = new Dervied2();
       instance.init();
       return instance;
    }
}

更新:正如您在更新中建议的那样,您可以将 Builder 传递给静态工厂方法,该方法将在实例上调用 。如果你的子类很少,我认为这是一个过于复杂的问题。init()


答案 2

中发生了什么?更好的设计可能会完全消除该方法,或者至少放宽它在子类的构造函数之后执行的要求。请确保在构造函数完成之前,不会使正在构造的对象对任何其他线程可见,因为这会产生并发错误。init()init()

作为一种(丑陋的)替代方案,抽象方法可以由子类作为伪构造函数实现:

abstract class Base {
  Base() {
    ctor();
    init();
  }
  abstract void ctor();
  abstract void init();
}