我有这样的事情:
public abstract class Menu {
public Menu() {
init();
}
protected abstract void init();
protected void addMenuItem(MenuItem menuItem) {
// some code...
}
}
public class ConcreteMenu extends Menu {
protected void init() {
addMenuItem(new MenuItem("ITEM1"));
addMenuItem(new MenuItem("ITEM2"));
// ....
}
}
//Somewhere in code
Menu menu1 = new ConcreteMenu();
正如您所看到的,超类的init方法是抽象的,并且在创建对象后由构造函数自动调用。
我很好奇我是否可以遇到类似代码的某些问题,当我需要创建一些这样的结构时,其结构不会及时更改。
会有更好的方法吗?它适用于Java,但是它可以在C ++和ActionScript中使用吗?
感谢您的回答。
答案 0 :(得分:12)
不要从构造函数中发现过多的方法。
引用来自 Effective Java 2nd Edition,第17项:继承的设计和文档,或者禁止它:
为了允许继承,类必须遵守一些限制。 构造函数不得直接或间接调用可覆盖的方法。如果违反此规则,将导致程序失败。超类构造函数在子类构造函数之前运行,因此在子类构造函数运行之前将调用子类中的重写方法。如果重写方法依赖于子类构造函数执行的任何初始化,则该方法将不会按预期运行。
以下是一个例子来说明:
public class ConstructorCallsOverride {
public static void main(String[] args) {
abstract class Base {
Base() { overrideMe(); }
abstract void overrideMe();
}
class Child extends Base {
final int x;
Child(int x) { this.x = x; }
@Override void overrideMe() {
System.out.println(x);
}
}
new Child(42); // prints "0"
}
}
此处,当Base
构造函数调用overrideMe
时,Child
尚未完成初始化final int x
,并且该方法的值不正确。这几乎肯定会导致错误和错误。
答案 1 :(得分:2)
你是对的,它可能会导致派生类出现问题,派生类的实例变量在构造函数中初始化或者在创建实例时。 如果你有这个:
public class ConcreteMenu extends Menu {
String firstItem = "Item1";
protected void init() {
addMenuItem(new MenuItem(firstItem));
// ....
}
}
然后MenuItem将null
作为构造函数参数!
在构造函数中调用非final方法是一种冒险的做法。
一个简单的解决方案可能是分离构造和初始化,如下所示:
Menu menu = new ConcreteMenu();
menu.init();
答案 2 :(得分:0)
正如其他人提到的那样,从构造函数中调用一个可覆盖的方法正在进入一个痛苦的世界......
您是否考虑过在构造函数中进行初始化?
public abstract class Menu {
public Menu() {
....
}
protected void addMenuItem(MenuItem menuItem) {
// some code...
}
}
public class ConcreteMenu extends Menu {
public ConcreteMenu() {
super();
addMenuItem(new MenuItem("ITEM1"));
addMenuItem(new MenuItem("ITEM2"));
// ....
}
}