У меня есть что-то вроде этого:
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 суперclassа является абстрактным и автоматически вызывается конструктором после создания объекта.
Мне любопытно, могу ли я столкнуться с некоторыми проблемами с таким кодом, когда мне нужно создать какой-то объект такого типа, структура которого не будет изменена во времени.
Будет ли какой-нибудь подход лучше? Он работает на Java, но будет ли он работать на C ++ и, возможно, в ActionScript?
Спасибо за ответ.
НЕ ПРИНИМАЙТЕ ОТКРЫТЫЕ МЕТОДЫ ОТ КОНСТРУКТОРА.
Цитата из Effective Java 2nd Edition, пункт 17: дизайн и документ для наследования, а также запрет :
Есть еще несколько ограничений, которые class должен соблюдать, чтобы разрешить наследование. Конструкторы не должны ссылаться на переопределяемые методы , прямо или косвенно. Если вы нарушите это правило, произойдет сбой программы. Конструктор суперclassа выполняется перед конструктором подclassа, поэтому метод переопределения в подclassе будет вызываться до запуска конструктора подclassа. Если метод переопределения зависит от любой инициализации, выполняемой конструктором подclassа, метод не будет вести себя так, как ожидалось.
Вот пример для иллюстрации:
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
, и метод получил неправильное значение. Это почти наверняка приведет к ошибкам и ошибкам.
Вы правы в том, что это может вызвать проблемы с производным classом, переменные экземпляра которого инициализируются в конструкторе или при создании экземпляра. Если у вас есть это:
public class ConcreteMenu extends Menu { String firstItem = "Item1"; protected void init() { addMenuItem(new MenuItem(firstItem)); // .... } }
Тогда MenuItem будет иметь null
поскольку это аргумент конструктора!
Вызов не конечных методов в конструкторах – это рискованная практика.
Простым решением может быть разделение конструкции и инициализации, например:
Menu menu = new ConcreteMenu(); menu.init();
Как упоминалось выше, вызов переопределяемого метода из конструктора входит в мир боли …
Рассматривали ли вы выполнение инициализации в самом конструкторе?
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")); // .... } }