Number945
Number945

Reputation: 4940

Module pattern in JavaScript for custom constructors

I am trying to apply module pattern in JavaScript when I have my custom constructor. Let us take this example:

// Assume I need `name` as private
function Dog(name){
    this.name = name;
}

I can rewrite above as:

function Dog(name) {

    let dogName = name;

    return {

        getName : function () {
            return dogName;
        },

        setName : function (name) {
            dogName = name;
        }
    }
}

The above code does not have right constructor property and Dog.prototype and the prototype of object returned does not match.

To fix this I made 2 Fixes:

function Dog(name) {

    let dogName = name;

    return {

        // Fix 1
        constructor : Dog,

        getName : function () {
            return dogName;
        },

        setName : function (name) {
            dogName = name;
        }
    }
}


let d2 = new Dog("Tony");
// Fix 2 : This is painful as every time I need to set this up
Object.setPrototypeOf(d2 , Dog.prototype);

As you can see Fix 2 is painful. Every time I have to create an object, I need to do this. Is there any better way?

Let us not deviate our discussion that getter and setter should be moved to prototype. Above is just a quick example I am using.

Upvotes: 2

Views: 96

Answers (2)

Robert
Robert

Reputation: 2669

Disclaimer: I don't advocate encapsulation in javascript. But here are some ugly ways to do it.

If all you're looking for is reduce code duplication, then you could easily just set everything up in the "constructor" by making it its own factory:

function Dog(name) {

    let dogName = name;

    let dog = Object.create(Dog.prototype);

    dog.getName = function () {
        return dogName;
    };

    dog.setName = function (name) {
        dogName = name;
    };

    return dog;
}


let d2 = new Dog("Tony");

If you also want to make actual use of the prototype you could use symbols to pretty much hide the name property:

Dog = function() {

    const nameSymbol = Symbol("name");

    function Dog(name) {
        Object.defineProperty(this, nameSymbol, {
            configurable: true,
            enumerable: false,
            writable: true,
            value: name
        });
    }

    Object.assign(Dog.prototype, {
        getName : function () {
            return this[nameSymbol];
        },

        setName : function (name) {
            this[nameSymbol] = name;
        }
    });

    return Dog;
}();

let d2 = new Dog("Tony");

But the symbol can still be retrieved via Object.getOwnPropertySymbols. So if you're looking for a way to really make it impossible to access the name without using the getter/setter, I think you will have to use a WeakMap:

Dog = function() {

    const dogNames = new WeakMap()

    function Dog(name) {
        dogNames.set(this, name);
    }

    Object.assign(Dog.prototype, {
        getName : function () {
            return dogNames.get(this);
        },

        setName : function (name) {
            dogNames.set(this, name);
        }
    });

    return Dog;
}();

let d2 = new Dog("Tony");

Upvotes: 2

Jake Holzinger
Jake Holzinger

Reputation: 6063

The problem here is you're returning an object literal in your "constructor", an object literal has the Object prototype. You should not be returning in the constructor, you should be assigning things to this like you did in your first code snippet.

function Dog(name) {

    let dogName = name;

    this.getName = function () {
        return dogName;
    };

    this.setName = function (name) {
        dogName = name;
    };
}

This isn't a common pattern for implementing classes though, you're not even using the prototype so it's unclear why the prototype is important to you. Obviously this approach allows you to close over a variable to make it "private" which wouldn't be possible using the prototype. However, this approach has more overhead because each instance of Dog will have it's own function for getName and setName, the standard convention for "private" variables and methods is to prefix them with an underscore so you can use the prototype as intended.

function Dog(name) {
    this._name = name;
}

Dog.prototype.getName = function () {
    return this._name;
};

Dog.prototype.setName = function (name) {
    this._name = name;
};

Upvotes: 2

Related Questions