0

I'm trying to grasp the module pattern with added inheritance. I come from a university background, with mostly Java in my trunk, but I have been working with web techniques for about ten years. I'm only about a year into JavaScript though...

Anyway, I'm trying a simple inheritance (.prototype) example. From a People object, you can add Gentleman's and then list them using their .toString() method. A Gentleman is a child to a Human. It went good until I implemented the "abstract" Human, but now the code will not run.

Please, comment on what is considered bad with my code. I would like to stay with the module/prototype approach though, but what am I doing wrong? I would also listen to what this means in different contexts. I.e., in People I can use the private _people directly, but in submodules I have to use this._name--why?

var People = People || {};

People = (function() {
    var People = function(){
        this._people = [];
    };

    var addGentleman = function (name) {
        this._people.push(new People.Gentleman(name));
    };

    var getList = function () {
        var temp = [];

        for (var i = 0; i < this._people.length; i++) {
            temp.push(this._people[i].toString());
        }

        return temp;
    };

    People.prototype.constructor = People;
    People.prototype.addGentleman = addGentleman;
    People.prototype.getList = getList;

    return People;
})();

People.Human = (function () {
    var Human = function (name, hasLadyParts) {
        this._name = name;
        this._hasLadyParts = hasLadyParts;
    };

    var hasLadyParts = function () {
        return this._hasLadyParts;
    };

    var toString = function () {
        var str = this._name;
        if (!this._hasLadyParts) str += ' no';
        return str + ' lady parts.';
    };

    Human.prototype.constructor = Human;
    Human.prototype.hasLadyParts = hasLadyParts;
    Human.prototype.toString = toString;

    return Human;
})();

People.Gentleman = (function () {
    var Gentleman = function (name) {
        People.Human.call(this, name, false);
    }

    var toString = function () {
        return 'Mr.' + People.Human.toString();
    };

    // Gentleman.prototype = Object.create(People.Human.prototype);
    Gentleman.prototype.constructor = Gentleman;
    Gentleman.prototype.toString = toString;

    return Gentleman;
})();

$(function () {
    var people = new People();
    people.addGentleman('Viktor');
    people.addGentleman('Joakim');
    var list = people.getList();
    var $ul = $('#people');

    for (var i = 0; i < list.length; i++) {
        $ul.append('<li>' + list[i] + '</li>');
    }
});

Fiddle: http://jsfiddle.net/5CmMd/5/

Edit: I've updated code and fiddle a bit. If I get this working, I think I understand most of the design. This example would also work as a simple tutorial for future OOP programmers visiting JavaScript land, I think.

Viktor
  • 487
  • 2
  • 8
  • 26
  • Can you give a link to a description of the pattern? I can see a number of things that arent going to work but Id rather phrase it in terms of the pattern youre working to. – Tom Elmore May 03 '13 at 20:07
  • This pattern is not really described very good. I'm trying to combine what feels right to me together with what is considered acceptable in JS discussions. One very quick attempt at this pattern: http://stackoverflow.com/questions/8683125/combining-inheritance-with-the-module-pattern But hell, feel free to change what you want, and I can read most explanations if they're well defined. :) – Viktor May 03 '13 at 21:12

2 Answers2

2
Gentleman.prototype = Object.create(People.Human.prototype);

Gentleman.prototype = {
    constructor = Gentleman,
    toString = toString
};

Should be constructor: Gentleman, .... Moreover you assign to the prototype twice and therefore overwrite it. That has the side effect that Gentleman no longer inherits from Human. You have to add to it:

Gentleman.prototype.toString = toString;

Or you add the properties with the Object.create() call. see reference

As for this:

You can use _peopledirectly because it is a variable. All instances of Peopleshare the same list of people.

When you call a function as a method of an object this refers to the object itself. Since every Human should have its own name this._name refers to the name of this ^^ human. So aGentleman.toString()would return the name of exactly this gentleman.

People.Human = (function () {
  this._name; //<- does not do anything.
Joseph at SwiftOtter
  • 4,276
  • 5
  • 37
  • 55
a better oliver
  • 26,330
  • 2
  • 58
  • 66
  • Then how do I do a `constructor(args) { super(args); // Gentleman code }`? And how come the base "class" `People` doesn't need to use `this`? – Viktor May 03 '13 at 21:15
  • @Viktor Question 1: You already do it. I don't understand the question. Question 2: You know Java, so you know the difference between a field and a variable. With `this` you access a field. `_people` is a variable. – a better oliver May 03 '13 at 21:43
  • 1. But if I delete `Gentleman.prototype = Object.create(People.Human.prototype);` it still will not work. Would you provide an updated fiddle? :) – Viktor May 03 '13 at 21:50
  • 2. In Java, `this` gives the current instance, but why would I need `this` in `People.Human` and not in ordinary `People`? – Viktor May 03 '13 at 21:51
  • @Viktor [http://jsfiddle.net/5CmMd/](http://jsfiddle.net/5CmMd/). 2. Excactly, `this` refers to the instance. Each instance of `People` should have its own name, hence `this._name`. I don't know why you use a variable `_people` and not a property `this._people`. It was your decision. You could have done the same for `Human` and `_name` but then each human would have the same name. – a better oliver May 03 '13 at 22:04
  • I tried using `this._people`, but it wouldn't work? Would you like to "fiddle" a bit? I've updated it further! – Viktor May 03 '13 at 22:25
  • Wow, thank you! Would you care to update http://jsfiddle.net/5CmMd/2/ too? I think this would benefit my understanding! There are still a lot of features that are not very standard in regular OOP languages, in my opinion. I do learn very well from code though. – Viktor May 03 '13 at 22:52
  • I started fiddling with it again this afternoon, but I can't get it to execute with my added functionality `hasLadyParts()`. Would you take a gander at this [fiddle](http://jsfiddle.net/5CmMd/5/), maybe? I would be ever so grateful! Chrome gives me "Uncaught RangeError: Maximum call stack size exceeded"... – Viktor May 04 '13 at 12:51
1

I think the main issue is in the prototype of Gentlemen. For one you are overwriting the prototype youve inherited from Human, for another you are using = instead of : to assign the functions :)

Try this:

    var People = People || {};

People = (function() {
    var _people = [];

    var People = function(){};

    var addGentleman = function (name) {
        _people.push(new People.Gentleman(name));
    };

    var getList = function () {
        var temp = [];

        for (var i = 0; i < _people.length; i++) {
            temp.push(_people[i].toString());
        }

        return temp;
    };

    People.prototype = {
        constructor: People,
        addGentleman: addGentleman,
        getList: getList
    };

    return People;
})();

People.Human = (function () {
    this._name;

    var Human = function (name) {
        this._name = name;
    };



    Human.prototype = {
        constructor: Human,
    };

    return Human;
})();

People.Gentleman = (function () {

    var Gentleman = function (name) {
        People.Human.call(this, name);     
    }

    var toString = function () {
        return 'Mr. ' + this._name;
    };

   Gentleman.prototype = Object.create(People.Human.prototype);


    Gentleman.prototype.constructor = Gentleman;
    Gentleman.prototype.toString = toString;

    return Gentleman;
})();

$(function () {
    var people = new People();
    people.addGentleman('Viktor'); // this is me
    people.addGentleman('Joakim'); // and my friend!
    var list = people.getList();
    var $ul = $('#people');

    for (var i = 0; i < list.length; i++) {
        $ul.append('<li>' + list[i] + '</li>');
    }
});

You can see that I have added the new toString method to the prototype object rather than overwriting whats already there. I dont know if the pattern has a nicer way to do this (im not familiar with it).

You could do this if it seems more appropriate:

Gentleman.prototype = Object.create(People.Human.prototype, 
{ 
    constructor : { configurable: true, get : function() { return Gentleman } }, 
    toString : { configurable: true, get : function() { return toString } }
});
Tom Elmore
  • 1,980
  • 15
  • 20
  • Aha, so I've got to use the `prototype.` notation and not `prototype = {}` because then I'm overwriting? – Viktor May 03 '13 at 21:17
  • yup - so when you do the second assignment it just overwrites the object reference that you set in the previous line. Reading zeroflags link you could also add in the extra properties using the object.create() - ill update with an example – Tom Elmore May 03 '13 at 21:49
  • Okay! I've updated my example with yet another `Human` method, but this is not abstract. Would you care to update my fiddle so that it works? I get the overwriting and I am therefore using your first solution. – Viktor May 03 '13 at 22:27
  • I extended my code a bit now, but I am having problem with my child's `toString()` not being able to call the parent's `toString()`. See this [fiddle](http://jsfiddle.net/5CmMd/5/)! – Viktor May 04 '13 at 13:05
  • I think at this point you have to start thinking about why you want javascript to act like java. They are very different languages and the class structure of java doesnt really translate to javascript. If you want something more classical, maybe look at a library that will do this work for you or maybe read about coffee script. – Tom Elmore May 05 '13 at 00:14