Funzione costruttore vs Funzioni fabbrica

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

Quando usarne uno invece dell’altro?

La differenza fondamentale è che una funzione di costruzione viene utilizzata con la new parola chiave (che fa sì che JavaScript crei automaticamente un nuovo object, lo imposti all’interno della funzione su quell’object e restituisca l’object):

 var objFromConstructor = new ConstructorFunction(); 

Una funzione di fabbrica è chiamata come una funzione “normale”:

 var objFromFactory = factoryFunction(); 

Ma per essere considerata una “fabbrica”, sarebbe necessario restituire una nuova istanza di qualche object: non la chiamerebbero una funzione “fabbrica” ​​se restituisse un valore booleano o qualcosa del genere. Questo non avviene automaticamente come con il new , ma consente una maggiore flessibilità per alcuni casi.

In un esempio molto semplice le funzioni di cui sopra 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; } 

Naturalmente è ansible rendere le funzioni di fabbrica molto più complicate di quel semplice esempio.

Alcune persone preferiscono utilizzare le funzioni di fabbrica per tutto solo perché non gli piace dover ricordare di usare new (EDIT: e questo può essere un problema perché senza new la funzione verrà comunque eseguita ma non come previsto). Non lo vedo come un vantaggio: il new è una parte fondamentale del linguaggio, quindi evitarlo deliberatamente è un po ‘arbitrario – potrebbe anche evitare altre parole chiave come else .

Un vantaggio delle funzioni di fabbrica è quando l’object da restituire potrebbe essere di diversi tipi a seconda di alcuni parametri.

Benefici dell’utilizzo di costruttori

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

  • this riferisce al nuovo object

  • Ad alcune persone piace il modo var myFoo = new Foo(); legge.

svantaggi

  • I dettagli dell’istanziazione vengono divulgati nell’API di chiamata (tramite il new requisito), quindi tutti i chiamanti sono strettamente associati all’implementazione del costruttore. Se hai bisogno della flessibilità aggiuntiva della fabbrica, dovrai rifattorizzare tutti i chiamanti (ammettiamolo il caso eccezionale, piuttosto che la regola).

  • Dimenticare il new è un bug così comune, dovresti prendere in seria considerazione l’idea di aggiungere un controllo boilerplate per assicurarti che il costruttore sia chiamato correttamente ( if (!(this instanceof Foo)) { return new Foo() } ). EDIT: Poiché ES6 (ES2015) non si può dimenticare di new con un costruttore di class , o il costruttore genera un errore.

  • Se esegui il controllo instanceof , lascia ambiguità se sia necessario o meno new . Secondo me, non dovrebbe essere. Hai effettivamente cortocircuitato il new requisito, il che significa che puoi cancellare l’inconveniente n. 1. Ma in questo caso hai appena una funzione di fabbrica in tutto, ma solo un nome , con una copia aggiuntiva, una lettera maiuscola, e meno flessibile this contesto.

I costruttori infrangono il principio aperto / chiuso

Ma la mia preoccupazione principale è che viola il principio aperto / chiuso. Si avvia l’esportazione di un costruttore, gli utenti iniziano a utilizzare il costruttore, quindi in fondo ci si accorge della necessità della flessibilità di un factory, (ad esempio, per passare all’implementazione per utilizzare i pool di oggetti o per creare un’istanza tra i contesti di esecuzione o per avere più flessibilità di ereditarietà utilizzando OO prototipo).

Sei bloccato, però. Non puoi apportare la modifica senza rompere tutto il codice che chiama il tuo costruttore con new . Ad esempio, non è ansible passare all’utilizzo di pool di oggetti per ottenere miglioramenti delle prestazioni.

Inoltre, l’utilizzo di costruttori fornisce instanceof ingannevole di ciò che non funziona nei contesti di esecuzione e non funziona se il prototipo del costruttore viene sostituito. Fallirà anche se inizi a restituire this dal tuo costruttore, e poi passa all’esportazione di un object arbitrario, che dovresti fare per abilitare il comportamento di fabbrica nel tuo costruttore.

Benefici dell’utilizzo di fabbriche

  • Meno codice – non è necessaria alcuna piastra di riscaldamento.

  • Puoi restituire qualsiasi object arbitrario e utilizzare qualsiasi prototipo arbitrario, offrendoti maggiore flessibilità per creare vari tipi di oggetti che implementano la stessa API. Ad esempio, un lettore multimediale in grado di creare istanze di entrambi i lettori HTML5 e Flash o una libreria di eventi che può emettere eventi DOM o eventi di socket web. Le fabbriche possono anche creare un’istanza di oggetti tra i contesti di esecuzione, sfruttare i pool di oggetti e consentire modelli di eredità prototipo più flessibili.

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

  • Nessuna ambiguità sull’uso di new . Non farlo. (Farà this comportamento male, vedi il prossimo punto).

  • this comporta come normalmente – quindi puoi usarlo per accedere all’object padre (ad esempio, all’interno di player.create() , this riferisce al player , proprio come qualsiasi altro richiamo del metodo: call e apply anche riassegna this , come previsto Se si memorizzano prototipi sull’object padre, questo può essere un ottimo modo per scambiare dynamicmente funzionalità e abilitare il polimorfismo molto flessibile per l’istanziazione dell’object.

  • Nessuna ambiguità sull’opportunità o meno di capitalizzare. Non farlo. Gli strumenti di lanugine si lamenteranno, e quindi sarete tentati di provare a usare new , e quindi annullerete i benefici descritti sopra.

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

svantaggi

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

  • this non si riferisce al nuovo object (invece, se il costruttore è invocato con notazione a punti o notazione a parentesi quadre, ad esempio foo.bar () – this riferisce a foo – proprio come ogni altro metodo JavaScript – vedere i vantaggi).

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

Le fabbriche sono “sempre” migliori. Quando si usano lingue orientate agli oggetti allora

  1. decidere il contratto (i metodi e cosa faranno)
  2. Crea interfacce che espongono tali metodi (in javascript non hai interfacce quindi devi trovare un modo per controllare l’implementazione)
  3. Creare uno stabilimento che restituisca un’implementazione di ciascuna interfaccia richiesta.

Le implementazioni (gli oggetti reali creati con nuovi) non sono esposte all’utente / consumatore di fabbrica. Ciò significa che lo sviluppatore di fabbrica può espandersi e creare nuove implementazioni finché non rompe il contratto … e consente al consumatore di fabbrica di trarre vantaggio dalla nuova API senza dover modificare il codice … se hanno usato una nuova e una “nuova” implementazione, devono andare e cambiare ogni linea che usa “nuova” per usare la “nuova” implementazione … con la fabbrica il loro codice non cambia …

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

Le fabbriche sono uno strato di astrazione e, come tutte le astrazioni, hanno una complessità in termini di complessità. Quando si incontra un’API stabilita in fabbrica, capire quale sia la fabbrica per una data API può essere difficile per il consumatore dell’API. Con i costruttori la rilevabilità è banale.

Quando si decide tra i medici e le 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 / memorizzazione nella cache.