Понимание ООП JavaScript: паттерн модуля с наследованием

Я пытаюсь понять шаблон модуля с добавленным наследованием. У меня университетское образование, в моем багаже ​​в основном Java, но я работаю с веб-технологиями около десяти лет. Я только около года в JavaScript, хотя...

Во всяком случае, я пробую простой пример наследования (.prototype). Из объекта People вы можете добавить Gentleman, а затем перечислить их, используя их метод .toString(). Gentleman является дочерним элементом Human. Все шло хорошо, пока я не реализовал "абстрактный" Human, но теперь код не запускается.

Пожалуйста, прокомментируйте, что считается плохим с моим кодом. Я хотел бы остаться с подходом модуль/прототип, но что я делаю неправильно? Я бы также послушал, что означает this в разных контекстах. То есть, в People я могу использовать приватный _people напрямую, но в подмодулях я должен использовать this._name - почему?

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>');
    }
});

Скрипт: http://jsfiddle.net/5CmMd/5/

Изменить: я обновил код и немного повозился. Если я заставлю это работать, я думаю, что пойму большую часть дизайна. Я думаю, что этот пример также послужит простым учебным пособием для будущих программистов ООП, посещающих страну JavaScript.


person Viktor    schedule 03.05.2013    source источник
comment
А можно ссылку на описание выкройки? Я вижу ряд вещей, которые не сработают, но я лучше сформулирую это с точки зрения шаблона, по которому вы работаете.   -  person Tom Elmore    schedule 04.05.2013
comment
Этот шаблон действительно не очень хорошо описан. Я пытаюсь совместить то, что кажется мне правильным, с тем, что считается приемлемым в обсуждениях JS. Одна очень быстрая попытка использовать этот шаблон: stackoverflow. com/questions/8683125/ Но, черт возьми, не стесняйтесь изменять то, что хотите, и я могу прочитать большинство объяснений, если они хорошо определены. :)   -  person Viktor    schedule 04.05.2013


Ответы (2)


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

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

Должно быть constructor: Gentleman, .... Более того, вы назначаете prototype дважды и поэтому перезаписываете его. Это имеет побочный эффект, заключающийся в том, что Gentleman больше не наследуется от Human. Вы должны добавить к нему:

Gentleman.prototype.toString = toString;

Или вы добавляете свойства с помощью вызова Object.create(). см. ссылку

Что касается this:

Вы можете использовать _peopledirectly, потому что это переменная. Все экземпляры People используют один и тот же список людей.

Когда вы вызываете функцию как метод объекта, this относится к самому объекту. Поскольку у каждого Human должно быть свое имя, this._name относится к имени this ^^ человека. Таким образом, aGentleman.toString() вернет имя ровно this джентльмена.

People.Human = (function () {
  this._name; //<- does not do anything.
person a better oliver    schedule 03.05.2013
comment
Тогда как мне сделать constructor(args) { super(args); // Gentleman code }? И почему базовому классу People не нужно использовать this? - person Viktor; 04.05.2013
comment
@Viktor Вопрос 1: Вы уже это делаете. Я не понимаю вопроса. Вопрос 2: Вы знаете Java, поэтому знаете разницу между полем и переменной. С помощью this вы получаете доступ к полю. _people — это переменная. - person a better oliver; 04.05.2013
comment
1. Но если я удалю Gentleman.prototype = Object.create(People.Human.prototype); все равно не получится. Не могли бы вы предоставить обновленную скрипку? :) - person Viktor; 04.05.2013
comment
2. В Java this дает текущий экземпляр, но зачем мне this в People.Human, а не в обычном People? - person Viktor; 04.05.2013
comment
@Viktor http://jsfiddle.net/5CmMd/. 2. Точно, this относится к экземпляру. Каждый экземпляр People должен иметь собственное имя, следовательно, this._name. Я не знаю, почему вы используете переменную _people, а не свойство this._people. Это было твое решение. Вы могли бы сделать то же самое для Human и _name, но тогда у каждого человека было бы одинаковое имя. - person a better oliver; 04.05.2013
comment
Я пытался использовать this._people, но это не сработало? Хочешь немного пошалить? Я обновил его дальше! - person Viktor; 04.05.2013
comment
Вау, спасибо! Не могли бы вы также обновить jsfiddle.net/5CmMd/2? Я думаю, это принесет пользу моему пониманию! На мой взгляд, есть еще много функций, которые не очень стандартны для обычных языков ООП. Хотя я очень хорошо учусь на коде. - person Viktor; 04.05.2013
comment
Я снова начал возиться с ним сегодня днем, но не могу заставить его выполняться с моей добавленной функциональностью hasLadyParts(). Не могли бы вы взглянуть на эту скрипку? Я был бы так благодарен! Chrome выдает мне Uncaught RangeError: превышен максимальный размер стека вызовов... - person Viktor; 04.05.2013

Я думаю, что основная проблема заключается в прототипе Джентльменов. Для одного вы перезаписываете прототип, унаследованный от человека, для другого вы используете = вместо : для назначения функций :)

Попробуй это:

    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>');
    }
});

Вы можете видеть, что я добавил новый метод toString к объекту-прототипу, а не перезаписал то, что уже есть. Я не знаю, есть ли у шаблона более приятный способ сделать это (я не знаком с ним).

Вы можете сделать это, если это кажется более подходящим:

Gentleman.prototype = Object.create(People.Human.prototype, 
{ 
    constructor : { configurable: true, get : function() { return Gentleman } }, 
    toString : { configurable: true, get : function() { return toString } }
});
person Tom Elmore    schedule 03.05.2013
comment
Ага, значит, я должен использовать обозначение prototype., а не prototype = {}, потому что тогда я перезаписываю? - person Viktor; 04.05.2013
comment
да, поэтому, когда вы выполняете второе присваивание, оно просто перезаписывает ссылку на объект, которую вы установили в предыдущей строке. Чтение ссылки zeroflags, которую вы также можете добавить в дополнительные свойства, используя object.create() - плохое обновление с примером - person Tom Elmore; 04.05.2013
comment
Хорошо! Я обновил свой пример еще одним методом Human, но он не является абстрактным. Не могли бы вы обновить мою скрипку, чтобы она работала? Я получаю перезапись, и поэтому я использую ваше первое решение. - person Viktor; 04.05.2013
comment
Я немного расширил свой код, но у меня проблема с тем, что мой ребенок toString() не может вызвать родительский toString(). Посмотрите на эту скрипку! - person Viktor; 04.05.2013
comment
Я думаю, что в этот момент вы должны начать думать о том, почему вы хотите, чтобы javascript действовал как java. Это очень разные языки, и структура классов java на самом деле не переводится в javascript. Если вы хотите что-то более классическое, возможно, посмотрите на библиотеку, которая сделает эту работу за вас, или, возможно, почитайте о сценарии кофе. - person Tom Elmore; 05.05.2013