Konstruktorfunktion vs Fabrikfunktionen

150

Kann jemand den Unterschied zwischen einer Konstruktorfunktion und einer Factory-Funktion in Javascript klären.

Wann sollte man eins anstelle des anderen verwenden?

Sinan
quelle

Antworten:

149

Der grundlegende Unterschied besteht darin, dass eine Konstruktorfunktion mit dem newSchlüsselwort verwendet wird (wodurch JavaScript automatisch ein neues Objekt erstellt, thisinnerhalb der Funktion auf dieses Objekt festgelegt und das Objekt zurückgegeben wird):

var objFromConstructor = new ConstructorFunction();

Eine Factory-Funktion wird wie eine "reguläre" Funktion aufgerufen:

var objFromFactory = factoryFunction();

Damit es jedoch als "Fabrik" betrachtet werden kann, muss eine neue Instanz eines Objekts zurückgegeben werden: Sie würden es nicht als "Fabrik" -Funktion bezeichnen, wenn es nur einen Booleschen Wert oder etwas zurückgibt. Dies geschieht nicht automatisch wie bei new, ermöglicht jedoch in einigen Fällen mehr Flexibilität.

In einem wirklich einfachen Beispiel könnten die oben genannten Funktionen ungefähr so ​​aussehen:

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

Natürlich können Sie Factory-Funktionen viel komplizierter machen als dieses einfache Beispiel.

Ein Vorteil von Factory-Funktionen besteht darin, dass das zurückzugebende Objekt je nach Parameter von verschiedenen Typen sein kann.

nnnnnn
quelle
14
"(BEARBEITEN: und dies kann ein Problem sein, da die Funktion ohne Neu weiterhin ausgeführt wird, jedoch nicht wie erwartet)." Dies ist nur dann ein Problem, wenn Sie versuchen, eine Factory-Funktion mit "new" aufzurufen, oder wenn Sie versuchen, der Instanz das Schlüsselwort "this" zuzuweisen. Andernfalls erstellen Sie einfach ein neues, beliebiges Objekt und geben es zurück. Keine Probleme, nur eine andere, flexiblere Art, Dinge zu erledigen, mit weniger Boilerplate und ohne Instanziierungsdetails in die API zu verlieren.
Eric Elliott
6
Ich wollte darauf hinweisen, dass die Beispiele für beide Fälle (Konstruktorfunktion vs. Fabrikfunktion) konsistent sein sollten. Das Beispiel für die Factory-Funktion enthält keine someMethodObjekte, die von der Factory zurückgegeben wurden, und dort wird es etwas neblig. Wenn dies innerhalb der Factory-Funktion nur der Fall var obj = { ... , someMethod: function() {}, ... }ist, würde dies dazu führen, dass jedes zurückgegebene Objekt eine andere Kopie enthält, von someMethodder wir möglicherweise nicht möchten. Hier würde die Verwendung newund prototypeFunktion innerhalb der Fabrik helfen.
Bharat Khatri
3
Wie Sie bereits erwähnt haben, versuchen einige Leute, Factory-Funktionen zu verwenden, nur weil sie nicht beabsichtigen, Fehler dort zu hinterlassen, wo die Leute vergessen, sie newmit der Konstruktorfunktion zu verwenden. Ich dachte, hier muss man vielleicht sehen, wie Konstruktoren durch Factory-Funktionen ersetzt werden, und dort dachte ich, dass die Konsistenz in den Beispielen erforderlich ist. Auf jeden Fall ist die Antwort informativ genug. Dies war nur ein Punkt, den ich ansprechen wollte, nicht dass ich die Qualität der Antwort in irgendeiner Weise herabsetze.
Bharat Khatri
4
Für mich besteht der größte Vorteil der Factory-Funktionen darin, dass Sie eine bessere Kapselung und ein besseres Ausblenden von Daten erhalten, was in einigen Anwendungen nützlich sein kann. Wenn es kein Problem gibt, jede Instanzeigenschaft und jede Instanzmethode öffentlich zu machen und für Benutzer leicht zu ändern, ist die Konstruktorfunktion meiner Meinung nach besser geeignet, es sei denn, Sie mögen das "neue" Schlüsselwort nicht wie manche Leute.
Devius
1
@Federico - Factory-Methoden müssen nicht nur ein einfaches Objekt zurückgeben. Sie können newintern oder Object.create()zum Erstellen eines Objekts mit einem bestimmten Prototyp verwendet werden.
nnnnnn
109

Vorteile der Verwendung von Konstruktoren

  • In den meisten Büchern lernen Sie, Konstruktoren und zu verwenden new

  • this bezieht sich auf das neue Objekt

  • Manche Leute mögen die Art zu var myFoo = new Foo();lesen.

Nachteile

  • Details der Instanziierung werden (über die newAnforderung) in die aufrufende API übertragen , sodass alle Aufrufer eng mit der Konstruktorimplementierung verbunden sind. Wenn Sie jemals die zusätzliche Flexibilität der Fabrik benötigen, müssen Sie alle Anrufer umgestalten (zugegebenermaßen eher der Ausnahmefall als die Regel).

  • Das Vergessen newist ein so häufiger Fehler, dass Sie unbedingt eine Boilerplate-Prüfung hinzufügen sollten, um sicherzustellen, dass der Konstruktor korrekt aufgerufen wird ( if (!(this instanceof Foo)) { return new Foo() }). BEARBEITEN: Seit ES6 (ES2015) können Sie newmit einem classKonstruktor nicht vergessen , oder der Konstruktor wird einen Fehler auslösen .

  • Wenn Sie die instanceofPrüfung durchführen, bleibt Unklarheit darüber, ob dies newerforderlich ist oder nicht . Meiner Meinung nach sollte es nicht sein. Sie haben die newAnforderung effektiv kurzgeschlossen , was bedeutet, dass Sie den Nachteil Nr. 1 beseitigen können. Aber dann haben Sie nur eine Factory-Funktion außer dem Namen , mit zusätzlichem Boilerplate, einem Großbuchstaben und einem weniger flexiblen thisKontext.

Konstruktoren brechen das Open / Closed-Prinzip

Mein Hauptanliegen ist jedoch, dass es gegen das Open / Closed-Prinzip verstößt. Sie beginnen mit dem Exportieren eines Konstruktors, Benutzer verwenden den Konstruktor und stellen dann fest, dass Sie stattdessen die Flexibilität einer Factory benötigen (z. B. um die Implementierung auf die Verwendung von Objektpools umzustellen oder über Ausführungskontexte hinweg zu instanziieren oder um haben mehr Vererbungsflexibilität mit prototypischem OO).

Du steckst aber fest. Sie können die Änderung nicht vornehmen, ohne den gesamten Code zu beschädigen, mit dem Ihr Konstruktor aufgerufen wird new. Sie können beispielsweise nicht zur Verwendung von Objektpools wechseln, um die Leistung zu steigern.

Die Verwendung von Konstruktoren führt zu einer Täuschung instanceof, die nicht über Ausführungskontexte hinweg funktioniert und nicht funktioniert, wenn Ihr Konstruktorprototyp ausgetauscht wird. Dies schlägt auch fehl, wenn Sie zunächst thisvon Ihrem Konstruktor zurückkehren und dann zum Exportieren eines beliebigen Objekts wechseln, was Sie tun müssen, um das fabrikähnliche Verhalten in Ihrem Konstruktor zu aktivieren.

Vorteile der Verwendung von Fabriken

  • Weniger Code - kein Boilerplate erforderlich.

  • Sie können ein beliebiges Objekt zurückgeben und einen beliebigen Prototyp verwenden. Dies gibt Ihnen mehr Flexibilität beim Erstellen verschiedener Objekttypen, die dieselbe API implementieren. Zum Beispiel ein Media Player, der Instanzen von HTML5 und Flash Playern erstellen kann, oder eine Ereignisbibliothek, die DOM-Ereignisse oder Web Socket-Ereignisse ausgeben kann. Fabriken können Objekte auch über Ausführungskontexte hinweg instanziieren, Objektpools nutzen und flexiblere prototypische Vererbungsmodelle ermöglichen.

  • Sie müssten nie von einer Fabrik zu einem Konstruktor konvertieren, daher wird Refactoring niemals ein Problem sein.

  • Keine Unklarheit über die Verwendung new. Tu es nicht. (Es wird thissich schlecht benehmen, siehe nächster Punkt).

  • thisverhält sich wie gewohnt - Sie können es also verwenden, um auf das übergeordnete Objekt zuzugreifen (z. B. innerhalb player.create(), thisbezieht sich playerwie bei jedem anderen Methodenaufruf auf. callund applyauch thiswie erwartet neu zuweisen. Wenn Sie Prototypen auf dem übergeordneten Objekt speichern, wird dies verwendet kann eine großartige Möglichkeit sein, Funktionen dynamisch auszutauschen und einen sehr flexiblen Polymorphismus für Ihre Objektinstanziierung zu ermöglichen.

  • Keine Unklarheit darüber, ob Kapital eingesetzt werden soll oder nicht. Tu es nicht. Fusselwerkzeuge werden sich beschweren, und dann werden Sie versucht sein, zu versuchen, zu verwenden new, und dann werden Sie den oben beschriebenen Vorteil rückgängig machen.

  • Manche Leute mögen den Weg var myFoo = foo();oder var myFoo = foo.create();lesen.

Nachteile

  • newverhält sich nicht wie erwartet (siehe oben). Lösung: Verwenden Sie es nicht.

  • thisverweist nicht auf das neue Objekt (stattdessen, wenn der Konstruktor mit Punktnotation oder eckiger Klammer aufgerufen wird, z. B. foo.bar () - thisverweist foo- wie bei jeder anderen JavaScript-Methode - auf Vorteile).

Eric Elliott
quelle
2
In welchem ​​Sinne meinen Sie, dass Konstruktoren die Aufrufer eng an ihre Implementierung koppeln? Was die Konstruktorargumente betrifft, müssen diese sogar an die Factory-Funktion übergeben werden, damit sie verwendet und der entsprechende Konstruktor aufgerufen werden kann.
Bharat Khatri
4
In Bezug auf die Verletzung von Open / Closed: Geht es hier nicht nur um Abhängigkeitsinjektion? Wenn A B benötigt, ob A neues B () oder A BFactory.create () aufruft, führen beide eine Kopplung ein. Wenn Sie andererseits A eine Instanz von B in der Kompositionswurzel geben, muss A überhaupt nichts darüber wissen, wie B instanziiert wird. Ich bin der Meinung, dass sowohl Konstrukteure als auch Fabriken ihre Verwendung haben. Konstruktoren dienen der einfachen Instanziierung, Fabriken der komplexeren Instanziierung. In beiden Fällen ist es jedoch ratsam, Ihre Abhängigkeiten einzufügen.
Stefan Billiet
1
DI eignet sich zum Injizieren des Status: Konfiguration, Domänenobjekte usw. Es ist übertrieben für alles andere.
Eric Elliott
1
Das Problem ist, dass das Erfordernis newdas Open / Closed-Prinzip verletzt. Unter medium.com/javascript-scene/… finden Sie eine viel ausführlichere Diskussion, als diese Kommentare zulassen.
Eric Elliott
3
Da jede Funktion ein neues Objekt in JavaScript zurückgeben kann und viele von ihnen dies ohne das newSchlüsselwort tun, glaube ich nicht, dass das newSchlüsselwort tatsächlich eine zusätzliche Lesbarkeit bietet. IMO, es scheint albern, durch Reifen zu springen, damit Anrufer mehr tippen können.
Eric Elliott
39

Ein Konstruktor gibt eine Instanz der Klasse zurück, für die Sie sie aufrufen. Eine Factory-Funktion kann alles zurückgeben. Sie würden eine Factory-Funktion verwenden, wenn Sie beliebige Werte zurückgeben müssen oder wenn eine Klasse einen großen Einrichtungsprozess hat.

Ignacio Vazquez-Abrams
quelle
5

Ein Beispiel für eine Konstruktorfunktion

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

let user = new User("Jack");
  • newErstellt ein Objekt, für das ein Prototyp erstellt wurde, User.prototypeund ruft Userdas erstellte Objekt als thisWert auf.

  • new behandelt einen Argumentausdruck für seinen Operanden als optional:

         let user = new User;

    würde veranlassen new, Userohne Argumente aufzurufen .

  • newGibt das erstellte Objekt zurück, es sei denn, der Konstruktor gibt einen Objektwert zurück , der stattdessen zurückgegeben wird. Dies ist ein Randfall, der größtenteils ignoriert werden kann.

Vor-und Nachteile

Von Konstruktorfunktionen erstellte Objekte erben Eigenschaften von der Konstruktoreigenschaft prototypeund geben mit dem instanceOfOperator für die Konstruktorfunktion true zurück .

Die oben genannten Verhaltensweisen können fehlschlagen, wenn Sie den Wert der Konstruktoreigenschaft dynamisch ändern, prototypenachdem Sie den Konstruktor bereits verwendet haben. Dies ist selten und kann nicht geändert werden, wenn der Konstruktor mit dem classSchlüsselwort erstellt wurde.

Konstruktorfunktionen können mit dem extendsSchlüsselwort erweitert werden .

Konstruktorfunktionen können nicht nullals Fehlerwert zurückgegeben werden. Da es sich nicht um einen Objektdatentyp handelt, wird er von ignoriert new.

Ein Beispiel für eine Factory-Funktion

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

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

Hier wird die Factory-Funktion ohne aufgerufen new. Die Funktion ist vollständig für die direkte oder indirekte Verwendung der Argumente und des zurückgegebenen Objekttyps verantwortlich. In diesem Beispiel wird ein einfaches [Objektobjekt] mit einigen Eigenschaften zurückgegeben, die aus Argumenten festgelegt wurden.

Vor-und Nachteile

Versteckt die Komplexität der Implementierung der Objekterstellung auf einfache Weise vor dem Aufrufer. Dies ist besonders nützlich für native Codefunktionen in einem Browser.

Die Factory-Funktion muss nicht immer Objekte des gleichen Typs zurückgeben und kann sogar nullals Fehleranzeige zurückgegeben werden.

In einfachen Fällen können Fabrikfunktionen in Struktur und Bedeutung einfach sein.

Zurückgegebene Objekte erben im Allgemeinen nicht von der prototypeEigenschaft der Factory-Funktion und kehren falsevon zurück instanceOf factoryFunction.

Die Factory-Funktion kann mit dem extendsSchlüsselwort nicht sicher erweitert werden, da erweiterte Objekte von der prototypeEigenschaft factory-Funktionen anstelle der prototypeEigenschaft des von der Factory-Funktion verwendeten Konstruktors erben würden .

traktor53
quelle
1
Dies ist eine späte Antwort als Antwort auf diese Frage zum gleichen Thema,
traktor53
Nicht nur "null", sondern "new" ignoriert auch alle von der Konstruktorfunktion zurückgegebenen prämitiven Datentypen.
Vishal
2

Fabriken sind "immer" besser. Bei Verwendung objektorientierter Sprachen dann

  1. über den Vertrag entscheiden (die Methoden und was sie tun werden)
  2. Erstellen Sie Schnittstellen, die diese Methoden verfügbar machen (in Javascript haben Sie keine Schnittstellen, daher müssen Sie eine Möglichkeit finden, die Implementierung zu überprüfen).
  3. Erstellen Sie eine Factory, die eine Implementierung jeder erforderlichen Schnittstelle zurückgibt.

Die Implementierungen (die mit new erstellten tatsächlichen Objekte) sind dem werkseitigen Benutzer / Verbraucher nicht zugänglich. Dies bedeutet, dass der Factory-Entwickler erweitern und neue Implementierungen erstellen kann, solange er nicht gegen den Vertrag verstößt ... und der Factory-Consumer nur von der neuen API profitieren kann, ohne seinen Code ändern zu müssen ... Wenn sie eine neue und eine "neue" Implementierung verwendet haben, müssen sie jede Zeile ändern, die "neu" verwendet, um die "neue" Implementierung zu verwenden ... mit der Fabrik ändert sich ihr Code nicht ...

Fabriken - besser als alles andere - das Federgerüst basiert vollständig auf dieser Idee.

Colin Saxton
quelle
Wie löst die Fabrik das Problem, dass jede Zeile geändert werden muss?
Codename Jack
0

Fabriken sind eine Abstraktionsebene und wie alle Abstraktionen kostenintensiv. Wenn Sie auf eine fabrikbasierte API stoßen, kann es für den API-Konsumenten eine Herausforderung sein, herauszufinden, was die Fabrik für eine bestimmte API ist. Bei Konstruktoren ist die Auffindbarkeit trivial.

Bei der Entscheidung zwischen Kunden und Fabriken müssen Sie entscheiden, ob die Komplexität durch den Nutzen gerechtfertigt ist.

Es ist erwähnenswert, dass Javascript-Konstruktoren beliebige Fabriken sein können, indem sie etwas anderes als dieses oder undefinierte zurückgeben. In js können Sie also das Beste aus beiden Welten herausholen - erkennbare API und Objektpooling / Caching.

PeterH
quelle
5
In JavaScript sind die Kosten für die Verwendung von Konstruktoren höher als die Kosten für die Verwendung von Fabriken, da jede Funktion in JS ein neues Objekt zurückgeben kann. Konstruktoren erhöhen die Komplexität durch: Erfordern new, Ändern des Verhaltens von this, Ändern des Rückgabewerts, Verbinden eines Prototyps ref, Aktivieren instanceof(was für diesen Zweck liegt und nicht verwendet werden sollte). Angeblich sind all dies "Merkmale". In der Praxis beeinträchtigen sie Ihre Codequalität.
Eric Elliott
0

Für die Unterschiede hat Eric Elliott sehr gut geklärt,

Aber für die zweite Frage:

Wann sollte man eins anstelle des anderen verwenden?

Wenn Sie aus dem objektorientierten Hintergrund kommen, sieht die Konstruktorfunktion für Sie natürlicher aus. Auf diese Weise sollten Sie nicht vergessen, das newSchlüsselwort zu verwenden .

Mostafa
quelle