6
\$\begingroup\$

I have been developing Javascript for few months, and, as a former Java developer, needed a simple way to perform class inheritance.

My needs are:

  • having private members not accessible (encapsulation),
  • to make inheritance simple,
  • to avoid 'new' keywords bugs,
  • to let the superclass methods accessible using the this.super property.

The purpose of this request is to indicate me if the solution I developped should be applied into my project or if there are some misbehaviour risks by using it, and if you have remarks on how to enhance it.

My code is below, thank you for your reviews.

/**
* Allows to retrieve the global object (used to test if the 'new' keyword is needed)
*/
function getGlobal(){
    return (function(){
            return this;
    }).call(null);
}

if (typeof Object.extend !== 'function') {
    Object.extend = function (object, superClassConstructor, superConstructorArgs) {
        //Inherits from the super object
        superClassConstructor.apply(object, superConstructorArgs);

        //make this.super allow to access to inherited object original functions
        object.super = {};
        for (var property in object){
            object.super[property] = object[property];
        }
    };
}
//The superclass
function SuperClass(options){
    //Make sure we will not alter global object
    if (this === getGlobal()){ 
        throw new Error("Constructor cannot be called as a function (new keyword should not be omitted)");
    }
    //Use of jshashtable-2.1:
    var attributes = new Hashtable();

    for (var key in options){
        attributes.put(key, options[key]);
    }

    this.setValue = function(key, value){
        if(value != null){
            attributes.put(key, value);
        }
        else{
            attributes.remove(key);
        }
    };

    this.getValue = function(key){
        return attributes.get(key);
    };
}

/**
* Inheriting class
*/
function ProjectModel(projectId) {
    //Make sure we will not alter global object
    if (this === getGlobal()){ 
        throw new Error("Constructor cannot be called as a function (new keyword should not be omitted)");
    }
    //extends SuperClass
    Object.extend(this, SuperClass, [{
                                    id:projectId,
                                    'one':1,
                                    'two':"2"
                                    }]);

    this.myType = "ProjectNumber";
    //Overriding a function
    this.getValue = function(key){
        return this.myType + ":" + this.super.getValue(key);
    };
}

var project = new ProjectModel(123);
alert(project.getValue('one')); //this displays 'ProjectNumber:123'
\$\endgroup\$
  • \$\begingroup\$ I do not agree: I do not try to do like Java, I try to do OOP. This means encapsulation and encapsulation is not possible without private attributes. \$\endgroup\$ – lauhub Nov 7 '11 at 11:15
  • 4
    \$\begingroup\$ @lauhub you don't need "private" that's not what encapsulation is about. \$\endgroup\$ – Raynos Nov 7 '11 at 12:21
  • 5
    \$\begingroup\$ @lauhub but you don't need "privates". They are slow, ugly and a pain in the ass. If you going to use "privates" then give up on OO and use closures everywhere. State bound in closures is actually quite elegant but you shouldn't be emulating OO using it. closures (functional programming) and inheritance (OO programming) are parallel constructs pick one or the other because any combination of the two is ugly \$\endgroup\$ – Raynos Nov 7 '11 at 12:36
  • 1
    \$\begingroup\$ @cHao: I work with non IT engineers since 10 years (mechanical engineers, scientific researchers) who modify or code some stuffs. And all I have to say is, although they are very efficient into their fields, they are not able to follow some IT rules only because they are more interested into their fields than in IT. My job is to support them. My question was: "is my code correct and how to enhance it ?", it was not "what is your opinion about software development ?" Thank you to all the people who understand that. \$\endgroup\$ – lauhub Nov 7 '11 at 22:30
  • 1
    \$\begingroup\$ If they can't follow a simple rule, then they have no business touching code. Period. They wouldn't tolerate you coming in and scribbling on their plans/graphs/whatever they use; if your work is any less worthy of respect, crap like this is why. And the answer to your question is "No, because the very idea is flawed. Javascript does not have classes. It does not have private variables. It is not Java, and you can not turn it into Java, because the two languages are fundamentally different. The more you try, the more gotchas you'll run into. That way lies madness. You've been warned." \$\endgroup\$ – cHao Nov 8 '11 at 3:59
15
\$\begingroup\$

It's generally a bad idea to try writing code in one language as if it were a different language.

Don't fight your tool; use it as it was designed. JavaScript has prototype-based OO rather than class-based OO, so use it like that.

Read "Javascript - the Good Parts" and learn to write actually good JavaScript rather than "Java in JavaScript" (for which other developers working with your code will hate and/or mock you).

\$\endgroup\$
  • 6
    \$\begingroup\$ @lauhub so you've asserted, twice. But no, encapsulation does not mean private variables, as any programmer in Python (for example) will tell you. Again, please drop the Java mindset when you're not using Java. \$\endgroup\$ – Daniel Roseman Nov 7 '11 at 11:36
  • 6
    \$\begingroup\$ it is not possible always to trust developpers -- then NO amount of encapsulation is sufficient! \$\endgroup\$ – greengit Nov 7 '11 at 12:01
  • 4
    \$\begingroup\$ @lauhub "it is not possible always to trust developpers" Fire your developers. Stop writing ugly ass code to deal with incompetent team mates. We really need to get over this problem, the solution is not becoming a turtle fortress in your code. \$\endgroup\$ – Raynos Nov 7 '11 at 12:22
  • 3
    \$\begingroup\$ "it is not possible always to trust developpers" A bit of a tangent, but this mindset is something that has always bugged me about Java. The entire language is infused with the sense that you are being talked down to, versus languages like Python that give you a sense of being respected. \$\endgroup\$ – jhocking Nov 7 '11 at 13:44
  • 2
    \$\begingroup\$ @lauhub prevent developers from making errors is really easy. Write unit tests, make sure unit tests pass before you commit any code. \$\endgroup\$ – Raynos Nov 7 '11 at 14:17
5
\$\begingroup\$

No, it's wrong. JavaScript inheritance is really easy.

var SomeKlass = {
  ...
};

var someInstance = Object.create(SomeKlass);

For a more complete example / conversion of your code see the following example

to make inheritance simple,

Can't be any simpler, to inherit any object call Object.create on it.

to avoid 'new' keywords bugs,

You don't use new anymore so that problem dissappeared

to let the superclass methods accessible using the this.super property.

Emulating super with elegant syntax is a seriously non trivial problem

The best mechanism I've seen is function replacement with either setting this.$super to a sensible value before and after or recompiling the function to reference a super object. Both of those solutions are ugly, I'd recommend just referencing the super object directly by name.

having private members not accessible (encapsulation),

You can't do non accessible private members in JavaScript using prototypical OO. You can use closures, but that's not prototypical OO and that does not work with prototypical OO.

I recommend you use pseudo internal properties prefixed with _ and shoot any developer who uses it.

Alternatively you can use namespaces. Live Example.

var SuperClass = (function () {
    // Name shim : https://gist.github.com/1344853
    var privates = Name();

    return {
        constructor: function (options) {
            privates(this).attributes = options;
        },
        setValue: function (key, value) {
            if (value) {
                privates(this).attributes[key] = value;    
            } else {
                delete privates(this).attributes[key];
            }
        },
        getValue: function (key) {
            return privates(this).attributes[key];
        }
    };
})();


// Object.make : https://github.com/Raynos/pd#pd.make
var ProjectModel = Object.make(SuperClass, {
    constructor: function (projectId) {
        SuperClass.constructor.call(this, {
            id: projectId,
            one: 1,
            two: "2"  
        });
        this.myType = "ProjectModel";
    },
    getValue: function (key) {
        return this.myType + ":" + SuperClass.getValue.apply(this, arguments);
    }
});

var project = Object.create(ProjectModel);
project.constructor(123);
alert(project.getValue('one')); //this displays 'ProjectNumber:123'
\$\endgroup\$
  • \$\begingroup\$ Thank you for your solution: I already tried it and it does not fulfill my first need (private members). You are out of the scope. \$\endgroup\$ – lauhub Nov 7 '11 at 13:22
  • \$\begingroup\$ @lauhub what? Name fulfills private members in a very elegant way and it actually works with prototypical OO \$\endgroup\$ – Raynos Nov 7 '11 at 13:23
  • \$\begingroup\$ Sorry, I did not have noticed you had changed your code. Your first version did not use Name and I agree that it is an elegant way to do that. Thank you \$\endgroup\$ – lauhub Nov 7 '11 at 13:26
  • \$\begingroup\$ I have only a remark: if I need to create a private member, I have to write privates(this).myVariable. This is not as simple as var myVariable and I usually declare more that a single private member. \$\endgroup\$ – lauhub Nov 7 '11 at 14:02
  • \$\begingroup\$ I would cache privates, so var _ = privates(this) and then _.myVariable = ... so it's actually one less character. However you get the massive advantage of people to access private variables outside of the constructor scope as long as you have a reference to privates \$\endgroup\$ – Raynos Nov 7 '11 at 14:18
0
\$\begingroup\$

Taking into account remarks and suggestions, here is an enhanced version of my initial code: it works and fulfills my needs.

/**
* Allows to retrieve the global object
*/
function getGlobal(){
    return (function(){
            return this;
    }).call(null);
}
function getFunctionName(fonction){
    var nom = fonction.toString();
    nom = nom.substr('function '.length);
    nom = nom.substr(0, nom.indexOf('('));
    return nom;
}    
if (typeof Object.extend !== 'function') {
    Object.extend = function (object, objectConstructor, superClassConstructor, superConstructorArgs) {
        //To stop if new keyword was not used
        if (object === getGlobal()){  
            throw new Error("Constructor should always be called with new keyword.");
        }
        //Check arguments:
        if(objectConstructor === undefined){throw new Error("Missing argument: objectConstructor");} 
        if(superClassConstructor === undefined){throw new Error("Missing argument: superClassConstructor");} 
        if(superConstructorArgs === undefined){throw new Error("Missing argument: superConstructorArgs");} 

        //Calls super class' constructor, apply it to new object:
        superClassConstructor.apply(object, superConstructorArgs);

        //Create a super class "mirror" object
        var superProperties = {};
        //At this point, the new object will only have super object properties
        //Copies each property:
        for (var property in object){
            superProperties[property] = object[property];
        }
        //Set it to new object:
        object.super = superProperties;

        //put super class name into a property
        if(object.super.className === undefined){
            object.super.className = getFunctionName(superClassConstructor);
        }
        //Set className for object
        var className = getFunctionName(objectConstructor);
        object.className = className;
        //Set constructor for object
        object.constructor = objectConstructor;
    };
}
//The superclass
function SuperClass(options){
    //Inherits from object:
    Object.extend(this, arguments.callee, Object, [{}]);

    //Use of jshashtable-2.1:
    var attributes = new Hashtable();

    for (var key in options){
        attributes.put(key, options[key]);
    }

    this.setValue = function(key, value){
        if(value != null){
            attributes.put(key, value);
        }
        else{
            attributes.remove(key);
        }
    };

    this.getValue = function(key){
        return attributes.get(key);
    };
}

/**
* Inheriting class
*/
function ProjectModel(projectId) {
    //extends SuperClass
    Object.extend(this, arguments.callee, SuperClass, [{
                                    id:projectId,
                                    'one':1,
                                    'two':"2"
                                    }]);

    this.myType = "ProjectNumber";
    //Overriding a function
    this.getValue = function(key){
        return this.myType + ":" + this.super.getValue(key);
    };
}

var project = new ProjectModel(123);
alert(project.getValue('one'));
//At this point, project will have:
// - a className property set to "ProjectModel"
// - a super property which will contain:
// --> className set to "SuperClass"
// --> setValue and getValue functions from SuperClass
// --> a super property set to Object parent class 
//     (with a className property set to "Object")
\$\endgroup\$
  • \$\begingroup\$ Actually, the first thing I found that does not work with this is instanceof function: it does not work with super classes. A work around is to do SubClass.prototype = new SuperClass() after SubClass declaration. \$\endgroup\$ – lauhub Nov 14 '11 at 14:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.