2

My use case is the following: I want to create a factory which produces various kinds of data transfer objects (DTOs). They must be easily serializable and they must have a few additional methods.

My current implementation looks like this (simplified):

window.Dto = function(type, properties)
{
    var
        self = this,
        values = {},
        object = Object.create(self);

    properties.forEach(function(prop){
        Object.defineProperty(object, prop, {
            get: function() { return values[prop]; },
            set: function(value) { values[prop] = value; },
            enumerable: true
        });
    });

    this.getType = function()
    {
        return type;
    };

    this.doSomeMagic = function()
    {
        // ...
    };

    return object;
};

// creating a DTO of the Transport.Motorized.Car class
var carObject = new Dto("Transport.Motorized.Car", ["vendor", "model", "color", "vmax", "price"]);

(Note: I do not want to create an explicit class for each of these objects, because there are hundets of them, and they are exported from the server side. Also, what you see as properties parameter above, is actually a map of meta data with validation constraints etc.)

I did a quick performance check with a loop where 50,000 of such objects were created. performance.now() tells me that it took a bit more than 1s – which looks ok, but not too impressive.

My question is mainly: Is it ok that the factory creates an instance from its own prototype (if I understand correctly what that code does) and returns it? What side effects can it have? Is there a better way?

Community
  • 1
  • 1
lxg
  • 12,375
  • 12
  • 51
  • 73
  • In general, I would not use the `new` operator for a factory function. Rather, I would create an object inside the function to use as the prototype: Instead of using `this`, you could do `var factoryProto = {}; factoryProto.getType = ...; object = Object.create(factoryProto);` – nils Dec 06 '15 at 11:16
  • @nils: The newly created object needs access to `Dto`’s “private” members, therefore I cannot create the instance from outside. There are meta data and e.g. validation functions which need access to the metas. And on serialization, I don’t want to see attached properties/methods. – lxg Dec 06 '15 at 11:19

1 Answers1

2

As far as I understand factory functions, their whole point is not needing to create new instances of the function itself. Instead, it just returns a newly created object.

So instead of using instance properties (via this) of the newly created instance (via the new operator), I would just create an object (let's call it factoryProto) and assign all the "instance" methods to that object instead.

Then, you can use factoryProto as the [[Prototype]] for your new object:

window.Dto = function(type, properties) {
    var factoryProto = {
          getType: function() {
            return type;
          },
          doSomeMagic: function() {
              // ...
          }
        },
        values = {},
        object = Object.create(factoryProto);

    properties.forEach(function(prop) {
        Object.defineProperty(object, prop, {
            get: function() { return values[prop]; },
            set: function(value) { values[prop] = value; },
            enumerable: true
        });
    });

    return object;
};

// creating a DTO of the Transport.Motorized.Car class
var carObject = Dto("Transport.Motorized.Car", ["vendor", "model", "color", "vmax", "price"]);

If you want to fully profit from the prototype-chain, you could define the factoryProto outside of the factory function. To keep track of type, you could add it as a non-enumerable object property:

window.Dto = (function() {
    var factoryProto = {
        getType: function() {
          return this.type;
        },
        doSomeMagic: function() {
            // ...
        }
    };

    return function(type, properties) {
        var values = {},
            object = Object.create(factoryProto);

        properties.forEach(function(prop) {
            Object.defineProperty(object, prop, {
                get: function() { return values[prop]; },
                set: function(value) { values[prop] = value; },
                enumerable: true
            });
        });

        Object.defineProperty(object, 'type', {
          value: type,
          enumerable: false
        });

        return object;
    };
})();

// creating a DTO of the Transport.Motorized.Car class
var carObject = Dto("Transport.Motorized.Car", ["vendor", "model", "color", "vmax", "price"]);
nils
  • 25,734
  • 5
  • 70
  • 79
  • Yup, that's better. Jeez, I had something similar yesterday evening before I started fiddeling with `Object.create`. I would have just have had to put it together. ;) – lxg Dec 06 '15 at 11:26
  • By the way, if you use getters and setters, `JSON.stringify` is not going to work (as far as I know). – nils Dec 06 '15 at 11:27
  • It does work in FF42. But thanks for mentioning, I will keep this in mind when testing in other/older browsers. – lxg Dec 06 '15 at 11:28
  • My bad, you can in fact stringify getters :) I just had a bad testcase. – nils Dec 06 '15 at 11:32
  • 2
    Just one more pointer, since you are planning on creating quite a few instances: If you define the factoryProto inside the factory function, you wont have the performance benefits of the prototype system (since `factoryProto` is redefined at every call of `window.Dto`). You could solve this with an IIFE and non-enumerable properties though. – nils Dec 06 '15 at 11:39
  • You’re right, thanks! Though I have to see if this works in my case. – lxg Dec 06 '15 at 11:41
  • 2
    I just updated the answer with a possible solution. non-enumerable properties don't get parsed by `JSON.stringify`: http://stackoverflow.com/questions/15733878/why-does-json-stringify-not-serialize-non-enumerable-properties – nils Dec 06 '15 at 11:46
  • Awesome, thanks! Works nice, and performance has doubled (500 ms for 50000 elements). Though I have changed `this.type` to `this._type` and omitted `enumerable: false` (as this is the default) in my code. Btw, I took the liberty of adding a missing `var` in your example. – lxg Dec 06 '15 at 12:31