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


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

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

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

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

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

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

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

10 144

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-для простых классов (value objects и др.) конструктор просто отлично (вы не делаете хотите перепроектировать свое приложение), но для сложных иерархий классов предпочтительным способом является заводской метод.

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

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

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

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

статические методы фабрики недостатки:

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

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

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

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

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

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

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

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

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

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

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

скажем, у меня есть конструктор для класса, который ожидает значение 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, которую приятно использовать с заводским способом.