Funkcja konstruktora a funkcje fabryczne

150

Czy ktoś może wyjaśnić różnicę między funkcją konstruktora a funkcją fabryczną w JavaScript.

Kiedy używać jednego zamiast drugiego?

Sinan
źródło

Odpowiedzi:

149

Podstawowa różnica polega na tym, że funkcja konstruktora jest używana ze newsłowem kluczowym (co powoduje, że JavaScript automatycznie tworzy nowy obiekt, ustawia thisw funkcji na ten obiekt i zwraca obiekt):

var objFromConstructor = new ConstructorFunction();

Funkcja fabryczna nazywana jest „zwykłą” funkcją:

var objFromFactory = factoryFunction();

Ale aby można było uznać ją za „fabrykę”, musiałby zwrócić nową instancję jakiegoś obiektu: nie nazwałbyś jej funkcją „fabryczną”, gdyby po prostu zwracała wartość logiczną lub coś w tym rodzaju. Nie dzieje się to automatycznie, jak w przypadku new, ale w niektórych przypadkach pozwala na większą elastyczność.

W naprawdę prostym przykładzie funkcje wymienione powyżej mogą wyglądać mniej więcej tak:

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

Oczywiście możesz znacznie bardziej skomplikować funkcje fabryczne niż ten prosty przykład.

Jedną z zalet funkcji fabrycznych jest sytuacja, gdy zwracany obiekt może mieć kilka różnych typów w zależności od niektórych parametrów.

nnnnnn
źródło
14
„(EDYTUJ: i może to być problem, ponieważ bez nowych funkcji nadal będzie działać, ale nie zgodnie z oczekiwaniami)”. Jest to problem tylko wtedy, gdy próbujesz wywołać funkcję fabryczną z „nowym” lub próbujesz użyć słowa kluczowego „this”, aby przypisać do instancji. W przeciwnym razie po prostu utworzysz nowy, dowolny obiekt i zwrócisz go. Żadnych problemów, po prostu inny, bardziej elastyczny sposób robienia rzeczy, mniej szablonowy i bez wycieku szczegółów instancji do API.
Eric Elliott,
6
Chciałem zaznaczyć, że przykłady dla obu przypadków (funkcja konstruktora vs funkcja fabryczna) powinny być spójne. Przykład funkcji fabryki nie obejmuje someMethodobiektów zwróconych przez fabrykę i wtedy robi się trochę mglisty. Wewnątrz funkcji fabrycznej, jeśli tylko to zrobi var obj = { ... , someMethod: function() {}, ... }, doprowadzi to do tego, że każdy zwracany obiekt będzie zawierał inną kopię, someMethodktórej moglibyśmy nie chcieć. W tym miejscu pomogłoby użycie newi prototypewewnątrz funkcji fabrycznej.
Bharat Khatri
3
Jak już wspomniałeś, niektórzy ludzie próbują używać funkcji fabrycznych tylko dlatego, że nie zamierzają zostawiać błędów, w których ludzie zapominają użyć newfunkcji konstruktora; Pomyślałem, że w tym miejscu może zaistnieć potrzeba zastąpienia konstruktorów przykładem funkcji fabrycznych i pomyślałem, że wymagana jest spójność przykładów. W każdym razie odpowiedź jest wystarczająco pouczająca. To była tylko kwestia, którą chciałem poruszyć, a nie to, że w jakikolwiek sposób obniżam jakość odpowiedzi.
Bharat Khatri
4
Dla mnie największą zaletą funkcji Factory jest lepsza enkapsulacja i ukrywanie danych, co może być przydatne w niektórych aplikacjach. Jeśli nie ma problemu z upublicznieniem właściwości i metod każdej instancji i ich łatwą modyfikacją przez użytkowników, to wydaje mi się, że funkcja Konstruktor jest bardziej odpowiednia, chyba że nie lubisz słowa kluczowego „new”, jak to robią niektórzy.
devius
1
@Federico - metody fabryczne nie muszą zwracać tylko zwykłego obiektu. Mogą używać newwewnętrznie lub używać Object.create()do tworzenia obiektu z określonym prototypem.
nnnnnn
110

Korzyści z używania konstruktorów

  • Większość książek uczy, jak używać konstruktorów i new

  • this odnosi się do nowego obiektu

  • Niektórzy ludzie lubią sposób var myFoo = new Foo();czytać.

Wady

  • Szczegóły tworzenia wystąpień wyciekają do wywołującego interfejsu API (poprzez newwymaganie), więc wszystkie wywołujące są ściśle powiązane z implementacją konstruktora. Jeśli kiedykolwiek będziesz potrzebować dodatkowej elastyczności fabryki, będziesz musiał refaktoryzować wszystkich dzwoniących (trzeba przyznać, że jest to wyjątkowy przypadek, a nie reguła).

  • Zapominanie newjest tak częstym błędem, należy zdecydowanie rozważyć dodanie standardowego sprawdzenia, aby upewnić się, że konstruktor jest wywoływany poprawnie ( if (!(this instanceof Foo)) { return new Foo() }). EDYCJA: Od ES6 (ES2015) nie można zapomnieć newo classkonstruktorze, w przeciwnym razie konstruktor zgłosi błąd.

  • Jeśli to zrobisz instanceof, pozostawia to niejasność co do tego, czy newjest to wymagane. Moim zdaniem nie powinno. Skutecznie zwarłeś newwymagania, co oznacza, że ​​możesz usunąć wadę # 1. Ale masz po prostu funkcję fabryki , z wyjątkiem nazwy , z dodatkowym schematem, wielką literą i mniej elastycznym thiskontekstem.

Konstruktorzy łamią zasadę otwartej / zamkniętej

Ale moim głównym zmartwieniem jest to, że narusza zasadę otwartego / zamkniętego. Zaczynasz od eksportowania konstruktora, użytkownicy zaczynają używać konstruktora, a następnie po drodze zdajesz sobie sprawę, że zamiast tego potrzebujesz elastyczności fabryki (na przykład, aby przełączyć implementację na używanie pul obiektów, tworzenie instancji w kontekstach wykonania lub mają większą elastyczność dziedziczenia dzięki prototypowemu obiektowi zewnętrznemu).

Ale utkniesz. Nie możesz dokonać zmiany bez zerwania całego kodu, który wywołuje twój konstruktor new. Na przykład nie można przełączyć się na używanie pul obiektów w celu zwiększenia wydajności.

Ponadto używanie konstruktorów daje oszustwo, instanceofktóre nie działa w różnych kontekstach wykonania i nie działa, jeśli twój prototyp konstruktora zostanie wymieniony. Nie powiedzie się również, jeśli zaczniesz wracać thisz konstruktora, a następnie przełączysz się na eksportowanie dowolnego obiektu, co musisz zrobić, aby włączyć zachowanie fabryczne w konstruktorze.

Korzyści z używania fabryk

  • Mniej kodu - nie jest wymagany szablon.

  • Możesz zwrócić dowolny obiekt i użyć dowolnego prototypu - co daje większą elastyczność przy tworzeniu różnych typów obiektów, które implementują to samo API. Na przykład odtwarzacz multimedialny, który może tworzyć instancje odtwarzaczy HTML5 i Flash, lub bibliotekę zdarzeń, która może emitować zdarzenia DOM lub zdarzenia gniazd sieciowych. Fabryki mogą również tworzyć instancje obiektów w kontekstach wykonywania, wykorzystywać pule obiektów i pozwalać na bardziej elastyczne prototypowe modele dziedziczenia.

  • Nigdy nie musiałbyś przechodzić z fabryki na konstruktora, więc refaktoryzacja nigdy nie będzie problemem.

  • Brak dwuznaczności w używaniu new. Nie. (Spowoduje to thiszłe zachowanie, patrz następny punkt).

  • thiszachowuje się jak zazwyczaj - tak można go użyć do uzyskania dostępu do obiektu nadrzędnego (na przykład wewnątrz player.create(), thisodnosi się do player., tak jak każdy inny wywołania metoda byłaby calli applyrównież Przypisz this, zgodnie z oczekiwaniami Jeśli przechowujesz prototypy na obiekcie nadrzędnym, że. może być świetnym sposobem na dynamiczną wymianę funkcjonalności i włączenie bardzo elastycznego polimorfizmu do tworzenia instancji obiektu.

  • Brak dwuznaczności co do tego, czy kapitalizować. Nie. Narzędzia kłaczków będą narzekać, a wtedy będziesz kuszony, aby spróbować ich użyć new, a wtedy cofniesz korzyści opisane powyżej.

  • Niektórzy ludzie lubią sposób var myFoo = foo();lub var myFoo = foo.create();czyta.

Wady

  • newnie zachowuje się zgodnie z oczekiwaniami (patrz wyżej). Rozwiązanie: nie używaj go.

  • thisnie odwołuje się do nowego obiektu (zamiast tego, jeśli konstruktor jest wywoływany z notacją z kropką lub nawiasem kwadratowym, np. foo.bar () - thisodnosi się foo- jak każda inna metoda JavaScript - zobacz korzyści).

Eric Elliott
źródło
2
W jakim sensie masz na myśli to, że konstruktorzy ściśle łączą wywołania z ich implementacją? Jeśli chodzi o argumenty konstruktora, będą one musiały zostać przekazane nawet do funkcji fabrycznej, aby mogła ich użyć i wywołać odpowiedni konstruktor wewnątrz.
Bharat Khatri
4
Odnośnie naruszenia Open / Closed: czy nie chodzi tu tylko o wstrzykiwanie zależności? Jeśli A potrzebuje B, to czy A wywołuje nowe B () lub A wywołuje BFactory.create (), oba wprowadzają sprzężenie. Z drugiej strony, jeśli dasz A wystąpienie B w katalogu głównym kompozycji, A nie musi w ogóle wiedzieć o sposobie tworzenia wystąpienia B. Czuję, że zarówno konstruktorzy, jak i fabryki mają swoje zastosowania; konstruktory służą do prostego tworzenia instancji, a fabryki do bardziej złożonych instancji. Ale w obu przypadkach wstrzyknięcie swoich zależności jest mądre.
Stefan Billiet
1
DI jest dobre do wstrzykiwania stanu: konfiguracji, obiektów domeny i tak dalej. To przesada w przypadku wszystkiego innego.
Eric Elliott,
1
Problem polega na tym, że wymaganie newnarusza zasadę otwarcia / zamknięcia. Zajrzyj na medium.com/javascript-scene/…, aby uzyskać dużo szerszą dyskusję, niż pozwalają na to te komentarze.
Eric Elliott
3
Ponieważ każda funkcja może zwrócić nowy obiekt w JavaScript, a całkiem sporo z nich robi to bez newsłowa kluczowego, nie sądzę, aby newsłowo kluczowe faktycznie zapewniało jakąkolwiek dodatkową czytelność. IMO, wydaje się głupie przeskakiwanie przez obręcze, aby umożliwić dzwoniącym pisanie więcej.
Eric Elliott
39

Konstruktor zwraca instancję klasy, do której został wywołany. Funkcja fabryczna może zwrócić wszystko. Funkcji fabrycznej można użyć, gdy trzeba zwrócić dowolne wartości lub gdy klasa ma duży proces konfiguracji.

Ignacio Vazquez-Abrams
źródło
5

Przykład funkcji konstruktora

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

let user = new User("Jack");
  • newtworzy prototyp obiektu User.prototypei wywołuje Userutworzony obiekt jako jego thiswartość.

  • new traktuje wyrażenie argumentu dla swojego operandu jako opcjonalne:

         let user = new User;

    spowodowałoby newwywołanie Userbez argumentów.

  • newzwraca utworzony przez siebie obiekt, chyba że konstruktor zwraca wartość obiektu , która jest zamiast tego zwracana. Jest to skrajny przypadek, który w większości można zignorować.

Plusy i minusy

Obiekty utworzone przez funkcje konstruktora dziedziczą właściwości z właściwości konstruktora prototypei zwracają wartość true przy użyciu instanceOfoperatora funkcji konstruktora.

Powyższe zachowania mogą się nie powieść, jeśli dynamicznie zmienisz wartość właściwości konstruktora prototypepo wcześniejszym użyciu konstruktora. Jest to rzadkie i nie można go zmienić, jeśli konstruktor został utworzony przy użyciu classsłowa kluczowego.

Funkcje konstruktora można rozszerzyć za pomocą extendssłowa kluczowego.

Funkcje konstruktora nie mogą zwracać nullwartości błędu. Ponieważ nie jest to typ danych obiektowych, jest ignorowany przez new.

Przykład funkcji Factory

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

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

Tutaj wywoływana jest funkcja fabryczna bez new. Funkcja jest całkowicie odpowiedzialna za bezpośrednie lub pośrednie użycie, jeśli jej argumenty i typ zwracanego obiektu. W tym przykładzie zwraca prosty [obiekt Object] z niektórymi właściwościami ustawionymi na podstawie argumentów.

Plusy i minusy

Z łatwością ukrywa przed wywołującym złożoność implementacji tworzenia obiektów. Jest to szczególnie przydatne w przypadku natywnych funkcji kodu w przeglądarce.

Funkcja fabryczna nie zawsze musi zwracać obiekty tego samego typu, a może nawet zwracać nulljako wskaźnik błędu.

W prostych przypadkach funkcje fabryczne mogą mieć prostą strukturę i znaczenie.

Zwracane obiekty zazwyczaj nie dziedziczą z właściwości funkcji fabryki prototypei wracają falsez instanceOf factoryFunction.

Funkcji fabrycznej nie można bezpiecznie rozszerzyć za pomocą extendssłowa kluczowego, ponieważ rozszerzone obiekty będą dziedziczyć z prototypewłaściwości funkcji fabryki, a nie z prototypewłaściwości konstruktora używanego przez funkcję fabryki.

traktor53
źródło
1
To jest późna odpowiedź wysłana w odpowiedzi na to pytanie na ten sam temat
traktor53
nie tylko „null”, ale „nowy” również zignoruje każdy wstępny typ danych zwracany przez funkcję contructor.
Vishal
2

Fabryki są „zawsze” lepsze. W takim razie podczas używania języków obiektowych

  1. zdecydować o umowie (metodach i co będą robić)
  2. Twórz interfejsy, które ujawniają te metody (w javascript nie masz interfejsów, więc musisz wymyślić jakiś sposób na sprawdzenie implementacji)
  3. Utwórz fabrykę, która zwraca implementację każdego wymaganego interfejsu.

Implementacje (rzeczywiste obiekty utworzone za pomocą nowego) nie są ujawniane fabrycznemu użytkownikowi / konsumentowi. Oznacza to, że deweloper fabryki może rozwijać i tworzyć nowe implementacje, o ile nie zerwie umowy ... i pozwala fabrycznemu konsumentowi po prostu korzystać z nowego interfejsu API bez konieczności zmiany kodu ... jeśli użyli nowej i pojawiła się „nowa” implementacja, muszą przejść i zmienić każdą linię, która używa „nowej”, aby użyć „nowej” implementacji ... w fabryce ich kod się nie zmienia ...

Fabryki - lepsze niż wszystko inne - ramy wiosenne są całkowicie zbudowane wokół tego pomysłu.

Colin Saxton
źródło
Jak fabryka rozwiązuje problem konieczności zmiany każdej linii?
Imię Jack
0

Fabryki są warstwą abstrakcji i jak wszystkie abstrakcje mają koszt w złożoności. W przypadku napotkania fabrycznego interfejsu API ustalenie, jaka jest fabryka dla danego interfejsu API, może stanowić wyzwanie dla konsumenta API. W przypadku konstruktorów wykrywalność jest banalna.

Decydując się między lekarzami a fabrykami, musisz zdecydować, czy złożoność jest uzasadniona korzyścią.

Warto zauważyć, że konstruktory Javascript mogą być dowolnymi fabrykami, zwracając coś innego niż this lub undefined. Tak więc w js możesz uzyskać to, co najlepsze z obu światów - wykrywalny interfejs API i buforowanie / buforowanie obiektów.

PeterH
źródło
5
W JavaScript koszt używania konstruktorów jest wyższy niż koszt używania fabryk, ponieważ każda funkcja w JS może zwrócić nowy obiekt. Konstruktorzy zwiększają złożoność poprzez: wymaganie new, zmianę zachowania this, zmianę wartości zwracanej, podłączenie referencji prototypu, włączanie instanceof(co leży i nie powinno być używane do tego celu). Na pozór wszystko to są „funkcje”. W praktyce szkodzą one jakości kodu.
Eric Elliott
0

Eric Elliott wyjaśnił bardzo dobrze różnice,

Ale jeśli chodzi o drugie pytanie:

Kiedy używać jednego zamiast drugiego?

Jeśli pochodzisz z tła zorientowanego obiektowo, funkcja Konstruktor wygląda dla Ciebie bardziej naturalnie. w ten sposób nie zapomnij użyć newsłowa kluczowego.

Mostafa
źródło