Javascript-форум (https://javascript.ru/forum/)
-   Оффтопик (https://javascript.ru/forum/offtopic/)
-   -   Множественное наследование, супер методы, наследование дескрипторов (https://javascript.ru/forum/offtopic/45583-mnozhestvennoe-nasledovanie-super-metody-nasledovanie-deskriptorov.html)

nerv_ 06.03.2014 20:17

Многоуровневое наследование, супер методы, наследование дескрипторов
 
Всем привет :)

Этот пост отвечает на следующие вопросы:
1. Как написать наследование на чистом javascript
2. Как написать многоуровневое наследование
3. Как написать наследование дескрипторов
4. Как написать многоуровневое наследование с наследованием дескрипторов
5. Как вызывать супер методы

Никаких библиотек не подключено, весь используемый код расположен ниже.
В качестве "многоуровневое наследования" выступают три класса:
Animal -> Mammal - Cat.
В качестве супер-метода выступает метод destroy().

Данный пост отражает точку зрения автора и носит рекомендательный характер.

/**********************
 * HELPERS
 * *********************/
/**
 * @borrows toString as toString
 */
Object.toString = Object.prototype.toString;
/**
 * Returns "true" if a value is date
 * @param {*} v A value
 * @return {Boolean}
 */
Date.isDate = function(v) {
    return Object.toString.call(v) === '[object Date]';
};
/**
 * Returns "true" if a value is regular expression
 * @param {*} v A value
 * @return {Boolean}
 */
RegExp.isRegExp = function(v) {
    return Object.toString.call(v) === '[object RegExp]';
};
/**
 * Returns "true" if a value is array
 * @param {*} v A value
 * @return {Boolean}
 */
Array.isArray = Array.isArray || function(v) {
    return Object.toString.call(v) === '[object Array]';
};
/**
 * Creates clone of object
 * Not working with DOM elements
 * [url]http://stackoverflow.com/a/728694[/url]
 * [url]https://github.com/andrewplummer/Sugar/blob/master/lib/object.js#L328[/url]
 * @param {Object} obj
 * @return {Object}
 */
Object.clone = function(obj) {
    // Number, String, Boolean, Function, null, undefined
    if (null === obj || 'object' !== typeof obj) {
        return obj;
    }

    // Date and RegExp
    if (Date.isDate(obj) || RegExp.isRegExp(obj)) {
        return new obj.constructor(obj);
        // Array and Object
    } else {
        var copy = Array.isArray(obj) ? [] : Object.create(Object.getPrototypeOf(obj));
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                copy[key] = this.clone(obj[key]);
            }
        }
        return copy;
    }
};
/**
 * Inherits a target (Class_1) by a source (Class_2)
 * @param {Object} target
 * @param {Object} source
 */
Object.inherit = function(target, source) {
    target._super = source;
    target.prototype = Object.create(target._super.prototype, target._super.descriptor);
    target.prototype.constructor = target;
};


/**********************
 * CLASSES
 * *********************/

/**
 * Class Animal
 * @param {Number} legs
 * @constructor
 */
function Animal(legs) {
    var descriptor = Object.clone(Animal.descriptor);
    descriptor.legs.value = legs;
    Object.defineProperties(this, descriptor);
}

Animal.prototype.destroy = function() {
    console.log('Animal.destroy()');
    for(var key in Animal.descriptor) {
        delete this[key];
    }
};
Animal.prototype.run = function() {
    console.log('Animal.run()');
};

Animal.descriptor = {
    legs: {
        value: null,
        enumerable: true,
        configurable: true
    }
};

// ------------------------

// Inheritance
Object.inherit(Mammal, Animal);

/**
 * Class Mammal
 * @param {Number} legs
 * @param {Number} teeth
 * @constructor
 */
function Mammal(legs, teeth) {
    // call the Animal constructor
    Mammal._super.apply(this, arguments);

    var descriptor = Object.clone(Mammal.descriptor);
    descriptor.teeth.value = teeth;
    Object.defineProperties(this, descriptor);
}

Mammal.prototype.destroy = function() {
    console.log('Mammal.destroy()');
    Mammal._super.prototype.destroy.call(this);
    for(var key in Mammal.descriptor) {
        delete this[key];
    }
};
Mammal.prototype.walk = function() {
    console.log('Mammal.walk()');
};

Mammal.descriptor = {
    teeth: {
        value: null,
        enumerable: true,
        configurable: true
    }
};

// ------------------------

// Inheritance
Object.inherit(Cat, Mammal);

/**
 * Class Cat
 * @param {Number} legs
 * @param {Number} teeth
 * @param {Number} tails
 * @constructor
 */
function Cat(legs, teeth, tails) {
    // call the Mammal constructor
    Cat._super.apply(this, arguments);

    var descriptor = Object.clone(Cat.descriptor);
    descriptor.tails.value = tails;
    Object.defineProperties(this, descriptor);
}

Cat.prototype.destroy = function() {
    console.log('Cat.destroy()');
    Cat._super.prototype.destroy.call(this);
    for(var key in Cat.descriptor) {
        delete this[key];
    }
};
Cat.prototype.climb = function() {
    console.log('Cat.climb()');
};

Cat.descriptor = {
    tails: {
        value: null,
        enumerable: true,
        configurable: true
    }
};


/********************
 * USAGE / INSTANCE
 ********************/

var cat = new Cat(8, 100, 3);

alert(JSON.stringify(cat));
console.info('created', cat);

console.log('cat instanceof Cat', cat instanceof Cat);
console.log('cat instanceof Mammal', cat instanceof Mammal);
console.log('cat instanceof Animal', cat instanceof Animal);

cat.run(); // Animal.prototype.run
cat.walk(); // Mammal.prototype.walk
cat.climb(); // Cat.prototype.climb
cat.destroy(); // Cat -> Mammal -> Animal

alert(JSON.stringify(cat));
console.info('destroyed', cat);

Octane 14.03.2014 10:29

Цитата:

Сообщение от nerv_
Object.toString = Object.prototype.toString;

Нафига? В Object и так есть toString

Цитата:

Сообщение от nerv_
// Inheritance
Object.inherit(…, …);

Я бы подумал, что это какие-то манипуляции с __proto__, может быть лучше в Function или вообще отдельно?

Кстати, мне последнее время нравится идея создавать объекты, не описывая конструкторы:
var Animal = {

	destroy: function () {
		console.log('Animal.destroy()');
	},

	run: function () {
		console.log('Animal.run()');
	}

};

var Mammal = Object.create(Animal);

Object.assign(Mammal, {

	walk: function () {
		console.log('Mammal.walk()');
	}

});

var Cat = Object.create(Mammal);
как-то так… правда тут с instanceof непонятки могут возникнуть

melky 14.03.2014 10:43

Цитата:

Сообщение от Octane
Нафига? В Object и так есть toString

а что это за статический метод? откуда он взялся?

Octane, согласен, ES6 - крутая вещь

Octane 14.03.2014 10:53

Цитата:

Сообщение от melky
а что это за статический метод? откуда он взялся?

Object.toString === Function.prototype.toString // true

nerv_ 14.03.2014 12:30

Цитата:

Сообщение от Octane
Нафига? В Object и так есть toString

есть, только не работает =)

Цитата:

Сообщение от Octane
Я бы подумал, что это какие-то манипуляции с __proto__

Никакой черной магии, все законно :)

Немного переписал функцию наследования согласно тому, что есть в Node.js
Если точнее, то вместо Class._super стало Class.super_

Цитата:

Сообщение от Octane
Кстати, мне последнее время нравится идея создавать объекты, не описывая конструкторы

а еще нет прототипов и нет супер методов

Цитата:

Сообщение от Octane
может быть лучше в Function или вообще отдельно?

чего?

BallsShaped 14.03.2014 13:02

Цитата:

Сообщение от nerv_
чего?

Метод .inherit

Octane 15.03.2014 01:15

Цитата:

Сообщение от nerv_
а еще нет прототипов и нет супер методов

В том и идея, что описываем прототипы, вместо конструкторов. Кроме как в конструкторе, ссылка super чаще всего бывает нужна только в выдуманных примерах, а тут нет конструкторов.
Единственное неудобство в instanceof, но можно воспользоваться isPrototypeOf
var barsic = Object.create(Cat);
if (Cat.isPrototypeOf(barsic)) {
   …
}

а еще getPrototypeOf (IE9+) есть, так что без super вполне можно обойтись
Object.getPrototypeOf(Cat) → Mammal

http://www.reddit.com/r/javascript/c...s_approach_is/

Не навязываю, что надо делать именно так, просто как пример интересного подхода


Цитата:

Сообщение от Octane
может быть лучше в Function или вообще отдельно?
Цитата:

Сообщение от nerv_
чего?
Цитата:

Сообщение от BallsShaped
Метод .inherit



Я про то, что в Object методу inherit не место.

nerv_ 16.03.2014 17:33

Цитата:

Сообщение от Octane
В том и идея, что описываем прототипы, вместо конструкторов

Куда писать те свойства, кот. должны быть созданы для каждого экземпляра, как не в конструктор?
Т.е. моя позиция в том, что придется описывать и то и другое.
А еще у нас есть дескрипторы.

Цитата:

Сообщение от Octane
Единственное неудобство в instanceof, но можно воспользоваться isPrototypeOf

я бы не стал так утверждать)

Цитата:

Сообщение от Octane
Кроме как в конструкторе, ссылка super чаще всего бывает нужна только в выдуманных примерах, а тут нет конструкторов.

Что то я тебя не понял :) Если есть необходимость в множественном наследовании, то ссылка нужна и на мой взгляд удобнее, чем
Object.getPrototypeOf(object)


Цитата:

Сообщение от Octane
Я про то, что в Object методу inherit не место.

не хотел расширять прототипы. В целом на усмотрение разработчика :)

Octane 16.03.2014 18:07

Кстати, тут ни разу не множественное наследование, у тебя всегда один суперкласс.


Цитата:

Сообщение от nerv_
Куда писать те свойства, кот. должны быть созданы для каждого экземпляра, как не в конструктор?

Либо инициализирующий метод, либо отдельно set-метод для каждого свойства/группы свойств, что мне кажется лучше. Это намного нагляднее, чем искать что там произошло после super.apply.


Цитата:

Сообщение от nerv_
Цитата:

Сообщение от Octane
Единственное неудобство в instanceof, но можно воспользоваться isPrototypeOf

я бы не стал так утверждать)

Можно подробнее в чем проблема?
var Animal = {};

var Mammal = Object.create(Animal);

var Cat = Object.create(Mammal);

var barsic = Object.create(Cat);

alert(Animal.isPrototypeOf(barsic)); // true
alert(Mammal.isPrototypeOf(barsic)); // true
alert(Cat.isPrototypeOf(barsic));    // true



Цитата:

Сообщение от nerv_
Цитата:

Сообщение от Octane
Кроме как в конструкторе, ссылка super чаще всего бывает нужна только в выдуманных примерах, а тут нет конструкторов.

Что то я тебя не понял Если есть необходимость в множественном наследовании, то ссылка нужна и на мой взгляд удобнее, чем Object.getPrototypeOf(object)

Вместо
Cat._super.prototype.destroy.call(this);

будет
Object.getPrototypeOf(Cat).destroy.call(this);
разве что в большем количестве символов неудобство


Цитата:

Сообщение от nerv_
А еще у нас есть дескрипторы.

Да дескрипторы у тебя это так наворот к классическому примеру с наследованием, для меня в этом нет никакого академического интереса.

nerv_ 16.03.2014 20:19

Цитата:

Сообщение от Octane
Кстати, тут ни разу не множественное наследование, у тебя всегда один суперкласс.

Согласен. Я выразился не совсем точно. Следовало бы переименовать в "многоуровневое" (например). А нативного множественного наследования в js нет, только mixin'ы.

Цитата:

Сообщение от Octane
Можно подробнее в чем проблема?

следуя общепринятым соглашениям по наименованию, с больших букв пишутся имена "классов". А раз это класс - это функция, следовательно можно использовать оператор new:
var Animal = {};

var Cat = Object.create(Animal);
      
var obj = new Cat();

console.log(obj);

Конечно, тут можно заявлять, что встроенные операторы языка нам не нужны, но если они есть, почему бы ими не воспользоваться?
Т.е. ты ломаешь то, что заложено в язык и к чему привыкли js-разработчики.

Цитата:

Сообщение от Octane
Да дескрипторы у тебя это так наворот к классическому примеру с наследованием

скорее как сахар. Признаться, я их тоже не люблю, но в одном проекте потребовались :)
Вместе с тем, считаю очень удобным то, что, например, приватные свойства
this._property;

можно делать неперебираемыми.

Цитата:

Сообщение от Octane
Либо инициализирующий метод, либо отдельно set-метод для каждого свойства/группы свойств, что мне кажется лучше. Это намного нагляднее, чем искать что там произошло после super.apply.

1. "Либо инициализирующий метод" та же функция конструктор
2. "либо отдельно set-метод для каждого свойства/группы свойств, что мне кажется лучше" не вижу причин, по которым нельзя сделать те же самые сет-методы в "стандартной" реализации

Впрочем, сколько людей, столько и мнений :)


Часовой пояс GMT +3, время: 02:22.