2016-04-29 16 views
3

Faire Warnung - vor langer Zeit schrieb ich eine Menge C++ und kann der Versuchung nicht widerstehen, Javascript in Entwurfsmuster zu zwingen, die ich damals kannte. Es ist in Ordnung, mich von Atavismus in alle Antworten zu beschuldigen ;-)ist dies ein neues Javascript-Fabrikmuster?


In meinem aktuellen Projekt, möchte ich namentlich Objekte erstellen, die das Werk Muster zeigt. Also lese ich die Top-Seite von Google Hits für 'Javascript Factory Pattern'. Sie alle haben diese hässliche Sache gemeinsam:

if (name === 'FactoryPartA') { 
    parentClass = PartA; 
} else if (name === 'FactoryPartB') { 
    parentClass = PartB; 
} else if ... 
    parentClass = PartZ; 
} 

return new parentClass(); 

Welche 2 Probleme:

  1. Jedes Mal, wenn ich einen neuen Teil schaffen für die Fabrik zu machen, ich habe die Fabrik Umsetzung zu bearbeiten und ich d lieber vermeiden beide die Arbeit & die Fehler-Insertion-Möglichkeit.
  2. Es ist eine hartcodierte, lineare Suche, die kaum "Effizienz" schreit!

Also hier ist, was ich kam mit - einer Kombination der Module und Fabrikmustern mit der Info Verdienste des Moduls Musters durch die Taktik innerhalb der register Schließung der Fabrik von der Definition der Klassen von Fabrikteilen versteckt.

Endlich zu meiner Frage: Ich kann nicht glauben, dass dies noch nicht von besseren Kodierern als ich getan wurde, also bitte teilen Sie einen Link zu der kanonischen Version dieser Drehung auf das Fabrikmuster, wenn Sie eines kennen.

N.B. In diesem Beispiel habe ich meinen gesamten Code zusammen ausgeführt. In meinem Projekt befinden sich die Factory, FactoryPartA, FactoryPartB und der Client-Code in separaten Dateien.

+4

Diese Art der dynamischen Factory-Verknüpfung ist genau das Problem Abhängigkeitsinjektion wurde erstellt, um zu lösen ([schamlose Link zu meiner eigenen Bibliothek] (https://github.com/ssube/noicejs)). – ssube

Antwort

4

Um die grundlegende Frage zu beantworten - ist dies ein neues Javascript-Fabrikmuster - nein, ist es nicht. (Check out ES6/ES2015, Typescript und Aureliens Dependency Injection-Modul.)

nun die allgemeine Aussage zu beantworten - was Sie im Wesentlichen tun versuchen, Metadaten zu einem „Klasse“ Typ in Javascript hinzuzufügen. Die Sache ist, es sieht so aus, als ob du versuchst, eine Fabrik von Fabriken zu schaffen - was ich nicht sicher bin. (. Vielleicht haben Sie Ihr Beispiel vereinfacht)

Mit Ihrem Beispiel, würde ich etwas mehr tun, wie folgt:

namespace('mynamespace'); 

// object factory 
mynamespace.factory = (function() { 
    'use strict'; 
    var api = {}; 
    var registry = {}; 

    // register an item 
    api.register = function (name, item, overwrite) { 
     if (!overwrite || registry.hasOwnProperty('name')) { 
      throw new Error ('factory.register(): name collision detected: ' + name); 
     } 

     registry[name] = item; 
    }; 

    // make an item given its name 
    api.make = function (name) { 
     var item = registry[name]; 
     return item ? new item() : null; // or better, Object.create(item); 
    }; 

    return api; 

})(); 


// define a module & register it with factory 
mynamespace.factory.register ('FactoryPartA', function FactoryPartA() { 
    'use strict'; 
    var label = 'Factory Part A'; // private property 

    this.test = undefined; // public property 

    this.label = function() { // public method 
     return label; 
    }; 
}); 

// define a different module & register it with factory 
mynamespace.factory.register ('FactoryPartB', function FactoryPartB() { 
    'use strict'; 
    var label = 'Factory Part B'; 

    this.test = undefined; 

    this.label = function() { 
     return label; 
    }; 
}); 

Dieses Zeug die zusätzliche Fabrik entfernt, so dass es kein FactoryFactory ist. (Nicht sicher, warum Sie das hatten.) Außerdem haben Sie eine lineare Suche erwähnt - indem Sie ein Objekt anstelle eines Arrays verwenden, vermeiden Sie eine lineare Suche (Objekt-Hashes sind Konstant-Zeit-Lookup). Schließlich können Sie jeden Namen registrieren, ohne ihn in ein Wrapper-Objekt einfügen zu müssen. Dies ist näher an DI.

Wenn Sie wirklich den Metadaten-Stil Ansatz wollen tun, obwohl, können Sie so etwas wie die folgenden tun:

namespace('mynamespace'); 

// object factory 
mynamespace.factory = (function() { 
    'use strict'; 
    var api = {}; 
    var registry = {}; 

    // register an item 
    api.register = function (item, overwrite) { 
     if (!overwrite || registry.hasOwnProperty(item.name)) { 
      throw new Error ('factory.register(): name collision detected: ' + item.name); 
     } 

     registry[item.name] = item; 
    }; 

    // make an item given its name 
    api.make = function (name) { 
     var item = registry[name]; 
     return item ? new item() : null; // or better, Object.create(item); 
    }; 

    return api; 

})(); 


// define a module & register it with factory 
mynamespace.factory.register (function FactoryPartA() { 
    'use strict'; 
    var label = 'Factory Part A'; // private property 

    this.test = undefined; // public property 

    this.label = function() { // public method 
     return label; 
    }; 
}); 

// define a different module & register it with factory 
mynamespace.factory.register (function FactoryPartB() { 
    'use strict'; 
    var label = 'Factory Part B'; 

    this.test = undefined; 

    this.label = function() { 
     return label; 
    }; 
}); 

Dies verwendet die Funktionsnamen anstelle eine separate Eigenschaft. (Funktionen können in Javascript benannt oder anonymisiert werden. Diese Methode würde nicht mit anonymen Funktionen funktionieren.) Ich erwähnte Typescript oben, da Typoskript, wenn es in Javascript kompiliert, tatsächlich viele eigene Metadaten auf Objekte legt, ähnlich wie Sie es tun .Ich erwähnte die Abhängigkeitsinjektion von Aurelia, weil die Funktionalität @autoinject diese Metadaten tatsächlich liest, um Objekte zu erstellen, ähnlich wie Sie es tun.

Aber wirklich .... es sei denn, Sie versuchen, einen Dependency-Injektionscontainer zu erstellen (was mehr Logik als in Ihrem Beispiel erfordern würde - Sie möchten einen Container mit Schlüsseln, die auch auf Instanzen verweisen können) würde argumentieren, dass Sie viel mehr Funktionalität aus Object.create() und Object.assign() bekommen, als Sie mit diesem Muster würden. Viele Entwurfsmuster, die Sie in einer statisch typisierten, stark typisierten, kompilierten Sprache benötigen, sind außerhalb dieser Umgebung nicht erforderlich. So können Sie das tun:

function PartA() { 
    'use strict'; 
    var label = 'Factory Part A'; // private property 

    this.test = undefined; // public property 

    this.label = function() { // public method 
     return label; 
} 

function PartB() { 
    'use strict'; 
    var label = 'Factory Part B'; 

    this.test = undefined; 

    this.label = function() { 
     return label; 
    }; 
} 

var A = Object.create(PartA); 
var B = Object.create(PartB); 

Dies ist, einfach, viel einfacher.

+0

Danke für solch eine umfassende und erhellende Antwort. Zu wissen, dass Hashes konstante Zeit sind, ist wirklich nützlich. – VorpalSword

+0

Ich hoffe, dass der Rest auch nützlich war. Dies ist nicht unbedingt die beste Antwort, da ich über viele Details, die für die Frage und die Antwort relevant sind, überflogen (und übersprungen) habe. Die meisten Designmuster, die wir verwenden, sind darauf zugeschnitten, das Typsystem auf unsere Wünsche zu übertragen - sie sind übrigens auch hauptsächlich für die klassische OOP gedacht. Außerdem werden einige dieser Entwurfsmuster in höheren Sprachen nicht benötigt. Ich würde sehr empfehlen, einige Dokumentationen über JavaScript zu lesen - Sie können die High-Level-Funktionen verwenden, um Sie zu unterstützen, und die funktionale Seite verwenden, um Standard-Code zu vermeiden. –