Funzione costruttore e funzioni di fabbrica


150

Qualcuno può chiarire la differenza tra una funzione di costruzione e una funzione di fabbrica in Javascript.

Quando usare l'uno anziché l'altro?

Risposte:


149

La differenza di base è che una funzione di costruzione viene utilizzata con la newparola chiave (che fa sì che JavaScript crei automaticamente un nuovo oggetto, imposti thisla funzione su quell'oggetto e restituisca l'oggetto):

var objFromConstructor = new ConstructorFunction();

Una funzione di fabbrica è chiamata come una funzione "normale":

var objFromFactory = factoryFunction();

Ma per essere considerato una "fabbrica" ​​dovrebbe restituire una nuova istanza di qualche oggetto: non la chiamereste una funzione "fabbrica" ​​se restituisse un valore booleano o qualcosa del genere. Questo non accade automaticamente come connew , ma in alcuni casi consente una maggiore flessibilità.

In un esempio davvero semplice, le funzioni sopra menzionate potrebbero apparire in questo modo:

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

Ovviamente puoi rendere le funzioni di fabbrica molto più complicate di quel semplice esempio.

Un vantaggio per le funzioni di fabbrica è quando l'oggetto da restituire potrebbe essere di diversi tipi a seconda di alcuni parametri.


14
"(EDIT: e questo può essere un problema perché senza nuove la funzione continuerà ma non come previsto)." Questo è solo un problema se si tenta di chiamare una funzione di fabbrica con "nuovo" o si tenta di utilizzare la parola chiave "this" per assegnare all'istanza. Altrimenti, devi semplicemente creare un nuovo oggetto arbitrario e restituirlo. Nessun problema, solo un modo diverso e più flessibile di fare le cose, con meno platee di caldaia e senza perdite di dettagli sull'istanza nell'API.
Eric Elliott,

6
Volevo sottolineare che gli esempi per entrambi i casi (funzione di costruzione e funzione di fabbrica) dovrebbero essere coerenti. L'esempio per la funzione factory non include someMethodgli oggetti restituiti dalla factory, ed è lì che diventa un po 'confuso. All'interno della funzione di fabbrica, se solo lo fa var obj = { ... , someMethod: function() {}, ... }, ciò porterebbe a ogni oggetto restituito contenere una copia diversa di someMethodciò che potremmo non desiderare. Ecco dove sarebbe utile usare newe prototypeall'interno della funzione di fabbrica.
Bharat Khatri

3
Come hai già detto, alcune persone cercano di usare le funzioni di fabbrica solo perché non intendono lasciare bug dove le persone dimenticano di usare newcon la funzione di costruzione; Ho pensato che fosse dove si potrebbe aver bisogno di vedere come sostituire i costruttori con l' esempio delle funzioni di fabbrica ed è qui che ho pensato che fosse necessaria la coerenza negli esempi. Comunque, la risposta è abbastanza istruttiva. Questo era solo un punto che volevo sollevare, non che sto abbassando la qualità della risposta in alcun modo.
Bharat Khatri,

4
Per me il più grande vantaggio delle funzioni di fabbrica è che ottieni un migliore incapsulamento e nascondimento dei dati che può essere utile in alcune applicazioni. Se non ci sono problemi nel rendere ogni proprietà e metodi di ogni istanza pubblici e facilmente modificabili dagli utenti, suppongo che la funzione Costruttore sia più appropriata a meno che non vi piaccia la "nuova" parola chiave come fanno alcune persone.
devius,

1
@Federico - i metodi di fabbrica non devono restituire solo un oggetto semplice. Possono utilizzare newinternamente o utilizzare Object.create()per creare un oggetto con un prototipo specifico.
nnnnnn,

110

Vantaggi dell'utilizzo di costruttori

  • La maggior parte dei libri ti insegnano a usare costruttori e new

  • this si riferisce al nuovo oggetto

  • Ad alcune persone piace var myFoo = new Foo();leggere.

svantaggi

  • I dettagli dell'istanza vengono trapelati nell'API chiamante (tramite il newrequisito), quindi tutti i chiamanti sono strettamente associati all'implementazione del costruttore. Se hai mai bisogno dell'ulteriore flessibilità della fabbrica, dovrai riformattare tutti i chiamanti (è vero il caso eccezionale, piuttosto che la regola).

  • Dimenticare newè un bug così comune, dovresti prendere in seria considerazione l'aggiunta di un controllo plateplate per assicurarti che il costruttore sia chiamato correttamente (if (!(this instanceof Foo)) { return new Foo() } ). EDIT: da ES6 (ES2015) non puoi dimenticare newun classcostruttore, altrimenti il ​​costruttore genererà un errore.

  • Se si esegue il instanceofcontrollo, si lascia l'ambiguità sulla necessità o meno new. Secondo me, non dovrebbe essere. Hai effettivamente cortocircuito ilnew requisito, il che significa che potresti cancellare lo svantaggio n. 1. Ma poi hai appena una funzione di fabbrica in tutto tranne il nome , con ulteriore boilerplate, una lettera maiuscola e un thiscontesto meno flessibile .

I costruttori infrangono il principio aperto / chiuso

Ma la mia preoccupazione principale è che viola il principio aperto / chiuso. Inizi a esportare un costruttore, gli utenti iniziano a utilizzare il costruttore, quindi lungo la strada ti rendi conto che hai bisogno della flessibilità di una fabbrica, invece (ad esempio, per cambiare l'implementazione per utilizzare pool di oggetti o per creare un'istanza in contesti di esecuzione o per avere una maggiore flessibilità ereditaria utilizzando OO prototipo).

Sei bloccato, però. Non è possibile apportare la modifica senza rompere tutto il codice che chiama il costruttore connew . Ad esempio, non è possibile passare all'utilizzo di pool di oggetti per migliorare le prestazioni.

Inoltre, l'uso di costruttori ti dà un inganno instanceofche non funziona in contesti di esecuzione e non funziona se il tuo prototipo di costruttore viene scambiato. Fallirà anche se inizi a tornarethis dal tuo costruttore e poi passi all'esportazione di un oggetto arbitrario, cosa che dovresti fare per abilitare un comportamento di fabbrica nel tuo costruttore.

Vantaggi dell'utilizzo delle fabbriche

  • Meno codice - nessuna caldaia richiesta.

  • Puoi restituire qualsiasi oggetto arbitrario e utilizzare qualsiasi prototipo arbitrario, offrendoti una maggiore flessibilità per creare vari tipi di oggetti che implementano la stessa API. Ad esempio, un lettore multimediale in grado di creare istanze sia di lettori HTML5 che di lettori flash o una libreria di eventi in grado di emettere eventi DOM o eventi di socket Web. Le fabbriche possono anche creare istanze di oggetti in contesti di esecuzione, sfruttare pool di oggetti e consentire modelli di ereditarietà prototipali più flessibili.

  • Non avresti mai bisogno di convertirti da una fabbrica a un costruttore, quindi il refactoring non sarà mai un problema.

  • Nessuna ambiguità sull'uso new. Non farlo. (Farà thiscomportarsi male, vedi punto successivo).

  • thissi comporta normalmente - quindi è possibile utilizzarlo per accedere all'oggetto padre (ad esempio, all'interno player.create(), si thisriferisce player, proprio come farebbe qualsiasi altro metodo di invocazione. callE applyanche riassegnare this, come previsto. Se si memorizzano prototipi sull'oggetto padre, quello può essere un ottimo modo per scambiare dinamicamente funzionalità e abilitare un polimorfismo molto flessibile per l'istanza dell'oggetto.

  • Nessuna ambiguità sull'opportunità o meno di capitalizzare. Non farlo. Gli strumenti Lint si lamenteranno, quindi sarai tentato di provare a utilizzare newe quindi annullerai i vantaggi sopra descritti.

  • Ad alcune persone piace il modo var myFoo = foo();o var myFoo = foo.create();legge.

svantaggi

  • newnon si comporta come previsto (vedi sopra). Soluzione: non usarlo.

  • thisnon fa riferimento al nuovo oggetto (invece, se il costruttore viene invocato con notazione punto o notazione parentesi quadra, ad esempio foo.bar () - si thisriferisce a foo- proprio come ogni altro metodo JavaScript - vedere i vantaggi).


2
In che senso intendi dire che i costruttori rendono i chiamanti strettamente associati alla loro implementazione? Per quanto riguarda gli argomenti del costruttore, questi dovranno essere passati anche alla funzione factory per poterli utilizzare e invocare il costruttore appropriato all'interno.
Bharat Khatri

4
Per quanto riguarda la violazione di Open / Closed: non si tratta solo dell'iniezione di dipendenza? Se A ha bisogno di B, se A chiama il nuovo B () o A chiama BFactory.create (), entrambi introducono l'accoppiamento. Se invece dai ad A un'istanza di B nella radice della composizione, A non ha bisogno di sapere nulla del modo in cui B viene istanziata. Sento che sia i costruttori che le fabbriche hanno i loro usi; i costruttori sono per un'istanza semplice, fabbriche per un'istanza più complessa. Ma in entrambi i casi, iniettare le tue dipendenze è saggio.
Stefan Billiet,

1
DI è buono per l'iniezione dello stato: configurazione, oggetti di dominio e così via. È eccessivo per tutto il resto.
Eric Elliott,

1
Il problema è che la richiesta newviola il principio aperto / chiuso. Vedi medium.com/javascript-scene/… per una discussione molto più ampia di quella consentita da questi commenti.
Eric Elliott,

3
Poiché qualsiasi funzione può restituire un nuovo oggetto in JavaScript e molti di loro lo fanno senza la newparola chiave, non credo che la newparola chiave in realtà fornisca alcuna leggibilità aggiuntiva. IMO, sembra sciocco saltare attraverso i cerchi per consentire ai chiamanti di digitare di più.
Eric Elliott,

39

Un costruttore restituisce un'istanza della classe su cui lo chiami. Una funzione di fabbrica può restituire qualsiasi cosa. Si utilizzerà una funzione di fabbrica quando è necessario restituire valori arbitrari o quando una classe ha un processo di installazione di grandi dimensioni.


6

Un esempio di funzione del costruttore

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

let user = new User("Jack");
  • newcrea un oggetto prototipato User.prototypee chiama Usercon l'oggetto creato come thisvalore.

  • new considera un'espressione argomento per il suo operando come facoltativo:

         let user = new User;

    causerebbe newchiamare Usersenza argomenti.

  • newrestituisce l'oggetto che ha creato, a meno che il costruttore non restituisca un valore oggetto , che viene invece restituito. Questo è un caso limite che per la maggior parte può essere ignorato.

Pro e contro

Gli oggetti creati dalle funzioni del costruttore ereditano le proprietà dalla proprietà del costruttore prototypee restituiscono true utilizzando l' instanceOfoperatore sulla funzione del costruttore.

I comportamenti sopra riportati possono non riuscire se si modifica dinamicamente il valore della prototypeproprietà del costruttore dopo averlo già utilizzato. Ciò è raro e non può essere modificato se il costruttore è stato creato utilizzando la classparola chiave.

Le funzioni del costruttore possono essere estese usando la extendsparola chiave.

Le funzioni del costruttore non possono essere restituite nullcome valore di errore. Poiché non è un tipo di dati oggetto, viene ignorato da new.

Un esempio di funzione di fabbrica

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

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

Qui la funzione di fabbrica viene chiamata senza new. La funzione è interamente responsabile dell'uso diretto o indiretto se restituisce i suoi argomenti e il tipo di oggetto. In questo esempio restituisce un semplice [oggetto oggetto] con alcune proprietà impostate dagli argomenti.

Pro e contro

Nasconde facilmente le complessità di implementazione della creazione di oggetti dal chiamante. Ciò è particolarmente utile per le funzioni di codice nativo in un browser.

La funzione di fabbrica non deve sempre restituire oggetti dello stesso tipo e potrebbe persino tornare nullcome indicatore di errore.

In casi semplici, le funzioni di fabbrica possono essere semplici nella struttura e nel significato.

Gli oggetti restituiti generalmente non ereditano dalla prototypeproprietà della funzione di fabbrica e restituiscono falseda instanceOf factoryFunction.

La funzione factory non può essere estesa in modo sicuro utilizzando la extendsparola chiave perché gli oggetti estesi erediteranno dalla prototypeproprietà delle funzioni di fabbrica anziché dalla prototypeproprietà del costruttore utilizzata dalla funzione di fabbrica.


1
Questa è una risposta tardiva pubblicata in risposta a questa domanda sullo stesso argomento,
traktor53,

non solo "null", ma il "nuovo" ignorerà anche qualsiasi tipo di dato prematuro restituito dalla funzione di controllo.
Vishal

2

Le fabbriche sono "sempre" migliori. Quindi, quando si usano lingue orientate agli oggetti

  1. decidere sul contratto (i metodi e cosa faranno)
  2. Crea interfacce che espongano questi metodi (in javascript non hai interfacce quindi devi trovare un modo per controllare l'implementazione)
  3. Creare una fabbrica che restituisce un'implementazione di ciascuna interfaccia richiesta.

Le implementazioni (gli oggetti reali creati con nuovi) non sono esposte all'utente / consumatore della fabbrica. Ciò significa che lo sviluppatore della fabbrica può espandere e creare nuove implementazioni purché non violi il contratto ... e consente al consumatore della fabbrica di beneficiare della nuova API senza dover modificare il proprio codice ... se hanno usato una nuova e una "nuova" implementazione, allora devono andare e cambiare ogni riga che usa "nuova" per usare la "nuova" implementazione ... con la fabbrica il loro codice non cambia ...

Fabbriche - meglio di ogni altra cosa - la struttura a molla è completamente costruita attorno a questa idea.


In che modo la fabbrica risolve questo problema di dover cambiare ogni riga?
Nome in codice Jack

0

Le fabbriche sono uno strato di astrazione e come tutte le astrazioni hanno un costo in complessità. Quando si incontra un'API basata sulla fabbrica, capire quale sia la fabbrica per una determinata API può essere una sfida per il consumatore dell'API. Con i costruttori la rilevabilità è banale.

Quando si decide tra medici e fabbriche è necessario decidere se la complessità è giustificata dal beneficio.

Vale la pena notare che i costruttori Javascript possono essere fabbriche arbitrarie restituendo qualcosa di diverso da questo o indefinito. Quindi in js puoi ottenere il meglio da entrambi i mondi: API rilevabili e pool di oggetti / cache.


5
In JavaScript, il costo dell'utilizzo dei costruttori è superiore al costo dell'utilizzo delle fabbriche poiché qualsiasi funzione in JS può restituire un nuovo oggetto. I costruttori aggiungono complessità per: Richiesta new, Modifica del comportamento di this, Modifica del valore di ritorno, Connessione di un riferimento prototipo, Abilitazione instanceof(che si trova e non deve essere utilizzata per questo scopo). Apparentemente, tutte queste sono "caratteristiche". In pratica, danneggiano la qualità del tuo codice.
Eric Elliott,

0

Per le differenze, Eric Elliott ha chiarito molto bene,

Ma per la seconda domanda:

Quando usare l'uno anziché l'altro?

Se vieni dallo sfondo orientato agli oggetti, la funzione Costruttore ti sembra più naturale. in questo modo non dovresti dimenticare di usare la newparola chiave.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.