vendredi 29 avril 2016

is this a new javascript factory pattern?

Fair warning - a long time ago I wrote a lot of C++ and can't help the temptation to coerce javascript into design patterns I was familiar with back then. It's ok to accuse me of atavism in any replies ;-)

In my current project, I want to create objects by name, which indicates the factory pattern. So I read the top page of google hits for 'javascript factory pattern'. They all have this ugly thing in common:

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

return new parentClass();

Which has 2 problems:

  1. Every time I create a new part for the factory to make, I have to edit the factory's implementation and I'd prefer to avoid both the work & the bug-insertion opportunity.
  2. It's a hard-coded, linear search which hardly screams "efficiency!"

So here's what I came up with - a combination of the module and factory patterns with the info hiding merits of the module pattern provided by the tactic of defining the classes of factory parts inside the factory's register closure.

Finally getting to my question: I can't believe that this hasn't been done before by better coders than me so please share a link to the canonical version of this twist on the factory pattern if you know of one.

N.B. For this example I've run all my code together. In my project the factory, FactoryPartA, FactoryPartB, and client code are all in separate files.

namespace('mynamespace');

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

    // register an item
    api.register = function (item) {
        if (registry.some (function (r) {return r.name === item.name;})) { 
            throw new Error ('factory.register(): name collision detected: ' + name);
        } else {
            registry.push(item);
        }
    };

    // make an item given its name
    api.make = function (name) {
        var item = null;
        var idx = registry.findIndex (function (r) {
            return r.name === name;
        });
        if (idx >= 0) {
            item = new registry[idx].make();
        }
        return item;
    };

    return api;

})();


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

        this.test = undefined;  // public property

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

        return this;
    }
});

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

        this.test = undefined;

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

        return this;
    }
});

// client code
var aPart = mynamespace.factory.make('FactoryPartA');
var bPart = mynamespace.factory.make('FactoryPartB');

console.log (aPart.label()); // logs 'Factory Part A'
console.log (bPart.label()); // logs 'Factory Part B'

var anotherPart = mynamespace.factory.make('FactoryPartA');
aPart.test = 'this one is not';
anotherPart.test = 'the same as this one';
console.log (aPart.test !== anotherPart.test); // logs true

Aucun commentaire:

Enregistrer un commentaire