Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I extend a class defined behind a closure in JavaScript?

Tags:

javascript

I have a set of JavaScript "classes" where a base class defines functions that are then shared by an inherited class. It is working, and it is set up like this:

var ThingA = function(name) {
    this.name = name;
};

ThingA.prototype = {
    sayHi: function() {
        alert('Hi, ' + this.name + '!');
    }
};


var ThingB = function() {
    ThingA.call(this, 'Charlie');
};

ThingB.prototype = new ThingA();
ThingB.prototype.constructor = ThingB;

var instanceOfB = new ThingB();
instanceOfB.sayHi();   // alerts 'Hi, Charlie!'

For reasons that are outside of my control, my company prefers to follow this pattern when writing JavaScript:

SomeClass = function() {

    // "Private" functions go here

    function somePrivateMethod() { 
        ...
    }

    return {

        // "Public" methods go here
        somePublicMethod: function() { ... }

    };
}();

Now, this is fine as far as things go, and it works well for many situations. But it is more of a functional style. There is only one "class" instance, and everything is static.

I've been asked to modify my working code to more closely match the style my company prefers. So my question is, there a way to inherit from a class that is wrapped inside a factory class? It would look something like this:

FactoryClassA = function() {

    var ThingA = function(name) {
        this.name = name;
    };

    ThingA.prototype = {
        sayHi: function() {
            alert('Hi, ' + this.name + '!');
        }
    };

    return {
         createThingA: function(name) {
             return new ThingA(name);
         }
    };
}();


FactoryClassB = function() {

    // Define a ThingB class that inherits from ThingA somehow

    return {
         createThingB: function() {
             return new ThingB();
         }
    };
}();


var instanceOfB = FactoryClassB.createThingB();
instanceOfB.sayHi();   // should alert 'Hi, Charlie!'

Is there a way to define ThingB wrapped in FactoryClassB that inherits from ThingA wrapped in FactoryClassA? Thanks to this question, I know that I'm not going to be able to do it exactly like this. I am thinking of using a method to extend a given class ... somehow?

This answer seems close, but I'm having trouble figuring out the details of how to modify that example to fit with the specifics of my situation. I am willing to bend my company's usual pattern a little bit, but can I at least get closer to it?

UPDATE 1

In response to Adam's comment to just add a parameter to the factory class, here's where I'm stuck:

ThingB.prototype = new ThingA();
ThingB.prototype.constructor = ThingB;

I can't figure out how to adapt these lines to make it work if I just pass in a parameter to the factory class method.

like image 877
Katie Kilian Avatar asked Mar 07 '14 15:03

Katie Kilian


1 Answers

Below is what (I believe) you're looking for:

FactoryClassA = function() {
    var ThingA = function(name) {
       this.name = name;
    };
    ThingA.prototype = {
        sayHi: function() {
            console.log('Hi, ' + this.name + '!');
        }
    };
    // Add the constructor back to the prototype
    // (see explanation below)
    ThingA.prototype.constructor = ThingA;

    return {
        createThingA: function(name) {
            return new ThingA(name);
        }
    };
}();

FactoryClassB = function() {
    // Bootstrapping:
    // Capture the instance, as we'll need it to set up the prototype
    var baseInstance = new FactoryClassA.createThingA();
    // Capture the constructor
    var baseConstructor = baseInstance.constructor;
    // Keep a reference to the base prototype
    var baseProto = baseConstructor.prototype;

    function ThingB(name) {
        // Call base constructor, along with our args
        baseConstructor.call(this, name);
    };
    ThingB.prototype = baseInstance;
    ThingB.prototype.constructor = ThingB;

    ThingB.prototype.sayHi = function() {
        console.log('here I am');
        // call the base class `sayHi`
        baseProto.sayHi.call(this);
    };

    return {
        createThingB: function(name) {
            return new ThingB(name);
        }
    };
}();

// Testing
var foo = FactoryClassB.createThingB("Indeed");
foo.sayHi();

// Output:
//   here I am 
//   hi indeed

Explanation:

in FactoryClassA, this line is necessary:

ThingA.prototype.constructor = ThingA;

Note that every prototype in JS is automatically created with a reference to its constructor. For example, when you do:

function T(){}

T.prototype already has a property called constructor which points back to T.

However, in your implementation of ThingA, you reset the entire prototype, by doing ThingA.prototype = { ... }. Therefore, you now have lost the reference to its constructor. In 99% of cases it is ok, and won't have any negative side effects (which is probably why most developers tend to forget it). However, in the case of inheritance, it may be necessary.

Now, within FactoryClassB, we need to do some bootstrapping:

var baseInstance = new FactoryClassA.createThingA();
var baseConstructor = baseInstance.constructor;
var baseProto = baseConstructor.prototype;

Observe the last two lines, as they are pivotal to achieving inheritance in this design pattern. First, since ThingA's constructor is accessible via the prototype (ThingA.prototype.constructor = ThingA), then it means that given an instance of ThingA, we can directly retrieve its constructor. Since the constructor is the function itself, and since every function has a reference to its prototype, we can keep a reference of ThingA.prototype with baseConstructor.prototype.

Next is the critical part, where we set up the inheritance chain:

function ThingB(name) {
    // Call the base constructor
    baseConstructor.call(this, name);
};
ThingB.prototype = baseInstance;
ThingB.prototype.constructor = ThingB;

The last line above is quite important, as it tells the prototype what its constructor is, otherwise it would still point to ThingA.

There you have it - prototypal inheritance.

Side note:

You can probably see how the above can get quite tedious, a little grotesque, and repetitive. Ergo, you might want to consider an inheritance library like Fiber.js which follows the encapsulation pattern you desired (along with some bonuses like mixins and decorators). Disclaimer: I authored the library.

like image 56
krisk Avatar answered Oct 15 '22 04:10

krisk