Конструкторы против заводских методов

при моделировании классов, каков предпочтительный способ инициализации:

  1. конструкторы, или
  2. Методы Фабрики

и каковы были бы соображения для использования любого из них?

в некоторых ситуациях я предпочитаю иметь заводской метод, который возвращает null, если объект не может быть построен. Это делает код аккуратным. Я могу просто проверить, не является ли возвращаемое значение нулевым, прежде чем предпринимать альтернативные действия, в отличие от с выбрасыванием исключения из конструктора. (Я лично не люблю исключений)

скажем, у меня есть конструктор класса, который ожидает значение id. Конструктор использует это значение для заполнения класса из базы данных. В случае, если запись с указанным идентификатором не существует, конструктор создает исключение RecordNotFoundException. В этом случае мне придется заключить построение всех таких классов в пределах попытки..блок catch.

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

какой подход лучше в этом случае, конструктор или метод фабрики?

10 ответов


из страница 108 из шаблоны проектирования: элементы многоразового объектно-ориентированного программного обеспечения Gamma, Helm, Johnson и Vlissides.

используйте шаблон Заводского метода, когда

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

спросите себя, что они такое и почему они у нас есть. Они оба существуют для создания экземпляра объекта.

ElementarySchool school = new ElementarySchool();
ElementarySchool school = SchoolFactory.Construct(); // new ElementarySchool() inside

пока нет разницы. Теперь представьте, что у нас есть различные типы школ, и мы хотим переключиться с использования ElementarySchool на HighSchool (который является производным от ElementarySchool или реализует тот же интерфейс ISchool, что и ElementarySchool). Изменение кода будет:

HighSchool school = new HighSchool();
HighSchool school = SchoolFactory.Construct(); // new HighSchool() inside

в случае интерфейса мы хотели есть:

ISchool school = new HighSchool();
ISchool school = SchoolFactory.Construct(); // new HighSchool() inside

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

и в этом главное отличие и преимущество. Когда вы начинаете работать со сложными иерархиями классов и хотите динамически создать экземпляр класса из такой иерархии, вы получаете следующий код. Методы фабрики могут возьмите параметр, который сообщает методу, какой конкретный экземпляр нужно создать. Предположим, у вас есть класс MyStudent, и вам нужно создать соответствующий объект ISchool, чтобы ваш ученик был членом этой школы.

ISchool school = SchoolFactory.ConstructForStudent(myStudent);

теперь у вас есть одно место в вашем приложении, которое содержит бизнес-логику, которая определяет, какой объект ISchool создать для различных объектов IStudent.

So - для простых классов (объекты значений и т. д.) конструктор просто отлично (вы не хотите overengineer приложения), но для сложных иерархий метод фабрики класса является предпочтительным способом.

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


нужно читать (если у вас есть доступ к) Эффективная Java 2 пункт 1: рассмотрите статические заводские методы вместо конструкторов.

статические методы фабрики преимущества:

  1. у них есть имена.
  2. них не требуется создавать новый объект каждый раз, когда они вызываются.
  3. они могут возвращать объект любого подтипа возвращаемого типа.
  4. они уменьшают многословность создания параметризованных экземпляр типа.

недостатки статических заводских методов:

  1. при предоставлении только статических заводских методов классы без открытых или защищенных конструкторов не могут быть подклассами.
  2. они не легко отличимы от других статических методов

по умолчанию конструкторы должны быть предпочтительными, потому что их проще понять и написать. Однако, если вам конкретно нужно отделить тонкости построения объекта от его семантического значения, как это понимается клиентским кодом, вам лучше использовать фабрики.

разница между конструкторами и фабриками аналогична, скажем, переменной и указателю на переменную. Существует еще один уровень косвенности, который является недостатком; но есть еще один уровень гибкости, что является преимуществом. Поэтому, делая выбор, вам было бы неплохо сделать этот анализ затрат и выгод.


цитата из "эффективной Java", 2-е изд., Пункт 1: Рассмотрим статические заводские методы вместо конструкторов, p. 5:

"обратите внимание, что статический заводской метод не совпадает с шаблоном Заводского метода из шаблонов проектирования [Gamma95, p. 107]. Статический заводской метод, описанный в этот элемент не имеет прямого эквивалента в шаблонах проектирования."


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

фабрики имеют возможность кэширования, например.

другой способ использования фабрик-в сценарии, где вы не знаете тип, который хотите построить. Часто вы видите этот тип использования в сценариях фабрики плагинов, где каждый плагин должен быть производным от базового класса или реализовывать какой-то интерфейс. Фабрика создает экземпляры классы, производные от базового класса или реализующие интерфейс.


конкретный пример из приложения CAD / CAM.

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

форма была бы сделана путем использование фабрики. Потому что, пока есть класс shape, каждая форма будет настроена по-разному в зависимости от типа фигуры это. Мы не знаем, какую форму мы будем инициализировать, пока пользователь не сделает выбор.


скажем, у меня есть конструктор класса, который ожидает значение id. Конструктор использует это значение для заполнения класса из базы данных.

этот процесс определенно должен быть вне конструктора.

  1. конструктор не должен обращаться к базе данных.

  2. задача и причина конструктора -инициализировать данные-члены и установить инвариант класса используя значения, переданные в конструктор.

  3. для всего остального лучше использовать статический метод фабрики или в более сложных случаях отдельной завод или строитель класса.

некоторые направляющие линии конструктора от Microsoft:

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

и

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


В дополнение к" эффективной java "(как упоминалось в другом ответе)," чистый код " также заявляет, что: предпочитают статические заводские методы (с именами, описывающими аргументы) вместо перегруженных конструкторов. Например, не пишите

Complex complex = new Complex(23.0);

но вместо того, чтобы писать

Complex complex = Complex.fromRealNumber(23.0);

книга идет так далеко, чтобы предложить сделать Complex(float) конструктор private, чтобы заставить пользователя вызвать статический заводской метод.


иногда вам нужно проверить / вычислить некоторые значения / условия при создании объекта. И если он может выбросить исключение-constructro очень плохой способ. Поэтому вам нужно сделать что-то вроде этого:

var value = new Instance(1, 2).init()
public function init() {
    try {
        doSome()
    }
    catch (e) {
        soAnotherSome()
    }
}

где все дополнительные вычисления находятся в init(). Но только вы как разработчик действительно знаете об этом init (). И конечно, через несколько месяцев ты просто забываешь об этом. Но если у вас есть фабрика-просто сделайте все, что вам нужно в одном методе со скрытием этого init() от прямого вызова-так нет проблемы. При таком подходе нет проблем с падением на создание и утечкой памяти.

кто-то рассказал вам о кэшировании. Это хорошо. Но вы также должны помнить о Flyweight pattern, который приятно использовать с заводским способом.