Множественное наследие OOП

449
22 декабря 2017, 03:25

Есть три класса: A, B и C. Как классу C унаследовать параметры и методы классов A и B?

/** 
 * @class A 
 */ 
function A (name) { 
	this.name = name; 
} 
 
A.prototype.methodA = function(){ 
	console.log("name:" + this.name) 
}; 
 
/** 
 * @class B 
 */ 
function B (age) { 
	this.age  =  age; 
} 
 
B.prototype.methodB = function(){ 
	console.log("age:" + this.age) 
}; 
 
/** 
 * @class C exstend A and B 
 */ 
 
function C () {}

При условии, что A и B не связанны друг с другом. при создании экземпляра pointof.

pointof = new C ('alex', 35)

В консоль должно быть следующие:

console.log(pointof.name)  //'alex' 
console.log(pointof.age)   //35 
pointof.methodA()          //"name:alex" 
pointof.methodB()          //"age:35" 
 
pointof instanceof A       //true 
pointof instanceof B       //true 
pointof instanceof C       //true

Answer 1

В версиях языка раньше ES 2015 (он же ES6, "Harmony") желаемого вами поведения добиться невозможно: если конструкторы A и B никак не связаны друг с другом, то одна из проверок x instanceof A или x instanceof B обязана вернуть false.

Тем не менее, начиная с Harmony появилась возможность переопределять оператор instanceof при помощи Symbol.hasInstance.

Поэтому множественное наследование можно реализовать следующим образом.

Для начала, функция-конструктор должна вызывать оба базовых конструктора:

function C(name, age) {
    A.call(this, name);
    B.call(this, age);
}

Далее, от одного из классов следует отнаследоваться обычным способом:

C.prototype = Object.create(A.prototype);
C.prototype.constructor = C;

От второго класса нужно отнаследоваться вручную:

Object.defineProperties(C.prototype, Object.getOwnPropertyDescriptors(B.prototype));

Однако, эта строчка работает только когда B ни от кого сам не унаследован. Иначе надо честно идти по цепочке прототипов:

var chain = [];
for (let base = B.prototype; base && !base.isPrototypeOf(C.prototype); base = Object.getPrototypeOf(base)) 
    chain.push(base);
chain.reverse(); // Обход должен идти от предка к потомку, а мы прошли наоборот
for (const base of chain)
    Object.defineProperties(C.prototype, Object.getOwnPropertyDescriptors(base));

Теперь в C.prototype есть все свойства из обоих прототипов предков, осталось сделать так чтобы проверка pointof instanceof B работала:

Object.defineProperty(B, Symbol.hasInstance, {
    value: function (obj) { 
        return B.prototype.isPrototypeOf(obj) || C.prototype.isPrototypeOf(obj);
    }
});

Вот весь код вместе:

/** 
 * @class A 
 */ 
function A (name) { 
	this.name = name; 
} 
 
A.prototype.methodA = function(){ 
	console.log("name:" + this.name) 
}; 
 
/** 
 * @class B 
 */ 
function B (age) { 
	this.age  =  age; 
} 
 
B.prototype.methodB = function(){ 
	console.log("age:" + this.age) 
}; 
 
/** 
 * @class C exstend A and B 
 */ 
 
function C(name, age) { 
    A.call(this, name); 
    B.call(this, age); 
} 
C.prototype = Object.create(A.prototype); 
C.prototype.constructor = C; 
{   // Доп. блок чтобы не загрязнять пространство имен переменных временной переменной 
    let chain = []; 
    for (let base = B.prototype; base && !base.isPrototypeOf(C.prototype); base = Object.getPrototypeOf(base))  
        chain.push(base); 
    chain.reverse(); // Обход должен идти от предка к потомку, а мы прошли наоборот 
    for (const base of chain) 
        Object.defineProperties(C.prototype, Object.getOwnPropertyDescriptors(base)); 
} 
 
Object.defineProperty(B, Symbol.hasInstance, { 
    value: function (obj) {  
        return B.prototype.isPrototypeOf(obj) || C.prototype.isPrototypeOf(obj); 
    } 
}); 
 
var pointof = new C ('alex', 35); 
console.log(pointof.name)  //'alex' 
console.log(pointof.age)   //35 
pointof.methodA()          //"name:alex" 
pointof.methodB()          //"age:35" 
 
console.log(pointof instanceof A) //true 
console.log(pointof instanceof B) //true 
console.log(pointof instanceof C) //true

Предупреждение. Решение приведенное выше - не более чем костыль, у него нет нескольких важных свойств. К примеру, там предполагается что все прототипы объекта достижимы по цепочке прототипов - но для класса C это свойство нарушается! То есть такой класс с несколькими предками нарушает принцип композиции.

Ну и манки-патчинг B[Symbol.hasInstance] в таком виде - тоже не самая совместимая вещь. Второе наследование от B затрет результаты первого.

Если по какой-то причине захочется множественного наследования в реальном коде - нужно будет внимательнее отнестись к таким случаям. Например, хранить список "дополнительных" наследников для каждого класса чтобы иметь возможность дополнять проверку...

var s_children = Symbol("children");
B[s_children] = [B, C];
Object.defineProperty(B, Symbol.hasInstance, {
    value: function (obj) { 
        return this[s_children].some(c => c.prototype.IsPrototypeOf(obj));
    }
});

Правда, вариант выше подвержен утечкам памяти, поэтому по-хорошему проверку надо "развернуть"...

var s_extra_prototypes = Symbol("extra_prototypes");
C.prototype[s_extra_prototypes] = [B.prototype];
Object.defineProperty(B, Symbol.hasInstance, {
    value: function (obj) { 
        return this.prototype.IsPrototypeOf(obj) 
          || obj[s_extra_prototypes].some(c => this.prototype.IsPrototypeOf(c));
    }
});    

И таких символов понадобится много.

Возможно, намного проще вместо множественного наследования использовать обертки:

function A (base) {
    return class A extends base {
        constructor(name) {
            this.name = name;
        }
        methodA() {
            console.log("name:" + this.name);
        }
    }
}
function B (base) {
    return class B extends base {
        constructor(age) {
            this.age = age;
        }
        methodB() {
            console.log("age:" + this.age);
        }
    }
}
class C extends A(B(Object)) { }

Правда, instanceof для оберток работать не будет.

Answer 2

по мне так лучше выработать другой подход в написании конструкторов и определению их протоптанных свойств.

 /**
 * @class A
 */
function A (name) {
/*
    *   инициализация методов для конструктора типа A
*/
    SCOPE_mtd0001.call(this.constructor.prototype)
    this.name = name;   
}
/**
 *  конструктор набора методов типа _mtd0001
 */
function SCOPE_mtd0001(){
    this['methodA'] = function(){
        console.log("name:" + this.name)
    };
}
/**
 * @class B
 */
function B(age){
/*
    *   инициализация  методов для конструктора типа B
*/
    SCOPE_mtd0002.call(this.constructor.prototype)
    this.age  =  age;
}
/**
 *  конструктор набора методов типа _mtd0002
 */
function SCOPE_mtd0002(){
    this['methodB'] = function(){
        console.log("age:" + this.age)
    };
}
/**
 * @class C exstend A and B
 */
function C(_name, _age) {
/*
    *   наследование  свойств конструкторов A и B
*/
    A.call(this, _name)
    B.call(this, _age)
/*
    *   инициализация  метода для конструктора типа С
*/  
    this.constructor.prototype["message"] = function(){
        this.methodA()
        this.methodB()
    }
}
var c_obj = new C("федя", 25)
    c_obj.message()
READ ALSO
Не получается распарсить json ответ

Не получается распарсить json ответ

Из ajax запроса в скрипт приходит примерно такой ответ:

254
Как отловить клик на элементе дропдауна, который сделан с использованием selectize

Как отловить клик на элементе дропдауна, который сделан с использованием selectize

Всем приветСобственно, проблема в том, что не получается отловить клик, как и написано в заголовке

237
Не работает setCenter Яндекс карты

Не работает setCenter Яндекс карты

Дано: на странице есть карта с несколькими маркерамиЕсть список ссылок с координатами этих маркеров, по клику отрабатывает onclick=changeCenter(x,y)

319