Запуск метода после конструктора любого производного класса - PullRequest
21 голосов
/ 25 мая 2010

Допустим, у меня есть класс Java

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

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

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

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

но это довольно сильно нарушает принцип "не повторяйся" (и будет много подклассов Base). Конечно, вызов init() не может войти в конструктор Base(), так как он будет выполнен слишком рано.

Есть идеи, как обойти эту проблему? Я был бы очень рад увидеть решение Scala тоже.

ОБНОВЛЕНИЕ: Вот общая версия подхода Factory Method:

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: Этот вопрос в основном "как вы используете Template Template для конструкторов"? И ответ, кажется, "Вы можете, но это плохая идея". Поэтому я могу вместо этого создать Фабрику шаблонов (Template Method + Abstract Factory).

Ответы [ 6 ]

11 голосов
/ 25 мая 2010

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

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

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

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

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

10 голосов
/ 25 мая 2010

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

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

abstract class Base {
  Base() {
    ctor();
    init();
  }
  abstract void ctor();
  abstract void init();
}
6 голосов
/ 25 мая 2010

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

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

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

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

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

2 голосов
/ 27 мая 2010

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

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

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

1 голос
/ 25 мая 2010

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

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

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

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

1 голос
/ 25 мая 2010

Или используйте пружину ... вы можете сделать <beans default-init-method="init">, см. Методы инициализации и уничтожения по умолчанию .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...