Функція конструктора проти заводських функцій


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;
}

Звичайно, ви можете зробити фабричні функції набагато складнішими, ніж простий приклад.

Однією з переваг заводських функцій є те, коли об'єкт, що повертається, може бути декількох різних типів залежно від певного параметра.


14
"(EDIT: і це може бути проблемою, оскільки без нового функція все одно буде працювати, але не так, як очікувалося)." Це проблема лише в тому випадку, якщо ви намагаєтеся викликати заводську функцію "new" або намагаєтесь використовувати ключове слово "this" для присвоєння екземпляру. В іншому випадку ви просто створите новий, довільний об'єкт і повернете його. Немає проблем, просто інший, більш гнучкий спосіб робити речі з меншою кількістю котла та без просочування детальних даних про API в API.
Ерік Елліотт

6
Я хотів би зазначити, що приклади для обох випадків (функція конструктора та заводська функція) повинні бути узгодженими. Приклад фабричної функції не включає someMethodоб’єкти, повернені фабрикою, і тут він стає трохи туманним. Всередині фабричної функції, якщо це просто зробити var obj = { ... , someMethod: function() {}, ... }, це призвело б до того, що кожен повернутий об'єкт тримає іншу копію, someMethodяку ми можемо не хотіти. Саме тут допоможе використання newі prototypeвсередині заводської функції.
Бхарат Хатрі

3
Як ви вже згадували, що деякі люди намагаються використовувати фабричні функції лише тому, що не мають наміру залишати помилки, де люди забувають користуватися newфункцією конструктора; Я подумав, що тут, можливо, потрібно побачити, як замінити конструктори на приклад заводських функцій, і саме там я вважав необхідною послідовність у прикладах. У будь-якому випадку, відповідь досить інформативна. Це був лише пункт, який я хотів підняти, не те, що я якось тягну за якістю відповіді.
Бхарат Хатрі

4
Для мене найбільшою перевагою заводських функцій є те, що ви отримуєте кращу інкапсуляцію та приховування даних, що може бути корисним у деяких програмах. Якщо немає проблем у тому, щоб зробити власність і методи кожного примірника загальнодоступними та легко модифікованими користувачами, я думаю, що функція Constructor є більш підходящою, якщо ви не любите "нове" ключове слово, як це роблять деякі люди.
Девіус

1
@Federico - заводські методи не повинні повертати просто звичайний об'єкт. Їх можна використовувати newвнутрішньо або використовувати Object.create()для створення об'єкта з певним прототипом.
nnnnnn

110

Переваги використання конструкторів

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

  • this відноситься до нового об’єкта

  • Деяким людям подобається спосіб var myFoo = new Foo();читання.

Недоліки

  • Деталі інстанції просочуються в API виклику (через newвимогу), тому всі абоненти тісно пов'язані з реалізацією конструктора. Якщо вам коли-небудь потрібна додаткова гнучкість на заводі, вам доведеться переробляти всіх абонентів (правда, винятковий випадок, а не правило).

  • Забуття newє такою поширеною помилкою, слід наполегливо розглянути можливість додавання перевірки на панелі котла, щоб переконатися, що конструктор викликається правильно ( if (!(this instanceof Foo)) { return new Foo() }). EDIT: З 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, як очікувалося. Якщо ви зберігаєте прототипи на батьківському об'єкті, може бути чудовим способом динамічно заміняти функціональність та ввімкнути дуже гнучкий поліморфізм для обґрунтування об'єкта.

  • Немає двозначності щодо капіталізації чи ні. Не варто. Інструменти для ворсу будуть скаржитися, і тоді ви будете спокушатися спробувати використовувати new, і тоді ви скасуєте вищеописану вигоду.

  • Деякі люди , як шлях var myFoo = foo();або var myFoo = foo.create();читають.

Недоліки

  • newне веде себе так, як очікувалося (див. вище). Рішення: не використовуйте його.

  • thisне посилається на новий об'єкт (натомість, якщо конструктор викликає позначення крапок або позначення квадратних дужок, наприклад, foo.bar () - thisпосилається на foo- як і будь-який інший метод JavaScript - див. переваги).


2
У якому сенсі ви маєте на увазі, що конструктори змушують абонентів щільно поєднуватися з їх реалізацією? Що стосується аргументів конструктора, їх потрібно буде передати навіть до заводської функції, щоб він міг їх використовувати і викликати відповідний конструктор всередині.
Бхарат Хатрі

4
Щодо порушення відкритого / закритого типу: чи не все стосується введення залежності? Якщо A потрібен B, тим більше A викликає новий B () або A BFactory.create (), вони вводять з'єднання. Якщо ви, з іншого боку, даєте екземпляр B у корені композиції, A не потрібно взагалі нічого знати про те, як B інстанціюється. Я відчуваю, що і конструктори, і заводи мають своє використання; конструктори для простої інстанції, заводи для більш складної інстанції. Але в обох випадках придумати свої залежності розумно.
Стефан Біллієт

1
DI добре підходить для стану введення: конфігурація, об'єкти домену тощо. Це надмірно для всього іншого.
Ерік Елліотт

1
Суєта полягає в тому, що вимога newпорушує принцип відкритого / закритого типу. Дивіться medium.com/javascript-scene/… для набагато більшого обговорення, ніж дозволяють ці коментарі.
Ерік Елліотт

3
Оскільки будь-яка функція може повернути новий об’єкт у JavaScript, і досить багато з них робить це без newключового слова, я не вірю, що newключове слово насправді не забезпечує додаткової читабельності. IMO, здається, нерозумно стрибати через обручі, щоб дозволити абонентам набирати більше.
Ерік Елліотт

39

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


5

Приклад функції конструктора

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і повертають істину, використовуючи instanceOfоператор функції конструктора.

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

Функції конструктора можна розширити за допомогою extendsключового слова.

Функції конструктора не можуть повернутися nullяк значення помилки. Оскільки це не тип об'єктних даних, він ігнорується new.

Приклад функцій заводу

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

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

Тут заводська функція викликається без new. Функція повністю відповідає за пряме або опосередковане використання, якщо її аргументи та тип об'єкта, який вона повертає. У цьому прикладі він повертає простий [Object object] з деякими властивостями, встановленими з аргументів.

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

Легко приховує складності реалізації об'єкта створення від абонента. Це особливо корисно для функцій нативного коду в браузері.

Фабрична функція не завжди повинна повертати об'єкти одного типу і навіть може повертатися nullяк індикатор помилок.

У простих випадках фабричні функції можуть бути простими за структурою та значенням.

Об'єкти, що повертаються, як правило, не успадковують prototypeвластивості заводської функції , а повертаються falseз instanceOf factoryFunction.

Фабричну функцію не можна безпечно розширити за допомогою extendsключового слова, оскільки розширені об'єкти успадкують від prototypeвластивості заводських функцій замість prototypeвластивості конструктора, використовуваного заводською функцією.


1
Це пізня відповідь, розміщена у відповідь на це запитання з тієї ж теми,
traktor53

не просто "null", але "new" також буде ігнорувати будь-який попередній тип даних, повернутий функцією кондуктора.
Вішал

2

Заводи "завжди" кращі. Тоді при використанні об'єктно-орієнтованих мов

  1. вирішити договір (методи та що вони будуть робити)
  2. Створіть інтерфейси, які розкривають ці методи (у JavaScript у вас немає інтерфейсів, тому вам потрібно придумати певний спосіб перевірки реалізації)
  3. Створіть фабрику, яка повертає реалізацію кожного необхідного інтерфейсу.

Реалізації (фактичні об'єкти, створені за допомогою нового) не піддаються фабричному користувачеві / споживачеві. Це означає, що розробник фабрики може розширювати та створювати нові реалізації, поки він / вона не порушить договір ... і це дозволяє заводському споживачеві просто отримати вигоду від нового API, не змінюючи їх код ... якщо вони використовували нову, а "нова" реалізація приходить разом, вони повинні перейти і змінити кожен рядок, який використовує "новий", щоб використовувати "нову" реалізацію ... з фабрикою їх код не змінюється ...

Фабрики - кращі за все інше - весняні рамки повністю побудовані навколо цієї ідеї.


Як фабрика вирішує цю проблему необхідності зміни кожного рядка?
Кодове ім’я Джек

0

Фабрики - це шар абстракції, і, як і всі абстракції, вони мають складну вартість. Зустрічаючи API на заводі, з'ясування того, що є фабрикою для даного API, може бути складним для споживача API. Відкриття конструкцій тривіально.

Вирішуючи питання між автотранспортом і фабриками, ви повинні вирішити, чи складність виправдана вигодою.

Варто зауважити, що конструктори Javascript можуть бути довільними фабриками, повертаючи щось інше, ніж це або невизначене. Таким чином, у js ви можете отримати найкраще з обох світів - відкритий API і об'єднання об'єктів / кешування.


5
У JavaScript вартість використання конструкторів вище, ніж вартість використання заводів, оскільки будь-яка функція в JS може повернути новий об’єкт. Конструктори додають складності: Потрібноnew , Змінюючи поведінку this, Змінюючи повернене значення, Підключення прототипу ref, Увімкнення instanceof(що лежить і не повинно використовуватися для цієї мети). Нібито, все це - "риси". На практиці вони шкодять якості вашого коду.
Ерік Елліотт

0

Для відмінностей Ерік Еліотт дуже добре уточнив,

Але для другого питання:

Коли використовувати один замість іншого?

Якщо ви надходите з об'єктно-орієнтованого фону, функція Constructor виглядає для вас більш природно. таким чином, ви не повинні забути використовувати newключове слово.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.