Конструкторы против фабричных методов [закрыто]


181

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

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

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

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

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

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

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

Ответы:


66

Со страницы 108 « Шаблонов проектирования: элементы многоразового объектно-ориентированного программного обеспечения» Гаммы, Хелма, Джонсона и Влиссида.

Используйте шаблон Factory Method, когда

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

21
Статический фабричный метод отличается от шаблона проектирования GoF - Factory Method Pattern. stackoverflow.com/questions/929021/…
Шри Рама

Пожалуйста, не сравнивайте с шаблоном фабричного метода шаблонов дизайна GoF.
Шри Рама

137
это мне ничего не объясняет
Sushant

@ Sushant, почему это так?
PaulD

2
Этот ответ не отвечает на вопрос, а просто передает информацию, чтобы прочитать, чтобы понять / объяснить концепцию ... Это больше комментарий.
Crt

202

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

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.

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

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


2
Даже если вы думаете, что это простой класс, есть шанс, что кто-то должен расширить ваш простой класс, поэтому метод фабрики все же лучше. Например, вы можете начать с ElementarySchool, но позже кто-то (включая вас) может расширить его с помощью PrivateElementarySchool и PublicElementarySchool.
Джек,

10
это должен быть принятый ответ
am05mhz

2
@ Дэвид, хороший ответ, но можете ли вы расширить пример, где каждая реализация интерфейса может требовать различных параметров для построения. Вот глупый пример: IFood sandwich = new Sandwich(Cheese chz, Meat meat);и IFood soup = new Soup(Broth broth, Vegetable veg);как фабрика и / или строитель могут помочь здесь?
Брайан

1
Я только что прочитал три других объяснения о цели использования фабрики, и это то, что, наконец, показало мне «щелчок». Спасибо!
Даниэль Пейрано

Почему этот ответ не принят?
Томас

74

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

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

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

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

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

4
Это кажется мне довольно серьезной ошибкой в ​​Java, а затем общей проблемой OOD. Существует множество ОО-языков, которые даже не имеют конструкторов, но подклассы работают просто отлично.
Йорг Миттаг

1
@cherouvim, почему в основном код пишется с использованием конструкторов, если( factory methods are better than Constructors. ( Item-1 ) ) Effective java
Асиф Муштак

Хорошие моменты. Это специфично для Java. Можно привести пример для языковой функции, которая отличает фабричные методы от других статических методов.
OCDev

30

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

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


17
Таким образом, (стиль TDD) вы начнете с конструкторов как самого простого способа выполнить работу. А затем рефакторинг на фабрики, как только вы начнете получать запахи кода (например, повторяющаяся условная логика, определяющая, какой конструктор вызывать)?
AndyM

1
Очень важный момент. Пользовательское исследование, сравнивающее фабрики и конструкторы, обнаружило весьма существенные результаты, свидетельствующие о том, что фабрики наносят ущерб удобству использования API: «пользователям требуется значительно больше времени (p = 0,005) для создания объекта с фабрикой, чем с конструктором» [Шаблон фабрики в дизайне API Оценка юзабилити .
mdeff

12

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

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

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


11

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

«Обратите внимание, что метод статической фабрики не совпадает с шаблоном фабричного метода из шаблонов проектирования [Gamma95, стр. 107]. Метод статической фабрики, описанный в этом пункте, не имеет прямого эквивалента в шаблонах проектирования».


10

В дополнение к «эффективной Java» (как упоминалось в другом ответе), другая классическая книга также предлагает:

Предпочитайте статические фабричные методы (с именами, которые описывают аргументы) перегруженным конструкторам.

Например. не пиши

Complex complex = new Complex(23.0);

но вместо этого напиши

Complex complex = Complex.fromRealNumber(23.0);

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


2
Чтение этой части книги привело меня сюда
Purple Haze

1
@Bayrem: я тоже, я недавно перечитывал это и думал, что должен добавить это к ответам.
blue_note

1
На соответствующую записку, может оказаться полезным некоторые соглашения об именовании выработанного java.time рамки относительно наименования from…, to…, parse…, with…, и так далее. Имейте в виду, что классы java.time созданы для того, чтобы быть неизменными, но некоторые из этих соглашений об именах могут быть полезны и для изменяемых классов.
Базилик Бурк

7

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

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

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


5

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

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

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

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

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

Некоторые руководящие указания конструктора от Microsoft :

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

И

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


2

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

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.