Функция конструктора против функций фабрики

150

Может кто-нибудь прояснить разницу между функцией конструктора и фабричной функцией в Javascript.

Когда использовать один вместо другого?

Синан
источник

Ответы:

149

Основное отличие состоит в том, что функция конструктора используется с newключевым словом (что заставляет JavaScript автоматически создавать новый объект, устанавливать thisв функции этот объект и возвращать объект):

var objFromConstructor = new ConstructorFunction();

Фабричная функция вызывается как «обычная» функция:

var objFromFactory = factoryFunction();

Но для того, чтобы он считался «фабрикой», ему нужно было бы вернуть новый экземпляр некоторого объекта: вы бы не назвали его «фабричной» функцией, если бы она просто возвращала логическое значение или что-то в этом роде. Это не происходит автоматически, как с new, но это допускает большую гибкость в некоторых случаях.

В действительно простом примере функции, упомянутые выше, могут выглядеть примерно так:

function ConstructorFunction() {
   this.someProp1 = "1";
   this.someProp2 = "2";
}
ConstructorFunction.prototype.someMethod = function() { /* whatever */ };

function factoryFunction() {
   var obj = {
      someProp1 : "1",
      someProp2 : "2",
      someMethod: function() { /* whatever */ }
   };
   // other code to manipulate obj in some way here
   return obj;
}

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

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

NNNNNN
источник
14
«(РЕДАКТИРОВАТЬ: и это может быть проблемой, потому что без новой функции все равно будет работать, но не так, как ожидалось).» Это проблема, только если вы пытаетесь вызвать фабричную функцию с помощью «new» или вы используете ключевое слово «this» для назначения экземпляру. В противном случае вы просто создаете новый произвольный объект и возвращаете его. Никаких проблем, просто другой, более гибкий способ сделать что-то, с меньшим количеством шаблонов и без утечки деталей инстанцирования в API.
Эрик Эллиотт
6
Я хотел указать, что примеры для обоих случаев (функция конструктора против фабричной функции) должны быть согласованы. Пример для фабричной функции не включает someMethodобъекты, возвращаемые фабрикой, и вот где она становится немного туманной. Внутри фабричной функции, если это просто так var obj = { ... , someMethod: function() {}, ... }, это приведет к тому, что каждый возвращаемый объект будет содержать другую копию someMethod, чего мы можем не хотеть. Вот где использование newи prototypeвнутри фабричной функции поможет.
Бхарат Хатри
3
Как вы уже упоминали, некоторые люди пытаются использовать фабричные функции только потому, что не собираются оставлять ошибки там, где люди забывают использовать newфункцию конструктора; Я подумал, что именно здесь, возможно, понадобится посмотреть, как заменить конструкторы примером фабричных функций, и именно здесь я подумал, что в примерах требуется согласованность. В любом случае, ответ достаточно информативен. Это была просто точка, которую я хотел поднять, а не то, что я каким-то образом теряю качество ответа.
Бхарат Хатри
4
Для меня наибольшим преимуществом функций Factory является то, что вы получаете лучшую инкапсуляцию и сокрытие данных, что может быть полезно в некоторых приложениях. Если нет проблем в том, чтобы сделать каждое свойство и методы экземпляра общедоступными и легко изменяемыми пользователями, тогда я думаю, что функция Constructor более уместна, если вам не нравится ключевое слово «new», как это делают некоторые люди.
Деви
1
@Federico - фабричные методы не должны возвращать только простой объект. Их можно использовать newвнутренне или использовать Object.create()для создания объекта с конкретным прототипом.
nnnnnn
110

Преимущества использования конструкторов

  • Большинство книг учат вас использовать конструкторы и new

  • this относится к новому объекту

  • Некоторым людям нравится, как var myFoo = new Foo();читает.

Недостатки

  • Детали создания экземпляров просочились в вызывающий API (через newтребование), поэтому все вызывающие объекты тесно связаны с реализацией конструктора. Если вам когда-нибудь понадобится дополнительная гибкость фабрики, вам придется рефакторинг всех вызывающих абонентов (правда, исключительный случай, а не правило).

  • Забыть new- это такая распространенная ошибка, что вам настоятельно рекомендуется добавить шаблонную проверку, чтобы убедиться, что конструктор вызывается правильно ( if (!(this instanceof Foo)) { return new Foo() }). РЕДАКТИРОВАТЬ: Начиная с ES6 (ES2015) вы не можете забыть newс classконструктором, иначе конструктор выдаст ошибку.

  • Если вы делаете instanceofпроверку, это оставляет двусмысленность относительно того new, требуется или нет . На мой взгляд, так не должно быть. Вы фактически замкнули короткое замыкание new, что означает, что вы можете устранить недостаток # 1. Но тогда у вас есть только фабричная функция во всем, кроме имени , с дополнительным шаблоном, заглавной буквой и менее гибким thisконтекстом.

Конструкторы нарушают открытый / закрытый принцип

Но моя главная проблема в том, что это нарушает принцип открытого / закрытого. Вы начинаете экспортировать конструктор, пользователи начинают использовать конструктор, а затем понимаете, что вместо этого вам нужна гибкость фабрики (например, для переключения реализации на использование пулов объектов или для создания экземпляров в разных контекстах выполнения или для иметь большую гибкость наследования, используя прототип OO).

Вы застряли, хотя. Вы не можете внести изменения, не нарушив весь код, который вызывает ваш конструктор new. Например, нельзя использовать пулы объектов для повышения производительности.

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

Преимущества использования заводов

  • Меньше кода - шаблон не требуется.

  • Вы можете вернуть любой произвольный объект и использовать любой произвольный прототип, что дает вам больше гибкости для создания различных типов объектов, которые реализуют один и тот же API. Например, медиаплеер, который может создавать экземпляры как HTML5, так и флэш-плееров, или библиотека событий, которая может генерировать события DOM или события веб-сокетов. Фабрики могут также создавать объекты в разных контекстах выполнения, использовать преимущества пулов объектов и предоставлять более гибкие модели наследования прототипов.

  • Вам никогда не понадобится переходить с фабрики на конструктор, поэтому рефакторинг никогда не будет проблемой.

  • Нет двусмысленности в использовании new. Не. (Это заставит thisсебя плохо себя вести, см. Следующий пункт).

  • thisведет себя как обычно - так что вы можете использовать его для доступа к родительскому объекту (например, внутри player.create(), thisссылается player, как и любой другой вызов метода), callа applyтакже переназначать this, как и ожидалось. Если вы храните прототипы в родительском объекте, это может быть отличным способом динамически поменять функциональность и обеспечить очень гибкий полиморфизм для реализации вашего объекта.

  • Никакой двусмысленности в том, стоит ли зарабатывать. Не. Инструменты Lint будут жаловаться, и тогда у вас возникнет соблазн попробовать использовать его new, а затем вы отмените описанное выше преимущество.

  • Некоторым людям нравится, как читает var myFoo = foo();или var myFoo = foo.create();читает.

Недостатки

  • newне ведет себя, как ожидалось (см. выше). Решение: не используйте его.

  • thisне ссылается на новый объект (вместо этого, если конструктор вызывается с точечной нотацией или с квадратной скобкой, например, foo.bar () - thisссылается foo- как и любой другой метод JavaScript - см. преимущества).

Эрик Эллиотт
источник
2
В каком смысле вы имеете в виду, что конструкторы тесно связывают вызывающие с их реализацией? Что касается аргументов конструктора, их нужно будет передать даже фабричной функции, чтобы они могли использовать их и вызывать соответствующий конструктор внутри.
Бхарат Хатри
4
Что касается нарушения Open / Closed: разве это не инъекция зависимостей? Если A нужен B, то A вызывает new B () или A вызывает BFactory.create (), оба они вводят связь. Если, с другой стороны, вы даете A экземпляр B в корне композиции, A вообще не нужно ничего знать о том, как создается экземпляр B. Я чувствую, что и у конструкторов, и у фабрик есть свое применение; конструкторы предназначены для простого создания экземпляров, фабрики - для более сложных. Но в обоих случаях вводить свои зависимости разумно.
Стефан Биллиет
1
DI подходит для внедрения состояний: конфигурации, доменных объектов и т. Д. Это излишне для всего остального.
Эрик Эллиотт
1
Суета заключается в том, что требование newнарушает принцип открытого / закрытого. См. Medium.com/javascript-scene/… для гораздо большего обсуждения, чем позволяют эти комментарии.
Эрик Эллиотт
3
Поскольку любая функция может возвращать новый объект в JavaScript, и многие из них делают это без newключевого слова, я не верю, что newключевое слово действительно обеспечивает дополнительную читаемость. IMO, кажется глупым перепрыгивать через обручи, чтобы позволить звонящим набирать больше.
Эрик Эллиотт
39

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

Игнасио Васкес-Абрамс
источник
6

Пример функции конструктора

function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Jack");
  • newсоздает прототип объекта User.prototypeи вызывает Userс созданным объектом в качестве thisзначения.

  • new обрабатывает выражение аргумента для своего операнда как необязательное:

         let user = new User;

    приведет newк вызову Userбез аргументов.

  • newвозвращает созданный им объект, если только конструктор не возвращает значение объекта , которое возвращается взамен. Это крайний случай, который по большей части можно игнорировать.

Плюсы и минусы

Объекты, созданные функциями конструктора, наследуют свойства от свойства конструктора prototypeи возвращают значение true, используя instanceOfоператор функции конструктора.

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

Функции конструктора могут быть расширены с помощью extendsключевого слова.

Функции конструктора не могут возвращаться nullкак значение ошибки. Поскольку это не тип данных объекта, он игнорируется new.

Пример фабричной функции

function User(name, age) {
  return {
    name,
    age,
  }
};

let user = User("Tom", 23);

Здесь заводская функция вызывается без new. Функция полностью отвечает за прямое или косвенное использование своих аргументов и типа возвращаемого объекта. В этом примере он возвращает простой [объект объекта] с некоторыми свойствами, установленными из аргументов.

Плюсы и минусы

Легко скрывает сложности реализации создания объекта от вызывающей стороны. Это особенно полезно для функций собственного кода в браузере.

Функция фабрики не всегда должна возвращать объекты одного и того же типа и даже может возвращаться nullкак индикатор ошибки.

В простых случаях фабричные функции могут быть простыми по структуре и значению.

Возвращаемые объекты обычно не наследуются от prototypeсвойства фабричной функции и возвращаются falseиз instanceOf factoryFunction.

Фабричную функцию нельзя безопасно расширять с помощью extendsключевого слова, поскольку расширенные объекты наследуются от prototypeсвойства фабричных функций, а не от prototypeсвойства конструктора, используемого фабричной функцией.

traktor53
источник
1
Это поздний ответ, опубликованный в ответ на этот вопрос на ту же тему,
traktor53
не только «ноль», но и «новый» также будет игнорировать любой примитивный тип данных, возвращаемый функцией-конструктором.
Вишал
2

Фабрики "всегда" лучше. При использовании объектно-ориентированных языков

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

Реализации (фактические объекты, созданные с помощью new) не предоставляются фабричному пользователю / потребителю. Это означает, что разработчик фабрики может расширять и создавать новые реализации до тех пор, пока он / она не нарушает контракт ... и это позволяет потребителю фабрики просто получать выгоду от нового API без необходимости изменять свой код ... если они использовали новую, и приходит «новая» реализация, тогда они должны переходить и менять каждую строку, которая использует «новую», чтобы использовать «новую» реализацию ... с фабрикой их код не меняется ...

Фабрики - лучше, чем все остальное - основа пружины полностью построена вокруг этой идеи.

Колин Сакстон
источник
Как фабрика решает эту проблему необходимости менять каждую строку?
Кодовое имя Джек
0

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

При выборе между ctors и фабриками вы должны решить, оправдывает ли сложность выгода.

Стоит отметить, что конструкторы Javascript могут быть произвольными фабриками, возвращая что-то отличное от this или undefined. Таким образом, в js вы можете получить лучшее из обоих миров - обнаруживаемый API и пул / кеширование объектов.

PeterH
источник
5
В JavaScript стоимость использования конструкторов выше, чем стоимость использования фабрик, потому что любая функция в JS может вернуть новый объект. Конструкторы увеличивают сложность за счет: запроса new, изменения поведения this, изменения возвращаемого значения, подключения прототипа ссылки, включения instanceof(которое лежит и не должно использоваться для этой цели). Якобы, все это "особенности". На практике они ухудшают качество вашего кода.
Эрик Эллиотт
0

Что касается различий, Эрик Эллиот объяснил очень хорошо,

Но для второго вопроса:

Когда использовать один вместо другого?

Если вы исходите из объектно-ориентированного фона, функция Constructor выглядит более естественной для вас. Таким образом, вы не должны забывать использовать newключевое слово.

Мостафа
источник