Московский физико-технический институт
Опубликован: 23.12.2005 | Доступ: свободный | Студентов: 2869 / 253 | Оценка: 4.61 / 4.44 | Длительность: 27:18:00
ISBN: 978-5-9556-0051-2
Лекция 8:

Эмулируем множественное наследование

< Лекция 7 || Лекция 8: 123456 || Лекция 9 >

Многократное множественное наследование и виртуальные базовые классы

Вместо того чтобы сохранять (лишь слегка модифицируя) цепочку __proto__ класса-"множественного наследника", мы сформируем ее заново. Специально для этого мы сохранили массив исходных базовых классов в поле bases, которое завели у конструктора класса-"множественного наследника".

Чтобы воспользоваться этим массивом для нового вызова multipleInherit, нужно научиться добавлять в него (точнее, в его копию) данные о новых виртуальных базовых классах. Это и является основной задачей настоящего параграфа. Ведь добавить данные о невиртуальных базовых классах труда не составит: ничего не надо будет менять в данных остальных базовых классов, порядок следования классов также не важен. Поэтому можно просто собрать данные обо всех новых базовых классах в один массив и воспользоваться методом concat. Но подчеркнем еще раз, что это работает только в том случае, если новые базовые классы не являются виртуальными (или, являясь виртуальными, не встречаются в цепочках __proto__ прежних базовых классов).

Итак, хотелось бы сделать удобные утилиты, которые облегчат процедуру выделения новых виртуальных базовых классов. Что должны делать эти утилиты? Одна из них должна скопировать массив bases (причем, поскольку сам этот массив состоит из субмассивов, эти массивы тоже должны быть скопированы - размещением в скопированный массив ссылок на субмассивы ограничиться нельзя, иначе содержимое исходных субмассивов может быть модифицировано). Другая утилита будет добавлять в скопированный массив один виртуальный базовый класс (для добавления нескольких эту функцию надо будет вызвать повторно). Что конкретно должна делать эта функция? Во-первых, она должна вставить субмассив с данными о новом виртуальном базовом классе в указанное место скопированного массива bases. Во-вторых, в данных о некоторых базовых классах (которые в своей цепочке __protо__ содержат добавляемый класс, который мы хотим сделать виртуальным базовым ) надо указать добавляемый класс в качестве стоп-класса. В принципе, эту часть работы можно было бы автоматизировать. Однако это представляется излишним - реальные иерархии классов крайне редко бывают настолько разветвленными, чтобы программисту было сложно указать классы, являющиеся наследниками данного. С другой стороны, явное указание стоп-классов позволяет программисту проконтролировать, что сам виртуальный базовый класс будет помещен ближе к корню результирующей цепочки __proto__, нежели оборванные указанием стоп-класса субцепочки.

Наконец, третья утилита будет получать в качестве аргумента класс-"множественный наследник". Она станет копировать его массив bases, а затем добавлять туда свой прототип (но только то, что добавлено в прототип непосредственно, а цепочка __proto__, прицепленная к нему, в данном случае не нужна, так как все ее содержимое, фактически, уже есть в массиве bases ). На самом деле в массив bases кладется, конечно, не сам прототип, а конструктор, к которому прототип прицеплен. Но тут важно, что мы кладем именно исходный конструктор, который когда-то был передан первым аргументом в multipleInherit (и затем сохранен в поле constr нового сгенерированного конструктора). То есть надо достать этот самый constr и установить его prototype указывающим на прототип класса-"множественного наследника". А затем (чтобы цеп очка __proto__ не тянулась за прототипом) в качестве стоп-класса указать сам "множественный наследник" и поставить режим "обрыв цепочки после стоп-класса" (то есть установить четвертый элемент субмассива равным true ).

Сейчас мы приведем код, реализующий все эти идеи и тестирующий их пока на строках, а не на классах. Ведь вышеописанным функциям практически все равно, что положить в массив - произвольный класс или строку. (Единственное место, где этот тест не проходит - то, где надо менять prototype у конструктора, записанного в constr. Это место на строках протестировано не будет, о чем есть соответствующие комментарии в коде.) Итак, вот код утилит, о которых мы говорили:

// Нам понадобится функция для рекурсивного копирования
// массивов неизвестной степени вложенности
_global.copyArrayRecursively = function(arr){
	var arrCopy = arr.slice(0, arr.length);
	for (var i=0; i<arr.length; i++){	
	 if (arr[i] instanceof Array){
	  arrCopy[i] = copyArrayRecursively(arr[i]);
	  }
	}
	return arrCopy;
}
// Основная функция для добавления нового виртуального базового
// класса в массив базовых классов, предназначенный для
// передачи в функцию multipleInherit
_global.addVirtualBase = 
function(
	sourceArray, virtualBaseParams, chainsToStop, 
	placeNearClass, placeAfter
)
{
	// Определяем "константы" для обращения к субмассивам
	// аргумента sourceArray (скопировано из функции multipleInherit).
	// В принципе, неплохо бы поместить эти "константы"в _global, 
	// причем завести для них объект-контейнер, чтобы не засорять
	// глобальное пространство имен. Но сейчас мы для наглядности
	// размещаем эти "константы" прямо здесь.
	var baseClassNum = 0, argsFuncNum = 1, 
		stopClassNum = 2, stopInclusiveNum = 3;
	// Поскольку мы будем вносить в массив изменения, надо его 
	// сперва скопировать.
	var sourceArrayCopy = copyArrayRecursively(sourceArray);
	// Останавливаем указанные цепочке на классе virtualBase
	// Проверка выхода из цикла основана на длине неизменного 
	// массива - так надежнее
	for (var i=0; i<sourceArray.length; i++){
	 for (var j=0; j<chainsToStop.length; j++){
	  if (sourceArrayCopy[i][baseClassNum] == chainsToStop[j] || 
            (chainsToStop[j].constr != null && 
            sourceArrayCopy[i][baseClassNum] == chainsToStop[j].constr)
	  ){
	  sourceArrayCopy[i][stopClassNum] = virtualBaseParams[baseClassNum];
	  sourceArrayCopy[i][stopInclusiveNum] = false;
	  }	
	 }
	}
// Вставляем субмассив virtualBaseParams в указанное место
// Проверка выхода из цикла основана на длине неизменного массива - 
	for (var i=0; i<sourceArray.length; i++){
	 if (sourceArrayCopy[i][baseClassNum] == placeNearClass ||
	  (placeNearClass.constr != null && 
	   sourceArrayCopy[i][baseClassNum] == placeNearClass.constr)
	  ){
	  sourceArrayCopy.splice(i + placeAfter, 0, virtualBaseParams);
	  break;
	}
	if (i == sourceArrayCopy.length - 1){
	  sourceArrayCopy.push(virtualBaseParams);
	 }
	}
	return sourceArrayCopy;
}
// Следующая функция извлекает из класса, полученного с помощью 
// функции multipleInherit, массив базовых классов, копирует 
// массив и добавляет в копию сам этот класс. При этом 
// принимаются меры к тому, чтобы за добавляемым классом не 
// последовала вся его цепочка __proto__ (она не нужна, так как 
// все классы из цепочки уже имеются в массиве базовых классов).
_global.extractCompleteArrayOfBases = function(sourceClass, 
	newArgFunction){
// Копируем исходный массив базовых классов (на всякий случай 
// копируем рекурсивно, чтобы ненароком не испортить потом).
	var newBases = copyArrayRecursively(sourceClass.bases);
// В качестве конструктора sourceClass нам не подойдет - 
// в нем слишком много лишнего. Нужно взять исходный
// конструктор, сохраненный в sourceConstr.constr и 
// прикрепить прототип к нему. Надо только учесть, что 
// при тестировании на строках всего этого делать не надо.
	if (!TESTING_WITH_STRINGS){
		sourceClass.constr.prototype = sourceClass.prototype;
	}
// Последний элемент добавляемого массива установлен в true - 
// это значит, что при создании новой цепочки __proto__ при
// помощи формируемого сейчас массива цепочка от sourceClass
// будет оборвана после него, а не перед ним. 
	newBases.unshift(
	[sourceClass.constr, newArgFunction, sourceClass.constr, true]
	);
	return newBases;
}

Имея код перед глазами, удобнее более подробно разобрать, какие аргументы передаются в функции. Функция addVirtualBase принимает пять аргументов:

  1. sourceArray - исходный массив с информацией о базовых классах (состоящий из субмассивов по четыре элемента - класс, функция формирования аргументов, стоп-класс и режим обрыва цепочки).
  2. virtualBaseParams - массив с данными о виртуальном базовом классе (содержит, как обычно, четыре элемента, имеющие тот же смысл, что и элементы субмассивов sourceArray ).
  3. chainsToStop - массив классов, в данные которых нужно внести добавляемый виртуальный базовый класс в качестве стоп-класса.
  4. placeNearClass - класс, перед которым или после которого надо разместить добавляемый виртуальный базовый класс.
  5. placeAfter - булевский аргумент, уточняющий смысл предыдущего аргумента. Если он установлен в false (или undefined ), то виртуальный базовый класс надо размещать в массиве перед классом, указанным в четвертом аргументе. В результате в цепочке __proto__ "множественного наследника" виртуальный базовый класс расположится дальше от корня цепочки, чем четвертый аргумент. Если же placeAfter установлен в true, то виртуальный базовый класс будет в массиве дальше четвертого аргумента (и ближе к корню в цепочке __proto__ ).

Возвращает addVirtualBase модифицированную вышеописанным образом копию массива sourceArray.

Функция extractCompleteArrayOfBases принимает два аргумента: sourceClass - класс-"множественный наследник" и newArgFunction - функцию формирования аргументов для конструктора этого класса при вызове его из нового "множественного наследника". Возвращает extractCompleteArrayOfBases копию массива sourceClass.bases с добавленным в него классом sourceClass (на самом деле, для добавления взят исходный конструктор sourceClass.constr, к нему прицеплен правильный прототип sourceClass.prototype, и, кроме того, в качестве стоп-класса установлен также sourceClass.constr, причем обрыв цепочки будет происходить после стоп-класса).

Давайте посмотрим теперь, как все эти функции работают. Вот тестовый код:

// ---------- Тестовая часть ------------
// Массив и функция, необходимые для тестирования.
// Для предварительного тестирования в исходный массив
// мы можем поместить строки вместо классов.
testBases = 
[
	["firstBaseClass", "firstArgFunc", "firstStopClass", false],
	["secondBaseClass", "secondArgFunc", "secondStopClass", true],
	["thirdBaseClass", "thirdArgFunc", "thirdStopClass", false],
	["fourthBaseClass", "fourthArgFunc", "fourthStopClass", false]
];
// Вспомогательная функция для форматированной печати массива
_global.getArrayString = function(arr){
	var str = "[";
	for (var i=0; i<arr.length; i++){	
		if (arr[i] instanceof Array){
			if (i == 0) str += "\n\t";
			str += getArrayString(arr[i]);
			if (i < arr.length - 1) str += ", \n\t";
			else str += "\n";
		}
		else{
			str += arr[i];
			if (i < arr.length - 1) str += ", ";
		}
	}
	str += "]";
	return str;
}
// Тестируем
newTestArray = addVirtualBase(
	testBases, 
	["virtualBase", "argFunction"], 
	["firstBaseClass", "secondBaseClass"], "thirdBaseClass", false
);
// Отличается от предыдущей функции тем, что вставлять новый 
// базовый класс надо после указанного класса, а не перед ним
newTestArray2 = addVirtualBase(
	testBases, 
	["virtualBase", "argFunction"], 
	["firstBaseClass", "secondBaseClass"], "thirdBaseClass", true
);
// Смотрим, что получится, если класс, после которого нужно
// вставлять virtualBase, отсутствует в массиве (если все
// работает правильно, то он должен попасть в самый конец).
newTestArray3 = addVirtualBase(
	testBases, 
	["virtualBase", "argFunction"], 
	["firstBaseClass", "secondBaseClass"], "aaa", false
);
// Выводим содержимое получившихся массивов
trace("--------- testBases ----------");
trace(getArrayString(testBases));
trace("\n-------- newTestArray ---------");
trace(getArrayString(newTestArray));
trace("\n-------- newTestArray2 ---------");
trace(getArrayString(newTestArray2));
trace("\n-------- newTestArray3 ---------");
trace(getArrayString(newTestArray3));
// Теперь тестируем функцию extractCompleteArrayOfBases
// Для этого пока что снова сымитируем классы при помощи строк
TESTING_WITH_STRINGS = true;
TestClass = new String("TestClass");
TestClass.bases = testBases;
TestClass.constr = "TestClassOriginalConstr";
extractedArray = extractCompleteArrayOfBases(TestClass, "newArgFunc");
trace("\n-------- extractedArray ---------");
trace(getArrayString(extractedArray));
// Тестируем корректность работы функции addVirtualBase на массиве,
// сделанном при помощи extractCompleteArrayOfBases
newTestArray4 = addVirtualBase(
	extractedArray, 
	["virtualBase", "argFunction"], 
	[TestClass, "secondBaseClass"], TestClass, true
);
trace("\n-------- newTestArray4 ---------");
trace(getArrayString(newTestArray4));

Подчеркнем еще раз, что этот этап тестирования функций - предварительный, поэтому здесь вместо классов используются строки (чтобы лучше было видно, что происходит с массивами).

Добавив этот тестовый код к вышеописанным утилитам и запустив получившуюся программу на выполнение, получим в консоли следующее:

--------- testBases ----------
[
	[firstBaseClass, firstArgFunc, firstStopClass, false], 
	[secondBaseClass, secondArgFunc, secondStopClass, true], 
	[thirdBaseClass, thirdArgFunc, thirdStopClass, false], 
	[fourthBaseClass, fourthArgFunc, fourthStopClass, false]
]
-------- newTestArray ---------
[
	[firstBaseClass, firstArgFunc, virtualBase, false], 
	[secondBaseClass, secondArgFunc, virtualBase, false], 
	[virtualBase, argFunction], 
	[thirdBaseClass, thirdArgFunc, thirdStopClass, false], 
	[fourthBaseClass, fourthArgFunc, fourthStopClass, false]
]
-------- newTestArray2 ---------
[
	[firstBaseClass, firstArgFunc, virtualBase, false], 
	[secondBaseClass, secondArgFunc, virtualBase, false], 
	[thirdBaseClass, thirdArgFunc, thirdStopClass, false], 
	[virtualBase, argFunction], 
	[fourthBaseClass, fourthArgFunc, fourthStopClass, false]
]
-------- newTestArray3 ---------
[
	[firstBaseClass, firstArgFunc, virtualBase, false], 
	[secondBaseClass, secondArgFunc, virtualBase, false], 
	[thirdBaseClass, thirdArgFunc, thirdStopClass, false], 
	[fourthBaseClass, fourthArgFunc, fourthStopClass, false], 
	[virtualBase, argFunction]
]
-------- extractedArray ---------
[
	[TestClassOriginalConstr, newArgFunc, TestClassOriginalConstr, true], 
	[firstBaseClass, firstArgFunc, firstStopClass, false], 
	[secondBaseClass, secondArgFunc, secondStopClass, true], 
	[thirdBaseClass, thirdArgFunc, thirdStopClass, false], 
	[fourthBaseClass, fourthArgFunc, fourthStopClass, false]
]
-------- newTestArray4 ---------
[
	[TestClassOriginalConstr, newArgFunc, virtualBase, false], 
	[virtualBase, argFunction], 
	[firstBaseClass, firstArgFunc, firstStopClass, false], 
	[secondBaseClass, secondArgFunc, virtualBase, false], 
	[thirdBaseClass, thirdArgFunc, thirdStopClass, false], 
	[fourthBaseClass, fourthArgFunc, fourthStopClass, false]
]

На что нужно обратить внимание в этой распечатке? Во-первых, заметьте, что в исходном массиве четвертый элемент второго субмассива установлен в true. В дальнейшем он будет изменен (в копиях). Далее, смотрим на результаты работы функции addVirtualBase. В первом случае данные для virtualBase были добавлены перед классом thirdBaseClass, во втором (когда аргумент placeAfter был установлен в true ) - после. В обоих случаях базовым классам firstBaseClass и secondBaseClass в качестве стоп-класса был установлен virtualBase, а режим обрыва цепочки был установлен в "обрыв перед стоп-классом" - четвертые элементы субмассивов равны false. Последнее можно заметить на примере данных для secondBaseClass, где ранее четвертый элемент субмассива был равен true.

Теперь посмотрим на newTestArray3. При его формировании аргумент placeNearClass был установлен в " aaa ", а такого значения первых элементов субмассивов у нас нет. Это эквивалентно тому, что данный аргумент вовсе не был задан. В результате данные virtualBase попали в самый конец массива. Если virtualBase - самодостаточный класс, цепочка __proto__ которого не оборвана указанием стоп-классов (и который, соответственно, не нуждается в том, чтобы ближе к корню новой цепочки "множественного наследника" стояли необходимые для его работы базовые классы) - можно не указывать последние два аргумента при вызове addVirtualBase.

Временно оставив функцию addVirtualBase, посмотрим, как отработала функция extractCompleteArrayOfBases (создавшая в результате extractedArray ). Она добавила в начало массива следующий субмассив:

[TestClassOriginalConstr, newArgFunc, TestClassOriginalConstr, true]

Такой субмассив сообщает, что нужно взять класс, заданный функцией-конструктором TestClassOriginalConstr и оборвать его цепочку __proto__ сразу после него самого (точнее, после его непосредственного прототипа). А именно это нам и надо.

И, наконец, взглянем на то, как работает функция addVirtualBase на массиве, созданном при помощи extractCompleteArrayOfBases. В данном случае важно, что в качестве класса, около которого надо разместить виртуальный базовый, передан как бы "множественный наследник". А ведь функция extractCompleteArrayOfBases вместо "множественного наследника" помещает в возвращаемый массив его исходный конструктор (с подправленным прототипом). Чтобы учесть этот случай, мы поместили в addVirtualBase соответствующую проверку: сравнение идет не только с переданным классом, но и с полем constr, если оно у переданного конструктора найдется. По массиву newTestArray4 видно, что эта мера принесла свои плоды. Массив с данными виртуального базового класса вставлен туда, куда нужно. И стоп-класс для TestClassOriginalConstr заменен (вообще-то последнее имело смысл делать только в тестовых целях, ибо функция extractCompleteArrayOfBases нарочно устанавливает "множественному наследнику" в качестве стоп-класса его же самого - ранее мы обсуждали, зачем это делается).

Ну что ж, предварительное тестирование наших утилит на строках вместо классов нас удовлетворило, давайте теперь устроим им настоящую проверку. Вот код, который создает класс с использованием свеженаписанных утилит.

stop();
// Делаем класс, аналогичный classDerDer, но без повторяющегося
// базового класса cn1. Теперь мы сделаем cn1 виртуальным. 
// Для этого в массив базовых классов надо отдельно добавить
// класс cy, отдельно - всю цепочку от cy до classDer3 (считаем
// по направлению к корню; в данном случае цепочка только из класса
// cy и состоит). И отдельно добавляем все базовые классы от 
// classDer3, включая его самого (пользуемся здесь утилитой 
// extractCompleteArrayOfBases).
newBases = 
	[[cy, function(){return [arguments[2]];}]].concat(
		[[cd4, function(){return arguments;}, classDer3]].concat(
			extractCompleteArrayOfBases(
				classDer3, function(){return arguments;}
			)
		)
	);
	
// Добавляем к полученному массиву базовых классов класс cn1
// в качестве виртуального базового класса
newBases = addVirtualBase(
	newBases, 
	[cn1, function(){return [arguments[2] + arguments[2], 
	   arguments[1] + arguments[1]];}], 
	[cy, cn2]
);
// Массив готов, делаем класс. При этом не забываем, что в качестве
// системного базового класса classDer3 у нас был Array, 
// его нужно указать в multipleInherit, поскольку в массиве
// newBases системный базовый класс задать нельзя было.
classDerWithVirt = multipleInherit(
  function(a, b, c){
   trace("constrOfDerivedWithVirtual: " + a + " | " + b + " | " + c);
	},
	newBases,
	[Array, function(){super(arguments[1], arguments[2]);}]);
// И любуемся на окончательный результат всех наших трудов
trace("********************************");
objectDerWithVirt = new classDerWithVirt
  ("FirstArg", "SecondArg", "ThirdArg");
trace("-----------------");
trace("objectDerWithVirt = " + objectDerWithVirt);
dumpObj(objectDerWithVirt, "objectDerWithVirt");

Поместим этот код (равно как и код используемых здесь утилит, приведенный ранее) вслед за предыдущим кодом и тестами из этой лекции (тесты нужны, поскольку мы используем определенные в них классы). После его выполнения получим в консоли следующее.

********************************
constr cn1: ThirdArgThirdArg | SecondArgSecondArg
constr cn2: SecondArg | SecondArg
constr cx: SecondArg
constr cn3: SecondArg | ThirdArg
constr cm2: FirstArg
constrOfDerived: FirstArg | SecondArg | ThirdArg
constr cd4: FirstArg | SecondArg | ThirdArg
constr cy: ThirdArg
constrOfDerivedWithVirtual: FirstArg | SecondArg | ThirdArg
-----------------
objectDerWithVirt = SecondArg,ThirdArg
==========================================================
:::::::::::  objectDerWithVirt  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
1 : ThirdArg
0 : SecondArg
length <hidden>: 2
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: 
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  objectDerWithVirt.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: _cn1,_cn1
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  objectDerWithVirt.__proto__.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
isACopy : THAT'S A COPY
length <hidden>: 2
0 : _cn1
1 : _cn1
cy_f : [type Function]
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: ,
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::: objectDerWithVirt.__proto__.__proto__.__proto__ :::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
isACopy : THAT'S A COPY
length <hidden>: 2
0 : \undefined
1 : \undefined
cd4_f : [type Function]
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: 
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  objectDerWithVirt.__proto__.__proto__.
__proto__.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
isACopy : THAT'S A COPY
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: ,,,
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  objectDerWithVirt.__proto__.__proto__.__proto__.
__proto__.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
isACopy : THAT'S A COPY
length <hidden>: 4
cm2_f : [type Function]
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: _cn2_cn1,_cn2_cn1
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  objectDerWithVirt.__proto__.__proto__.__proto__.__
proto__.__proto__
.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
isACopy : THAT'S A COPY
length <hidden>: 2
0 : _cn2_cn1
1 : _cn2_cn1
cn3_f : [type Function]
cn3_g : [type Function]
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: _cn2_cn1,_cn2_cn1
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  objectDerWithVirt.__proto__.__proto__.__proto__.
__proto__.__proto__.__proto__.
__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
isACopy : THAT'S A COPY
length <hidden>: 2
0 : _cn2_cn1
1 : _cn2_cn1
cx_f : [type Function]
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: _cn1,_cn1
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  objectDerWithVirt.__proto__.__proto__.__proto__.
__proto__.__proto__.__proto__.
__proto__.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
isACopy : THAT'S A COPY
length <hidden>: 2
0 : _cn1
1 : _cn1
cn2_f : [type Function]
cn2_g : [type Function]
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: 
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  objectDerWithVirt.__proto__.__proto__.__proto__.__
proto__.__proto__.
__proto__.__proto__.__proto__.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
isACopy : THAT'S A COPY
length <hidden>: 0
cn1_f : [type Function]
cn1_g : [type Function]
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: 
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  objectDerWithVirt.__proto__.__proto__.__
proto__.__proto__.__
proto__.
__proto__.__proto__.__proto__.__proto__.__
proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
sortOn <hidden>: [type Function]
reverse <hidden>: [type Function]
sort <hidden>: [type Function]
toString <hidden>: [type Function]
splice <hidden>: [type Function]
join <hidden>: [type Function]
slice <hidden>: [type Function]
unshift <hidden>: [type Function]
shift <hidden>: [type Function]
concat <hidden>: [type Function]
pop <hidden>: [type Function]
push <hidden>: [type Function]
__proto__ <hidden>: [object Object]
constructor <hidden>: [type Function]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::  objectDerWithVirt.__proto__.__proto__.__proto__.__
proto__.__proto__.
__proto__.__proto__.__proto__.__proto__.__
proto__.__proto__  ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__proto__ <hidden>: \undefined
toLocaleString <hidden>: [type Function]
isPropertyEnumerable <hidden>: [type Function]
isPrototypeOf <hidden>: [type Function]
hasOwnProperty <hidden>: [type Function]
toString <hidden>: [type Function]
valueOf <hidden>: [type Function]
addProperty <hidden>: [type Function]
unwatch <hidden>: [type Function]
watch <hidden>: [type Function]
constructor <hidden>: [type Function]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
=========================================================

Чтобы убедиться, что все в порядке, вспомним, какова последовательность базовых классов в цепочке __proto__ класса classDer3. Эта последовательность выглядит так: classDer3->cm2->cn3->cnx->cn2->cn1->Array->Object (порядок довольно странный, и мы в свое время немало потрудились, чтобы сделать его таким). Как видно из приведенной распечатки, последовательность базовых классов в цепочке класса classDerWithVirt такова: cy->cd4->classDer3->cm2->cn3->cnx->cn2->cn1->Array->Object, при этом класс cn1 мы сделали как бы виртуальным базовым, что проявляется в наличии у него специфических аргументов конструктора, полученных из аргументов, передаваемых в конструктор classDerWithVirt. Аргументы эти приготовлены функцией, специально для этого сделанной нами в последнем тестовом пр имере (и переданной в утилиту addVirtualBase ). В самом начале данной распечатки виден результат работы конструктора класса cn1 с этими аргументами: это строка

constr cn1: ThirdArgThirdArg | SecondArgSecondArg

В качестве базового класса в классах cy и classDer3 класс cn1 не имел подобной функции подготовки аргументов, удваивающей два последних аргумента, да еще и размещающей их в обратном порядке.

Осталось отметить, что нам замечательно удалось не только добавление виртуального базового класса, но и корректное использование класса Array в качестве системного, чего не получалось при простом повторном использовании multipleInherit. Таким образом, созданный с помощью новых утилит класс classDerWithVirt, который, фактически, унаследован от тех же классов, что и classDerDer, получился гораздо более аккуратным и корректным.

Итак, можно констатировать, что наша попытка эмулировать при помощи Флэш МХ основные свойства множественного наследования увенчалась весьма убедительным успехом.

< Лекция 7 || Лекция 8: 123456 || Лекция 9 >