Выполнение метода после конструктора любого производного класса

Скажем, у меня есть класс Java

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

и я знаю, что каждый производный класс должен будет звонить init() после того, как это создается. Я мог, конечно, просто назвать его в конструкторах производных классов:

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

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

но это повреждается, "не повторяют себя" принцип скорее плохо (и там будут многими подклассами Base). Конечно, init() вызов не может войти 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: Этот вопрос в основном, "как Вы используете Шаблонный Метод для конструкторов"? И ответ, кажется, "Вы можете, но это - плохая идея". Таким образом, я могу сделать Шаблонную Фабрику (Шаблонный Метод + Абстрактная Фабрика) вместо этого.

22
задан Alexey Romanov 5 August 2013 в 12:59
поделиться

6 ответов

Что происходит в init () ? Вероятно, что лучший дизайн мог бы полностью исключить метод или, по крайней мере, ослабить требование, чтобы он выполнялся после конструктора подкласса. Убедитесь, что init () не делает строящийся объект видимым для любых других потоков до завершения конструктора, потому что это создает ошибки параллелизма.

В качестве (уродливой) альтернативы абстрактный метод может быть реализован подклассами как псевдоконструктор:

abstract class Base {
  Base() {
    ctor();
    init();
  }
  abstract void ctor();
  abstract void init();
}
10
ответ дан 29 November 2019 в 05:15
поделиться

Избегайте этого. Если вы это сделаете, любой класс, расширяющий ваш класс DerivedX , может решить также вызвать init () , таким образом оставив объект в несогласованном состоянии.

Один из подходов - позволить клиенту вашего класса вручную вызывать метод init () . Иметь инициализированное поле и выбросить IllegalStateExcepion , если какой-либо метод, требующий инициализации, вызывается без него.

Лучшим подходом было бы использование статического фабричного метода вместо конструкторов:

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

Обновление: как вы предлагаете в своем обновлении, вы можете передать Builder статическому фабричному методу, который вызовет init () в экземпляре. Если у вас мало подклассов, я думаю, что это чрезмерное усложнение.

11
ответ дан 29 November 2019 в 05:15
поделиться

В дополнение к рекомендации Божо, контейнер приложения отлично подходит для этой задачи.

Пометьте свой метод init() аннотацией javax.annotation.PostConstruct, и правильно настроенный контейнер EJB или Spring выполнит метод после завершения инъекций зависимостей, но до того, как объект сможет быть использован приложением.

Пример метода:

@PostConstruct
public void init() { 
    // logic..
}

В корпоративном приложении вы можете открыть ресурсы, например, файловую систему в методе init(). Эта инициализация может вызвать исключения и не должна вызываться из конструктора.

6
ответ дан 29 November 2019 в 05:15
поделиться

Если по какой-то причине вы против использования фабрик, вы можете использовать следующий трюк:

trait RunInit {
    def init():Unit
    init()
}

class Derived1 extends Base with RunInit {
    def init() = println("INIT'ing!")
}

Это запустит init () перед конструктором / телом Derived1.

1
ответ дан 29 November 2019 в 05:15
поделиться

Или используйте spring ... вы можете использовать , см. Методы инициализации и уничтожения по умолчанию .

1
ответ дан 29 November 2019 в 05:15
поделиться

если бы в Java это было, мы бы не видели всех этих вызовов метода init() в природе.

"окружить чем-то дочерний конструктор" - это невозможно сделать в чистой java. Жаль, потому что могут быть очень интересные приложения, особенно с анонимным классом + блоком инициализации экземпляра.

фабрика и контейнер - они могут быть полезны, когда родной new не справляется с задачей; но это тривиально и скучно, и не будет работать с анонимными классами.

2
ответ дан 29 November 2019 в 05:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: