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?

2
  • 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); Commented Dec 6, 2015 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. Commented Dec 6, 2015 at 11:19

1 Answer 1

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"]);
Sign up to request clarification or add additional context in comments.

8 Comments

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. ;)
By the way, if you use getters and setters, JSON.stringify is not going to work (as far as I know).
It does work in FF42. But thanks for mentioning, I will keep this in mind when testing in other/older browsers.
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.
I just updated the answer with a possible solution. non-enumerable properties don't get parsed by JSON.stringify: stackoverflow.com/questions/15733878/…
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.