Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
like image 283
VorpalSword Avatar asked Apr 29 '16 17:04

VorpalSword


People also ask

What is Factory Pattern JavaScript?

The factory pattern is a creational design pattern that provides a generic interface for creating objects. In the factory pattern, we can specify the type of object being created and we do not need to explicitly require a constructor.

Is JavaScript Prototype pattern?

Classical languages rarely use the Prototype pattern, but JavaScript being a prototypal language uses this pattern in the construction of new objects and their prototypes.

Does JavaScript have design patterns?

You may not know it, but you've used a JavaScript design pattern. Design patterns are reusable solutions to commonly occurring problems in software design. During any language's lifespan, many such reusable solutions are made and tested by a large number of developers from that language's community.

How many JavaScript design patterns are there?

The book explores the capabilities and pitfalls of object-oriented programming, and describes 23 useful patterns that you can implement to solve common programming problems. These patterns are not algorithms or specific implementations.


1 Answers

To answer the fundamental question - is this a new Javascript factory pattern - no, it is not. (Check out ES6/ES2015, Typescript, and Aurelia's dependency injection module.)

Now, to answer the overall statement - what you're essentially doing is trying to add metadata to a "class" type in Javascript. The thing is, it looks like you're trying to create a factory of factories - which I'm not sure you need. (Perhaps you've simplified your example.)

With your example, I'd do something more like this:

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

This removes the extra factory stuff so that it's not a FactoryFactory. (Not sure why you had that.) Further, you mentioned a linear search - by using a object instead of an array, you avoid a linear lookup (object hashes are constant-time lookup). Finally - you can actually register any name without having to place it into a wrapper object. This is closer to DI.

If you really want to do the metadata-style approach, though, you can do something like the following:

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

This uses the function name instead of a separate property. (Functions can be named or anonymous in Javascript. This method would not work with anonymous functions.) I mentioned Typescript above because when Typescript compiles to Javascript, it actually places a lot of its own metadata on objects, similar to what you're doing. I mentioned Aurelia's dependency injection because its @autoinject functionality actually reads this metadata to create objects, similar to what you're doing.

But really....unless you're attempting to create a dependency injection container (which would require more logic than is in your example - you'd want a container with keys that can point to instances as well) I would argue that you'll get much more functionality out of Object.create() and Object.assign() than you would with this pattern. A lot of the design patterns that you need in a statically typed, strongly typed, compiled language, aren't necessary outside of that environment. So you can do this:

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

This is, simply, much easier.

like image 158
jedd.ahyoung Avatar answered Sep 21 '22 21:09

jedd.ahyoung