Использование абстрактной функции init() в конструкторе абстрактного класса

У меня есть что-то вроде этого:

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

Как вы можете видеть, метод superclass init является абстрактным и автоматически вызывается конструктором после создания объекта.

Мне любопытно, могу ли я столкнуться с некоторыми проблемами с таким кодом, когда мне нужно создать какой-то объект такого типа, структура которого не будет изменена во времени.

Будет ли какой-нибудь подход лучше? Он работает на Java, но будет ли он работать на С++ и возможно ActionScript?

Спасибо за ответ.

Ответ 1

НЕ ПРИНИМАЙТЕ ОТВЕРЖДЕННЫЕ МЕТОДЫ ОТ КОНСТРУКТОРА.

Цитата из 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, и метод получил неправильное значение. Это почти наверняка приведет к ошибкам и ошибкам.

Связанные вопросы

См. также

Ответ 2

Вы правы в том, что это может вызвать проблемы с производным классом, переменные экземпляра которого инициализируются в конструкторе или при создании экземпляра. Если у вас есть это:

public class ConcreteMenu extends Menu {
 String firstItem = "Item1";

 protected void init() {
  addMenuItem(new MenuItem(firstItem));
  // ....
 }
}

Тогда в MenuItem будет null как аргумент конструктора!

Вызов не конечных методов в конструкторах - рискованная практика.

Простым решением может быть разделение конструкции и инициализации следующим образом:

Menu menu = new ConcreteMenu();
menu.init();

Ответ 3

Как упоминалось выше, вызов переопределяемого метода из конструктора входит в мир боли...

Рассматривали ли вы выполнение инициализации в самом конструкторе?

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