Основания
для оптимизма
Отсутствие
множественного наследования
- это одна из тех вещей, которая сильно раздражает
программистов, переходящих с С++ на Java. (Есть еще
довольно объемистый список таких вещей. Например,
отсутствие перегрузки операторов и типизованных
коллекций. А также отсутствие параметров по умолчанию в
функциях и т.д.) К счастью, множество недостатков
ActionScript первой версии слегка компенсируется тем,
что какие-то вещи можно в той или иной степени
эмулировать. Например, параметры по умолчанию
эмулировать совсем просто:
function defArgExample(_text,
_prefix){
if (_prefix != undefined) trace(_prefix + ": " + _text);
else trace(_text);
return;
}
defArgExample("building 8");
defArgExample("building 8", "address");
Этот код
выводит
building 8
address: building 8
Или можно
сделать даже так:
function
defArgExample1(_text){
var tmpString;
if (arguments.length > 1){
for (var i=arguments.length; -i>=1;) tmpString += arguments[i] + ":
";
trace(tmpString + _text);
}
else trace(_text);
return;
}
defArgExample1("building 9");
defArgExample1("building 9", "address");
defArgExample1("building 9", "address", "official
data");
в результате
чего получим:
building 9
address: building 9
official data: address: building 9
Вот и все -
параметры по умолчанию готовы. Ранее вы могли видеть,
как были эмулированы статические и приватные поля и
методы. Так что возможности для эмуляции "всего на свете"
интерпретатор ActionScript предоставляет весьма богатые.
А это, в свою очередь, дает нам некоторые основания для
оптимизма и в задаче об эмуляции
множественного наследования.
Переходим к делу
Сразу
отметим: авторы осознают, что проекты такого размера,
которые требуют полноценного
множественного наследования, на Флэш МХ вряд ли
будут делаться. Хотя: как знать? Так или иначе, данная
лекция является, скорее, демонстрацией возможностей
языка ActionScript (а также и ECMA-Script, который,
напоминаем, в виде JavaScript используется в браузерах).
И хотя полноценное множественное
наследование вам, скорее всего, не понадобится,
возможно некоторые приемы, которыми мы сейчас
воспользуемся, когда-нибудь используете и вы.
Теперь
поставим задачу более строго. Конечно, не составит
никакого труда просто скопировать все методы и поля из
одного класса в другой с помощью кода наподобие
следующего.
for (var name in sourceObj){
destObj[name] = sourceObj[name];
}
При этом
надо взять в качестве sourceObj
не что иное, как
sourceClass.prototype; аналогично, в качестве
destObj берем
destClass.prototype. Более
того, видимо, именно так и следует поступить, если все,
что вам нужно от одного из базовых классов - это его
поля и методы. Совсем другая ситуация возникает, если
серьезная инициализационная работа производится в
конструкторах, которые в свой черед вызывают базовые
конструкторы и т.д. В таком случае
цепочку __proto__ "сворачивать",
копируя все методы, нельзя. Но возникает другая идея: а
нельзя ли скопировать эту цепочку целиком? Скопировать
каждую из цепочек (для каждого из базовых классов), а
затем составить эти "поезда" друг за другом!
Идея
хорошая, но давайте посмотрим, какие трудности нам
придется преодолеть в процессе ее реализации.
Во-первых,
надо ли копировать цепочки? Нельзя ли просто установить
ссылку __proto__ самого
последнего из базовых классов (для определенности будем
дальше говорить "самого верхнего класса") одной из
цепочек так, чтобы она указывала на самый нижний класс в
другой цепочке? К сожалению, это невозможно, ибо
приведет к "порче" ряда классов из первой цепочки - у
них вдруг появятся базовые, которых отродясь не бывало.
А ведь эти классы запросто могут использоваться безо
всякого отношения к нашему
множественному наследованию. Конечно, лишние
базовые классы - это не то же самое, что недостающие;
поведение изменится лишь в небольшом числе специальных
случаев; но все равно неприятно. Так что копировать
нужно.
Во-вторых,
самый первый из конструкторов каждой субцепочки (так мы
будем называть всю цепочку одного из базовых классов
нашего "множественного наследника") надо будет вызывать
по отдельности. И специальным образом формировать для
каждого из таких конструкторов массив аргументов.
Впрочем, здесь нет ничего необычного, ведь примерно то
же самое происходит при вызове базовых конструкторов в
С++.
В-третьих,
как только мы начинаем работать с
множественным наследованием, немедленно возникает
проблема дублирования базовых классов. Что делать, если
один и тот же класс повторяется в двух различных
субцепочках? Один из естественных ответов на этот вопрос
- не делать ничего. Поскольку у нас в субцепочках будет
все-таки не сам класс, а его копии, такая ситуация ничем
особенным не грозит. Однако есть два неудобства: а)
занимается лишнее место в памяти и б) конструкторы этого
класса в разных субцепочках могут делать вещи, которые
конфликтуют друг с другом. В С++ обе эти проблемы
решаются введением виртуальных
базовых классов. Нам нужно будет предусмотреть
механизм, который позволит иметь вместо двух совпадающих
классов в разных субцепочках один класс, причем его
конструктор будет вызываться прямо из конструктора "множественного
наследника", как это делается для
виртуальных базовых классов в С++.
В-четвертых,
нужно уметь как следует работать с
системными базовыми классами. Скажем, к последней
субцепочке можно прикрепить сам системный класс, а не
его копию (поскольку к нему в "хвост" уже ничего
крепиться не будет). Наконец, если в разных субцепочках
- разные системные базовые классы,
надо уметь правильно их скопировать, включая скрытые
поля и методы. Для этого, конечно, придется на короткое
время все скрытое "приоткрыть", однако потом нужно будет
аккуратно спрятать все обратно.
Как это будет работать
Теперь мы
приступим к описанию функции
multipleInherit, которая сможет решить все
вышеописанные проблемы. Эта функция будет принимать
следующие аргументы.
- Функцию, которая
будет "прообразом" конструктора для создаваемого
класса.
- Массив базовых
классов (причем массив двумерный, о чем речь пойдет
далее).
- Третий аргумент -
необязательный. Он представляет собой массив, в
котором хранится системный
базовый класс (его нужно прикрепить к самой
последней субцепочке - без копирования), а также
функция вызова его конструктора, передающая туда
необходимые аргументы.
Возвращать
функция multipleInherit
станет сгенерированный в ней объект-функцию, который и
будет готовым конструктором нового класса. В поле
prototype этого
объекта-функции нужно будет заводить новые методы
производного класса.
Поговорим
подробнее об аргументах
multipleInherit. Первый из них (функцию) мы
назвали прообразом конструктора, поскольку из настоящего
конструктора (его генерирует
multipleInherit) для того, чтобы произвести
инициализацию, будет вызвана именно эта функция. Поэтому
в рассматриваемой функции вы должны предусмотреть все
необходимые инициализационные действия (за исключением
вызова конструкторов базовых классов - для этого мы
будем применять отдельный механизм).
Второй
аргумент - массив базовых классов. Однако, массив этот
двумерный, поскольку для каждого базового класса нужно
указать еще некоторую дополнительную информацию. Таким
образом, каждый базовый класс описывается своим массивом,
в котором может быть до четырех элементов. Набор таких
массивов, объединенных в один большой массив - это и
есть второй аргумент. О смысле элементов маленьких
массивов мы скажем чуть позже.
Наконец,
третий аргумент имеет смысл использовать, если скажем,
некие два класса (из всей массы классов, от которых вы
наследуетесь) являются наследниками одного и того же
системного класса. Тогда лучшее, что вы можете сделать -
прицепить этот системный класс в конце
цепочки
__proto__, причем исходный
класс, а не его копию. (В результате получается нечто
вроде виртуального базового класса.)
Поскольку в этом случае непонятно, с какими аргументами
вызывать конструктор системного класса - с теми, что
передаются через цепочку первого базового класса, или
второго - необходимо предусмотреть функцию для генерации
правильного набора аргументов конструктора. Эта функция
имеет еще одну особенность, о которой мы скажем далее.
Сам системный класс и эту функцию при вызове
multipleInherit надо
поместить в массив (в нем, таким образом, будет всего
два элемента), и этот массив и передать в качестве
третьего аргумента в
multipleInherit.
Чтобы
окончательно понять, как именно будет вызываться функция
multipleInherit, нам
остается проанализировать, из чего состоят маленькие
массивы, которые являются составными частями второго
аргумента - списка базовых классов. Итак, первым
элементом такого массива является сам базовый класс.
Вторым - функция, которая подготавливает аргументы для
его конструктора. Ей будет передан тот же список
аргументов, что и конструктору "множественного
наследника" при создании экземпляра этого класса. А
вернуть такая функция должна массив аргументов, которые
будут переданы конструктору базового класса, к коему "приписана"
эта функция. (И хотя эта последняя похожа на аналогичную
функцию, которую мы делаем для
системного базового класса, они все же
существенно отличаются - дальше мы поговорим об этом
подробнее). Третий - необязательный - аргумент - это
класс, на котором нужно остановиться при копировании
субцепочки предков данного класса. Скажем, если мы хотим
эмулировать виртуальный базовый
класс, субцепочки всех его наследников надо
оборвать в тот момент, когда мы доберемся до класса,
который хотим сделать виртуальным
базовым (копируем мы цепочки, естественно,
начиная "снизу" - с производных классов). И, кроме того,
нужно будет в списке базовых классов указать тот,
который мы хотим сделать
виртуальным базовым (причем указать после его
производных). Мы увидим, как все это работает, разбирая
примеры.
По поводу
третьего элемента маленького массива может возникнуть
вопрос: где мы, собственно останавливаемся при
копировании? То есть включаем в цепочку указанный класс,
или же нет? Оказывается, полезными в разных случаях
будут оба варианта. Поэтому четвертый элемент маленького
массива и будет указывать, как нам поступать: если он
равен true, то указанный
класс в цепочку включаем, если
false - нет.
Проблемы
По ходу
дела нам придется преодолеть ряд специфических
трудностей. Трудность первая: мы не можем вызывать
конструкторы базовых классов напрямую, поскольку это не
позволит оборвать цепочку вызовов
super() в нужном месте. Поэтому, копируя
цепочки
__proto__, мы заменяем
конструкторы, вызывая из них старые при помощи
apply. (И если вам кажется,
что это может не сработать, вы правы: с этим связана
вторая проблема, более серьезная. Но пойдем по порядку).
Так вот, нам нужно сообразить, где хранить ссылки на эти
новые конструкторы. Конечно, для этого есть стандартное
поле constructor; но если
речь идет об "основных" базовых классах, из которых мы и
делаем нашего "множественного наследника", то к их новым
конструкторам необходимо иметь моментальный доступ из
готового конструктора "множественного наследника" (поскольку
именно оттуда мы будем их вызывать). Так что для них
придется создать отдельный массив.
Трудность
вторая, по сравнению с которой первая кажется пустячной:
вызывая конструкторы базовых классов как функции, мы
делаем это с помощью кода наподобие
baseConstr.apply(this,
baseArguments). С одной стороны, никакого выбора
у нас нет: действительно, базовый конструктор должен
подействовать на this и
никак иначе. С другой стороны, при вызове
super() в базовом
конструкторе мы оказываемся в двусмысленной ситуации:
ведь super вызывает
функцию-конструктор, записанную в
__constructor__ у
прототипа текущего класса; а текущим классом мы сами
указали this, а вовсе не
тот базовый, конструктор которого вызывали. Поначалу
возникает впечатление, будто бы трудность эта (вызванная
тем, что мы пренебрегли системными средствами и вызываем
конструкторы "вручную") непреодолима. Затем появляется
слабая надежда: записать в
this.__proto__. __constructor__
ссылку на ту функцию-конструктор, которая нам нужна,
перед вызовом super(). Как
ни удивительно, это срабатывает, и мы можем двигаться
дальше.
Трудность
третья: вызвать конструктор
системного базового класса аналогично остальным
конструкторам нам не удастся. Это связано именно с тем,
что конструкторы большинства системных классов (вроде
Array или
String) работают немного
по-другому, чем обычные функции-конструкторы. В
частности, они одновременно являются и
функциями-операторами для преобразования типа. Видимо,
потому-то сии функции определяют, вызваны ли они через
new или непосредственно (в
принципе, определить это довольно просто: при вызове из
new значение arguments.caller
равно null). Так или
иначе, вызов конструкторов системных классов через
apply не срабатывает
корректно. Трудность кажется непреодолимой, однако нас
снова выручит искусственный прием. Нужно лишь вспомнить,
что наследоваться от системного класса обыкновенным
образом вполне возможно. А это значит, что вызов его
конструктора через super
должен работать (несмотря на то, что
arguments.caller при
вызове через super выдает
уже не null; так что здесь
вступают в игру какие-то дополнительные встроенные
механизмы). Итак, каким бы образом ни работал вызов
конструктора системного базового
класса через super,
нам нужно воспользоваться именно этим методом. Но ведь
настраивать ссылку на "текущий базовый конструктор" мы
уже умеем! Так что необходимо сделать следующее.
Функция, формирующая аргументы для
системного базового класса, должна не возвращать
массив с ними, а выполнять вызов наподобие
super(arg1, arg2). А перед
вызовом этой функции мы установим ссылку
this.__proto__.__constructor__
указывающей на системный базовый
класс. Вот пример, который подтвердит, что такой
пр ием сработает. Выполним следующий код:
class1 = function(arg){this.a
= 100; this.b = arg;}
class2 = function(argClass, otherArgsArray){
argClass.apply(this, otherArgsArray);
}
func = function(someArgsArray){
super(someArgsArray[0], someArgsArray[1]);
}
class3 = function(argClass, otherArgsArray){
this.__proto__.__constructor__ = argClass;
func.apply(this, [otherArgsArray]);
}
b = new class2(class1, [1000]);
c = new class2(Array, [111, 222]);
b1 = new class3(class1, [1000]);
c1 = new class3(Array, [111, 222]);
Здесь
"базовым классом" являются по очереди
class1 и
Array, а "производными" -
class2 и
class3. Мы пишем термины
"базовый" и "производный" в кавычках, поскольку
цепочку
__proto__ мы здесь не
создаем, а лишь проверяем, как работают разные
нестандартные способы вызова базовых конструкторов. При
этом конструктор "базового класса" в
class2 вызывается при
помощи apply, а в
class3 - при помощи
вспомогательной функции, внутри которой стоит вызов
super(). К вспомогательной
функции мы здесь прибегаем, чтобы создать "условия,
приближенные к боевым", то есть сделать код максимально
похожим на тот, что будет использоваться при реализации
множественного наследования.
Итак, запустив этот код на исполнение, а затем нажав
Ctrl+Alt+V (вывод переменных), получим:
Level #0:
Variable _level0.$version = "WIN 6,0,21,0"
Variable _level0.class1 = [function
'__constructor__']
Variable _level0.class2 = [function]
Variable _level0.func = [function]
Variable _level0.class3 = [function] {
prototype:[object #5, class 'Object'] {
__constructor__:[function '__constructor__']
}
}
Variable _level0.b = [object #7] {
a:100,
b:1000
}
Variable _level0.c = [object #8] {}
Variable _level0.b1 = [object #9] {
a:100,
b:1000
}
Variable _level0.c1 = [object #10] [
0:111,
1:222
]
Отсюда
видно, что при использовании в качестве базового класса
созданного нами class1
работают оба способа (вызов и через
apply, и через
super). А вот для
системного класса Array
подходит только последний способ.
Наконец,
при написании функции, реализующей
множественное наследование, будет полезно сделать
некоторые приготовления на будущее. Далее мы поговорим о
том, какие могут возникнуть у пользователя пожелания,
когда он соберется наследоваться от сделанного при
помощи multipleInherit
класса. Особенно если это наследование в свою очередь
будет множественным.
Оказывается, чтобы этот процесс был наиболее удобным,
полезно сохранить ссылку на массив базовых классов. Что
мы и сделаем, прикрепив ссылку в качестве поля к
возвращаемому из multipleInherit
готовому конструктору.
Код вспомогательных
функций
Итак, пора
заканчивать разговоры и разбирать код. Далее приведены
подробно откомментированные функции, с помощью которых
реализуется множественное
наследование. Начинаем с функции, которая
реализует копирование. Сделаем сразу функцию, которая
может работать в двух режимах: с копированием скрытых
полей и методов и без него. (Для
множественного наследования мы будет использовать
первый из них.) Вот код этой функции.
/*=======================================================*
* Функция copyObjectExt - вспомогательная функция
для копирования *
*=======================================================*/
// Главная особенность функции copyObjectExt - флаг
copyWithHidden.
// Если он установлен в false, то копируются все
поля и методы,
// кроме скрытых.
// Если же установить этот флаг в true, то
копируются и скрытые
// зметоды, а исключением __proto__, constructor и
// __constructor__, которые мы устанавливаем
вручную. После
// копирования те методы, что ранее были скрыты от
for...in,
// скрываются обратно.
_global.copyObjectExt = function(sourceObj, destObj,
copyWithHidden){
// Если объект, в который нужно все копировать,
// не задан, он создается.
if (destObj == null) destObj = new Object();
if (!copyWithHidden){
// Копируем нерекурсивно - вложенные объекты в
прототипах,
// как правило, не используются. Если они вам
нужны -
// поставьте здесь рекурсивное копирование.
// Мы, правда, далее заводим поля у
обектов-функций,
// выполняющих роль конструктора. Но конструкторы
мы здесь
// не копируем вовсе.
for (var name in sourceObj){
// Если защита снята заранее,
все поля будут видны,
// но копировать надо не все.
if (name != "__proto__" &&
name !=
"constructor" &&
name !=
"__constructor__") destObj[name] = sourceObj[name];
}
// В некоторых случаях при отладке будет полезно
узнать,
// что рассматриваемый объект - копия.
if (DEBUG) destObj["isACopy"] = "THAT'S A COPY";
}
else{
// Копируем сначала открытые
поля, чтобы запомнить,
// какие были открыты.
var tempObj = copyObjectExt(sourceObj, null,
false);
// Снимаем защиту со всех полей
ASSetPropFlags(sourceObj, null, 0, 1);
// Копируем все поля
copyObjectExt(sourceObj, destObj, false);
// Ставим защиту обратно:
сначал на все поля вообще
ASSetPropFlags(sourceObj, null, 1);
ASSetPropFlags(destObj, null, 1);
// Потом открываем те поля, что
должны быть открыты
for (var name in tempObj){
ASSetPropFlags(sourceObj, name,
0, 1);
ASSetPropFlags(destObj, name,
0, 1);
}
}
return destObj;
}
Для того
чтобы посмотреть, что получается в результате работы
этой функции копирования, нам придется усовершенствовать
функцию dumpObj, которой
мы пользовались раньше. В новом варианте рядом с именем
функции (поля) будет выводиться комментарий о том, что
она является скрытой (если это действительно так). Если
функция скрытой не является, комментария не будет
никакого. Код новой функции
dumpObj таков:
// Печатаем имя и значение
поля, при этом предпринимаем усилия,
// чтобы null и undefined преобразовывались в
"\null" и
// "\undefined", а не в пустую строку.
_global.printField = function(name, value, comment){
if (value === undefined) value = "\\undefined";
if (value === null) value = "\\null";
trace(name + " " + comment + ": " + value);
}
// Печатаем все, что доступно функции for...in.
// Если передана информация об открытых и
неопределенных полях
// (то есть tempObj и undefinedList), используем ее,
добавляя,
// когда нужно, комментарий "<hidden>" к имени поля.
_global.doPrintFields =
function(obj, str, tempProto, tempObj, undefinedList)
{
trace("::::::::::: " + str + " ::::::::::::");
trace("::::::::::::::::::::::::::::::::::::::::::::::::");
for (var name in obj){
// Принимаем меры для того,
чтобы наши действия
// с обнулением __proto__ не
отражались на выводимой
// информации.
var comment = "";
if (tempObj != null){
if (
tempObj[name
+ "_notInObjectSuffix"] === undefined
&&
!undefinedList[name + "_notInObjectSuffix"]
){
comment =
"<hidden>";
}
}
if (name == "__proto__") printField(name,
tempProto,
comment);
else printField(name, obj[name], comment);
}
trace("::::::::::::::::::::::::::::::::::::::::::::::::");
}
// Чтобы увидеть скрытые поля, снимаем с них защиту.
// Попутно запоминаем, какие поля были открытыми и
// в каких было записано undefined, хотя они и существовали
_global.printFieldsByForIn = function(obj, str,
tempProto, dontRecoverHidden){
if (dontRecoverHidden){
doPrintFields(obj, str, tempProto);
}
else{
// Копируем сначала открытые
поля, чтобы запомнить,
// какие были открыты
var tempObj = new Object();
var undefinedList = new Object();
var tempWOSuffixes = new Object();
for (var name in obj){
// Добавляем суффиксы, чтобы не
перепутать с функциями
// Object, которые в tempObj,
конечно, есть.
// В отличие от случая с
функцией копирования,
// пренебрегать этим нельзя,
потому что до Object'а мы
// все равно доберемся, причем
именно когда он будет
// в "незащищенном" состоянии.
tempObj[name +
"_notInObjectSuffix"] = obj[name];
tempWOSuffixes[name] =
obj[name];
if (obj[name] === undefined)
undefinedList[name + "_notInObjectSuffix"] = true;
}
// Снимаем защиту со всех полей
ASSetPropFlags(obj, null, 0, 1);
// Выводим содержимое полей
doPrintFields(obj, str, tempProto, tempObj,
undefinedList);
// Ставим защиту обратно: сначалa на все поля
вообще
ASSetPropFlags(obj, null, 1);
// Потом открываем те поля, что должны быть
открыты
for (var name in tempWOSuffixes){
if (
tempObj[name
+ "_notInObjectSuffix"] !== undefined
||
undefinedList[name + "_notInObjectSuffix"]
){
ASSetPropFlags(obj, name, 0, 1);
}
}
}
}
// В этой рекурсивной функции мы используем фокус с
// "отцеплением цепочки" __proto__ - иначе оператором
// for...in были бы выведены вперемешку поля и методы от
// разных классов цепочки.
_global.printAllFields = function(obj, name,
dontRecoverHidden){
var tempProto = obj.__proto__;
obj.__proto__ = null;
printFieldsByForIn(obj, name, tempProto, dontRecoverHidden);
obj.__proto__ = tempProto;
// Проверка на null не нужна: null == undefined,
хотя
// отличить их и можно при помощи оператора ===.
if (obj.__proto__ != undefined)
printAllFields(obj.__proto__, name +
".__proto__");
}
// А эта функция просто вызывает основную рабочую
функцию и
// добавляет "элементы оформления" (в текстовом
виде, разумеется).
_global.dumpObj = function(obj, name,
dontRecoverHidden){
trace("=============================================");
if (name == undefined) name = "<Dumped object>";
printAllFields(obj, name, dontRecoverHidden);
trace("=============================================");
trace("");
}
Пример 8.1.
Теперь
сделаем небольшой тестовый пример. Для удобства этот и
два предыдущих фрагмента кода можно разместить в трех
последовательных ключевых кадрах нового флэш-ролика,
только не забудьте в четвертом кадре поставить
stop().
// cn1 - означает
"constructor number 1".
// Похожие классы (в том числе с другими номерами)
// мы будем использовать далее в тестовых целях.
cn1 = function(a, b){
super(a + "_cn1", b + "_cn1");
trace("constr cn1: " + a + " | " + b);
}
cn1.prototype = new Array();
cn1.prototype.cn1_f = function(){trace("func:
cn1_f");}
cn1.prototype.cn1_g = function(){trace("func:
cn1_g");}
// Имя означает "object number 1".
on1 = new cn1(5, 6);
on1_copy = copyObjectExt(on1, null, true);
trace("\n Строчка, расположенная выше - это
результат работы
конструктора");
trace("\n А теперь выведем поля и методы
интересующих нас
объектов. \n");
dumpObj(on1, "on1");
dumpObj(on1_copy, "on1_copy");
trace("\n Проверяем, что мы не испортили скрытие
полей и
методов. \n");
dumpObj(on1, "on1");
trace("\n А теперь раскроем все методы класса Array.
\n");
ASSetPropFlags(Array.prototype, null, 0, 7);
on1_copy2 = copyObjectExt(on1, null, true);
dumpObj(on1_copy2, "on1_copy2");
dumpObj(Array.prototype, "Array.prototype");
Запуск
этого примера дает следующий результат:
constr cn1: 5 | 6
Строчка, расположенная выше - это результат работы конструктора
А теперь выведем поля и методы интересующих нас объектов.
==========================================================
::::::::::: on1 ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
1 : 6_cn1
0 : 5_cn1
length <hidden>: 2
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: on1.__proto__ ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
cn1_g : [type Function]
cn1_f : [type Function]
length <hidden>: 0
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: on1.__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]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: on1.__proto__.__proto__.__proto__
::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__proto__ : \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]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
==========================================================
==========================================================
::::::::::: on1_copy ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
length <hidden>: 2
0 : 5_cn1
1 : 6_cn1
cn1_f : [type Function]
cn1_g : [type Function]
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: [object Object]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: on1_copy.__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]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
==========================================================
Проверяем,
что мы не испортили скрытие полей и методов.
==========================================================
::::::::::: on1 ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
1 : 6_cn1
0 : 5_cn1
length <hidden>: 2
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: on1.__proto__ ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
cn1_g : [type Function]
cn1_f : [type Function]
length <hidden>: 0
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: on1.__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]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: on1.__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]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
==========================================================
А теперь
раскроем все методы класса Array.
==========================================================
::::::::::: on1_copy2 ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
length <hidden>: 2
0 : 5_cn1
1 : 6_cn1
cn1_f : [type Function]
cn1_g : [type Function]
push : [type Function]
pop : [type Function]
concat : [type Function]
shift : [type Function]
unshift : [type Function]
slice : [type Function]
join : [type Function]
splice : [type Function]
toString : [type Function]
sort : [type Function]
reverse : [type Function]
sortOn : [type Function]
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: [object Object]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: on1_copy2.__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]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
==========================================================
==========================================================
::::::::::: Array.prototype ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
sortOn : [type Function]
reverse : [type Function]
sort : [type Function]
toString : [type Function]
splice : [type Function]
join : [type Function]
slice : [type Function]
unshift : [type Function]
shift : [type Function]
concat : [type Function]
pop : [type Function]
push : [type Function]
__proto__ : [object Object]
constructor : [type Function]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: Array.prototype.__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]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
==========================================================
Давайте
разберемся, что же мы получили. Во-первых, новая функция
dumpObj работает и не
портит изначально скрытые объекты. Во-вторых, функция
копирования тоже работает аккуратно и копирует с учетом
скрытия. В-третьих, поскольку
ASSetPropFlags не действует на поля из базовых
классов, скрытые функции базовых классов не копируются.
Если же их раскрыть (как мы дальше поступили с классом
Array), то копируется все.
Эту особенность следует учитывать при пользовании
функцией копирования. Впрочем, при реализации
множественного наследования
мы будем ею пользоваться, предварительно "отцепляя
цепочку" базовых классов путем установки
__proto__ в
null. Так что данная
тонкость для нас не будет важна.
Код основной функции
Переходим
к основной функции множественного
наследования
multipleInherit. Ее аргументы мы описали ранее;
еще раз напомним, что в нее передаются функция-заготовка
для конструктора, массив базовых классов (состоящий из
субмассивов, каждый из которых содержит всю нужную
информацию о конкретном базовом классе) и, если надо,
массив с информацией о некопируемом
системном базовом классе.
Вот код этой функции.
/*=======================================================*
* Функция multipleInherit - реализует множественное
наследование *
*=======================================================*/
// В функции multipleInherit надо скопировать
цепочки __proto__
// для всех используемых классов. При составлении
цепочки
// __proto__ нужно клонировать объекты-функции
конструкторов.
_global.multipleInherit = function(constr, bases,
systemBase){
// Определяем "константы" для обращения к
субмассивам аргумента
// bases
var baseClassNum = 0, argsFuncNum = 1,
stopClassNum = 2, stopInclusiveNum = 3;
// Если базовых классов нет, выходим
if (bases == null || bases.length == 0) return null;
// Если systemBase не указан, то это Object
if (systemBase == null || systemBase[baseClassNum] == null){
systemBase = [Object, function (){}];
}
// Заводим локальные переменные.
// Префиксы при именах переменных означают три
уровня классов:
// prev - этот класс мы уже вставили в цепочку (и к
нему
// прицепляем следующие), cur - этот класс мы
вставляем в
// цепочку как раз сейчас, next - этот класс мы
будем вставлять
// в цепочку на следующем этапе. В свою очередь,
"корень" source
// обозначает копируемую цепочку, а dest - цепочку
новую.
// Поскольку мы заводим только необходимые
переменные, наборы для
// исходной и для новой цепочек получились не вполне
симметричны.
// Переменные для исходной цепочки
var prevSourceConstr; // Предыдущий класс (наследник текущего)
var curSourceConstr; // Текущий класс (его конструктор)
var curSourceProt; // Прототип последнего готового класса
var nextSourceProt; // Прототип следующего класса
// Переменные для новой цепочки
var prevDestProt; // Прототип последнего готового класса
var prevDestConstr; // Конструктор последнего готового класса
var curDestConstr; // Конструктор приготовляемого класса
var curDestProt; // Прототип приготовляемого класса
// Здесь мы будем держать скопированные (и модифицированные)
// базовые конструкторы
var baseConstructorsReady = new Array();
// Создаем новый класс - он будет результатом нашей работы.
// В его конструкторе будет размещена функция вызова базовых
// конструкторов. Именно этот класс и возвращает
// функция multipleInherit
var ClassToReturn = function(){
// Сохраняем ссылку на базовый конструктор,
поскольку
// эту ссылку нам придется изменять.
var baseConstr = this.__proto__.__constructor__;
// Вызываем конструкторы базовых классов,
// начиная с "наиболее базовых".
// Если есть systemBase, то вызываем конструктор
для него
if (systemBase != null &&
systemBase[baseClassNum] != null){
// Вызов делается через
функцию, передающую аргументы
// (через вызов super() в ней),
поэтому устанавливаем
// в качестве базового
конструктора именно системный
// базовый класс.
this.__proto__.__constructor__
= systemBase[baseClassNum];
// Собственно вызов конструктора вышеописанным
способом.
systemBase[argsFuncNum].apply(this, arguments);
}
// Далее вызываем конструкторы остальных базовых
классов.
// Снова таки, начинаем с классов,
// ближайших к Object (или systemBase)
for (var i=bases.length - 1; i>=0; i-){
// Вызываем
конструктор
baseConstructorsReady[i].apply(
this,
bases[i][argsFuncNum].apply(this, arguments)
);
}
// Восстанавливаем ссылку на конструктор базового
класса
this.__proto__.__constructor__ = baseConstr;
// И потом вызываем исходный конструктор
constr.apply(this, arguments);
}
// Цепляем массивы с исходными функциями и классами
// к создаваемому классу. Это позволит нам в дальнейшем
// создать удобные утилиты.
// Для той же цели цепляем и исходную функцию-конструктор
ClassToReturn.bases = bases;
ClassToReturn.constr = constr;
// Готовимся копировать цепочки __proto__
ClassToReturn.prototype = new Object();
prevDestProt = ClassToReturn.prototype;
prevSourceConstr = null;
prevDestConstr = ClassToReturn;
ClassToReturn.constr.prototype = prevDestProt;
// Этот флаг позволит настроить цепочку классов так, чтобы
// все вызовы первых конструкторов в каждой субцепочке
// (для каждого из непосредственных базовых классов)
// производить исключительно из только что созданного нами
// конструктора класса-наследника
var isFirstInSubChain = true;
for(var i=0; i<bases.length; i++){
curSourceConstr = bases[i][baseClassNum];
curSourceProt = curSourceConstr.prototype;
isFirstInSubChain = true;
// Цикл для одной субцепочки (то есть для цепочки
// __proto__ одного из базовых классов)
while(curSourceProt){
// Проверяем,
не надо ли оборвать цепочку
if(
!bases[i][stopInclusiveNum] &&
bases[i][stopClassNum] == curSourceConstr
) break;
if(
bases[i][stopInclusiveNum] &&
bases[i][stopClassNum] == prevSourceConstr
) break;
if (
curSourceConstr == systemBase[baseClassNum] ||
curSourceConstr == Object
) break;
// В системных классах
constructor прототипа
// указывает на сам класс (в
отличие от классов,
// сделанных самостоятельно).
Проверяем этот вариант:
if (
curSourceConstr.prototype.constructor ==
curSourceConstr
&&
(
curSourceProt.constructor ==
systemBase[baseClassNum] ||
curSourceProt.constructor == Object
)
) break;
// Заводим новый объект и
копируем в него функции из
// текущего прототипа исходной
цепочки
curDestProt = new Object();
// При этом нам надо временно
отцепить "хвост", чтобы
// не копировать его содержимое
nextSourceProt =
curSourceProt.__proto__;
curSourceProt.__proto__ =
undefined;
// Копируем, учитывая скрытые
поля (если есть)
copyObjectExt(curSourceProt,
curDestProt, true);
// Восстанавливаем исходную
цепочку
curSourceProt.__proto__ =
nextSourceProt;
// Теперь надо приготовить
конструктор новому классу
curDestConstr = function(){
// Вызываем
исходный конструктор.
// Он будет
прикреплен к данному объекту-функции.
// Только
перед его вызовом надо временно изменить
// ссылку на
базовый конструктор в this - чтобы
// она
указывала на класс, базовый к вызываемому
//
конструктору, а не к классу, объектом которого
// является
this
this.__proto__.__constructor__ =
arguments.callee.__constructor__;
arguments.callee.curSourceConstr.apply(this,
arguments);
// Значение
this.__proto__.__constructor__ будет
//
восстановлено далее в конструкторе this
}
// Теперь прикрепляем к
созданному объекту-функции
// прототип и исходный
конструктор
curDestConstr.prototype =
curDestProt;
curDestConstr.curSourceConstr =
curSourceConstr;
// Готовый конструктор цепляем
к предыдущему прототипу
prevDestProt.constructor =
curDestConstr;
// Его же (конструктор) надо
прикрепить в качестве
// базового конструктора к
производному классу
// (если только мы не в начале
субцепочки; а если в
// начале, то вместо этого
записываем в массив
// сгенерированный
конструктор). Потом (перед вызовом)
// прикрепленный к конструктору
производного класса
// базовый конструктор мы
достанем,
// используя конструкцию
argumetns.callee
if (!isFirstInSubChain){
prevDestConstr.__constructor__ = curDestConstr;
}
else baseConstructorsReady[i] =
curDestConstr;
// Готовый объект цепляем в
цепочку.
prevDestProt.__proto__ =
curDestProt;
// Сдвигаемся на один шаг по
цепочке
// (в направлении базовых
классов)
prevSourceConstr =
curSourceConstr;
curSourceConstr =
curSourceProt.constructor;
// Только теперь, получив
ссылку на новый исходный
// конструктор, можно менять
ссылку на исходный прототип
curSourceProt =
curSourceProt.__proto__;
prevDestProt = curDestProt;
prevDestConstr = curDestConstr;
// Приводим значение флага в
соответствие
// с нашим новым положением в
субцепочке
isFirstInSubChain = false;
} // Закончили цикл одной субцепочки
} // Закончили цикл одной серии базовых классов (виртуальных
// или нет)
// Осталось добавить базовый класс systemBase
prevDestProt.__proto__ = systemBase[baseClassNum].prototype;
// Все готово, возвращаем результат
return ClassToReturn;
}
Пример 8.2.
Тестируем основную
функцию
Давайте
проверять, как работает то, что у нас получилось. Для
этого создадим две несвязанные иерархии классов, а затем
- классы, которые наследуются от них обеих. Сначала
сделаем совсем простое
множественное наследование, затем проверим, как
работают стоп-классы и системные
некопируемые базовые классы, наконец, при помощи
стоп-классов эмулируем виртуальные
базовые классы.
Для
удобства мы разместили тестовые функции в нескольких
кадрах, что рекомендуем сделать и вам. Только не
забудьте поставить stop();
в ключевом кадре, стоящем после всех тестовых. Итак,
кадр с первой тестовой иерархией.
// Включаем отладочный режим -
это повлияет на поведение
// функции копирования, которая станет помечать
копии
// специальным текстовым полем
_global.DEBUG = true;
// Заводим тестовые классы
// Имя cn1 означает class number 1
// (или constructor number 1).
// Классы серии cn отнаследованы от массива (класса
Array)
cn1 = function(a, b){
super(a + "_cn1", b + "_cn1");
trace("constr cn1: " + a + " | " + b);
}
cn1.prototype = new Array();
cn1.prototype.cn1_f = function(){trace("func:
cn1_f");}
cn1.prototype.cn1_g = function(){trace("func:
cn1_g");}
// class number 2
cn2 = function(a, b){
super(a + "_cn2", b + "_cn2");
trace("constr cn2: " + a + " | " + b);
}
cn2.prototype = new cn1();
cn2.prototype.cn2_f = function(){trace("func:
cn2_f");}
cn2.prototype.cn2_g = function(){trace("func:
cn2_g");}
// class number 3
cn3 = function(a, b){
super(a + "_cn3", b + "_cn3");
trace("constr cn3: " + a + " | " + b);
}
cn3.prototype = new cn2();
cn3.prototype.cn3_f = function(){trace("func:
cn3_f");}
cn3.prototype.cn3_g = function(){trace("func:
cn3_g");}
trace("====================================");
// Object of class number 3
on3 = new cn3("a~arg", "b~arg");
trace("------------------");
// Проверяем работоспособность функций
on3.cn3_g();
on3.cn1_f();
// Проверяем, что объект может работать (и
печататься) как массив
trace("on3: " + on3);
// Смотрим, что у нас получился за объект -
// пока без использования множественного
наследования
dumpObj(on3, "on3");
Посмотрим,
что выводят в окно Output функции из этого кадра.
(Здесь множественное наследование
мы еще не использовали. Пока что выясним поподробнее,
что представляют собой исходные классы.)
constr cn1: |
constr cn1: _cn2 | _cn2
constr cn2: |
====================================
constr cn1: a~arg_cn3_cn2 | b~arg_cn3_cn2
constr cn2: a~arg_cn3 | b~arg_cn3
constr cn3: a~arg | b~arg
------------------
func: cn3_g
func: cn1_f
on3: a~arg_cn3_cn2_cn1,b~arg_cn3_cn2_cn1
==========================================================
::::::::::: on3 ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
1 : b~arg_cn3_cn2_cn1
0 : a~arg_cn3_cn2_cn1
length <hidden>: 2
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: _cn2_cn1,_cn2_cn1
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: on3.__proto__ ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
cn3_g : [type Function]
cn3_f : [type Function]
1 : _cn2_cn1
0 : _cn2_cn1
length <hidden>: 2
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: _cn1,_cn1
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: on3.__proto__.__proto__
::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
cn2_g : [type Function]
cn2_f : [type Function]
1 : _cn1
0 : _cn1
length <hidden>: 2
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: on3.__proto__.__proto__.__proto__
::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
cn1_g : [type Function]
cn1_f : [type Function]
length <hidden>: 0
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::
on3.__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]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::
on3.__proto__.__proto__.__proto__.__proto__.__proto__
::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__proto__ : \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]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
==========================================================
Что же мы
видим? До первой двойной черты (напечатанной знаками
равенства) - то, что выводят конструкторы в процессе
наследования (при создании нового объекта для помещения
в prototype). Далее (под
чертой) то, что печатают конструкторы (видим, что все
базовые конструкторы вызваны с правильными аргументами).
Потом - результат работы двух функций "на пробу"
(функции печатают то, что и ожидалось). Еще дальше -
выводим с помощью trace
сам вновь созданный объект (поскольку он унаследован от
Array, то и в самом деле,
через запятую выводятся значения, переданные в
конструктор Array через
цепочку вызовов super()).
Наконец, еще ниже расположен результат работы функции
dumpObj, из которого можно
подробно узнать обо всех полях и методах получившегося
объекта.
Теперь
заведем еще одну иерархию классов. Она аналогична
предыдущей, только происходит от класса
String и короче. О ней мы
уже не будем выводить столь подробные данные. Код,
который ее создает, таков:
trace("\n");
// Следующую серию классов называем аналогично
первой,
// только букву n меняем на m.
// Все эти классы являются наследниками String
cm1 = function(a){
super(a + "_cm1");
trace("constr cm1: " + a);
}
cm1.prototype = new String();
cm1.prototype.cm1_f = function(){trace("func:
cm1_f");}
// Второй класс серии m
cm2 = function(a){
super(a + "_cm2");
trace("constr cm2: " + a);
}
cm2.prototype = new cm1();
cm2.prototype.cm2_f = function(){trace("func:
cm2_f");}
trace("====================================");
// Объект втогого класса серии m
om2 = new cm2("a-arg");
trace("------------------");
// Проверяем, что функции работают
om2.cm2_f();
om2.cm1_f();
// Проверяем, что объект не потерял свойства строки
trace("om2: " + om2);
Конструкторы, вызываемые функции и тестовый
trace дают на выходе
constr cm1:
====================================
constr cm1: a-arg_cm2
constr cm2: a-arg
------------------
func: cm2_f
func: cm1_f
om2: a-arg_cm2_cm1
Так что
все работает, как ожидалось.
А вот
дальше начинается то, ради чего, собственно, и написана
эта лекция. Дальше мы сделаем два класса, унаследованных
от обеих иерархий, причем один совсем простой, а другой
- уже с использованием стоп-классов и некопируемого
(системного) базового класса.
Вот этот код:
// Базовые классы готовы,
начинаем применять multipleInherit.
// Простейщий вариант - без виртуальных классов
classDer1 = multipleInherit(
function(a, b, c){
trace("constrOfDerived: " + a + " | " + b + " | "
+ c);
},
[
[cn3, function(){return [arguments[0],
arguments[1]];}],
[cm2, function(){return [arguments[2]];}]
]
);
// А здесь используем системный некопируемый класс
classDer2 = multipleInherit(
function(a, b, c){
trace("constrOfDerived: " + a + " | " + b + " | "
+ c);
},
[
[cm2, function(){return [arguments[0]];}, cm1],
[cn3, function(){return [arguments[1],
arguments[2]];}]
],
[Array, function(){super(arguments[0], arguments[1],
arguments[2]);}]
);
trace("********************************");
// Создаем объекты. od1 означает object of "derived
1"
od1 = new classDer1("aaaa", "bbbb", "cccc");
// Проверяем, что функции работают
trace("---");
od1.cn3_g();
od1.cn1_f();
trace("---");
od1.cm2_f();
od1.cm1_f();
trace("---");
// Смотрим, как объект выводится
trace("od1 = " + od1);
trace("********************************");
// Теперь смотрим, что у получившегося объекта
внутри
dumpObj(od1, "od1");
trace("********************************");
// Следующий объект создаем, соответственно, из
класса derived 2.
od2 = new classDer2("aaaa", "bbbb", "cccc");
// Смотрим, как объект выводится
trace("od2 = " + od2);
// И что у него внутри
dumpObj(od2, "od2");
Основная
часть этого кода (то есть вызовы
multipleInherit), как мы уже говорили, создает
два класса. Первый из них - совершенно "бесхитростный"
наследник классов cn3 и
cm2. Конструктор его будет
выводить три переданных в него аргумента, разделенных
знаком "|". В конструктор
cn3 будут передаваться
первые два аргумента, а в cm2
- третий аргумент.
Второй из
создаваемых классов отличается от первого тремя вещами.
Во-первых, порядком следования базовых классов (что
влияет на порядок выполнения конструкторов - первыми
выполняются конструкторы классов, указанных в самом
конце, так как они стоят ближе всего к "корню" цепочки).
Во-вторых, при наследовании от
cm2 указан стоп-класс cm1
- это значит, что cm1 и
его базовые классы не будут включены в цепочку (а сам
cm2 - будет). В-третьих,
класс Array указан в
качестве системного некопируемого класса. При этом
указано, что в его конструктор будут переданы все
аргументы, с которыми будет вызван конструктор
"множественного наследника".
Далее
создаются объекты каждого из этих классов, у этих
объектов вызывается несколько методов, а также для
каждого из вновь созданных объектов вызывается функция
dumpObj. В результате в
консоль выводится следующая (довольно объемистая)
информация.
********************************
constr cm1: cccc_cm2
constr cm2: cccc
constr cn1: aaaa_cn3_cn2 | bbbb_cn3_cn2
constr cn2: aaaa_cn3 | bbbb_cn3
constr cn3: aaaa | bbbb
constrOfDerived: aaaa | bbbb | cccc
---
func: cn3_g
func: cn1_f
---
func: cm2_f
func: cm1_f
---
od1 =
********************************
==========================================================
::::::::::: od1 ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: od1.__proto__ ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: _cn2_cn1,_cn2_cn1
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: od1.__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>: _cn1,_cn1
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: od1.__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>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::
od1.__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>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::
od1.__proto__.__proto__.__proto__.__proto__.__proto__
::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
isACopy : THAT'S A COPY
push <hidden>: [type Function]
pop <hidden>: [type Function]
concat <hidden>: [type Function]
shift <hidden>: [type Function]
unshift <hidden>: [type Function]
slice <hidden>: [type Function]
join <hidden>: [type Function]
splice <hidden>: [type Function]
toString <hidden>: [type Function]
sort <hidden>: [type Function]
reverse <hidden>: [type Function]
sortOn <hidden>: [type Function]
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: [type Object]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
: od1.__proto__.__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>: [type Object]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::
od1.__proto__.__proto__.__proto__.__proto__.__proto__.__
proto__.__proto__ :
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
isACopy : THAT'S A COPY
length <hidden>: 0
cm1_f : [type Function]
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: [type Object]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::
od1.__proto__.__proto__.__proto__.__proto__.__proto__.
__proto__.__proto__.__proto__ :::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
isACopy : THAT'S A COPY
valueOf <hidden>: [type Function]
toString <hidden>: [type Function]
toUpperCase <hidden>: [type Function]
toLowerCase <hidden>: [type Function]
charAt <hidden>: [type Function]
charCodeAt <hidden>: [type Function]
concat <hidden>: [type Function]
indexOf <hidden>: [type Function]
lastIndexOf <hidden>: [type Function]
slice <hidden>: [type Function]
substring <hidden>: [type Function]
split <hidden>: [type Function]
substr <hidden>: [type Function]
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: [object Object]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::
od1.__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]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
==========================================================
********************************
constr cn1: bbbb_cn3_cn2 | cccc_cn3_cn2
constr cn2: bbbb_cn3 | cccc_cn3
constr cn3: bbbb | cccc
constr cm2: aaaa
constrOfDerived: aaaa | bbbb | cccc
od2 = aaaa,bbbb,cccc
==========================================================
::::::::::: od2 ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
2 : cccc
1 : bbbb
0 : aaaa
length <hidden>: 3
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: od2.__proto__ ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: ,,,
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: od2.__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
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: od2.__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>: _cn1,_cn1
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::
od2.__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>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::
od2.__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>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::
od2.__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]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::
od2.__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]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
==========================================================
Что же мы
получили на этот раз? Под первой строчкой из звездочек
выведен результат работы конструктора класса
classDer1. (Напомним, что
конструкторы всех его базовых классов устроены так, что
прибавляют суффиксы с именем класса к аргументам,
передаваемым в super()).
Видно, что сначала сработала цепочка вызовов
конструкторов базового класса cm2,
а потом - цепочка cn3.
Затем (после короткой черты) расположено то, что вывели
методы объекта od1 (класса
classDer1), унаследованные
им от различных базовых классов. Результаты работы
методов разных базовых классов мы нарочно разделили еще
одной короткой чертой. Все методы отработали как
положено.
Потом мы
попробовали напечатать od1
с помощью trace и
обнаружили, что наш "массив" (ведь этот класс был
наследован от массива) пуст. (На самом деле, в числе
базовых классов был также и класс
String. Все дальнейшие рассуждения в этом абзаце
относятся к нему в той же мере, что и к
Array.) Массив пуст, а
ведь при обычном наследовании класс
cn3 вел себя не так и
массив вовсе не был пустым. Все это связано с тем, что
базовый класс Array был
скопирован вместе с остальными и его конструктор, так же
как и остальные, вызывается через
apply (а не через super).
А это, как мы ранее выяснили, для
системных базовых классов неприемлемо. В
принципе, можно было вызывать через
super абсолютно все
базовые конструкторы, но это было бы не так удобно. Мы
не смогли бы передавать аргументы в базовые классы в
массиве. Эта возможность особенно ценна, если в базовый
класс передаются в точности те же аргументы, что и в
производный (что бывает очень часто), и мы можем в
качестве функции, формирующей аргументы, передать просто
function(){return arguments;}.
Итак, если правильный вызов конструктора
Array был бы важен для
нас, нам следовало бы указать
Array в качестве системного
базового класса (что и сделано в классе
classDer2). А пока будем
считать, что для работы класса
classDer1 эти тонкости несущественны, потому-то
мы о них и не позаботились.
Далее идет
дамп полей и методов объекта od1.
Из дампа также видно, что полей с именами 0, 1 и т.д.,
которые должны были бы присутствовать, если бы правильно
сработал конструктор Array
в созданном нами объекте, нет. Но давайте посмотрим, из
чего состоит цепочка
__proto__ объекта
od1. Объект
od1.__proto__ не содержит
в себе ничего интересного - это прототип, лежащий в
функции-конструкторе, созданной нами при помощи
multipleInherit. В него
можно было бы поместить специфичные для класса
classDer1 методы, но в
данном тестовом примере мы этого не делали. А вот далее
в цепочке мы увидим плоды нашей работы (точнее, работы
функции multipleInherit).
Хотя мы и не снабдили классы отладочными полями, в
которых были бы указаны их имена, разобраться, с каким
именно классом мы имеем дело, можно, взглянув на имена
функций - они снабжены соответствующими префиксами. Ит
ак, сначала (при движении по направлению к корню
цепочки) мы видим классы cn3,
cn2 и
cn1. Затем идет
скопированный класс Array
(его тоже можно определить по набору методов). Потом
идут классы cm2,
cm1 и скопированный класс
String. Наконец, самым
последним идет Object (он
не скопирован, поскольку, если
systemBase не указан, в качестве него
подразумевается Object).
Дальше
идет информация, относящаяся уже к классу
classDer2. Мы видим, что,
в отличие от classDer1,
его содержимое как массива соответствует тому, что мы
хотели. Это следует как из результата работы
trace, так и из дампа.
Подробнее изучая дамп, мы видим, что правильно сработало
наследование от cm2 - мы
скомандовали не включать классы, начиная с
cm1, так и произошло.
Далее в цепочке присутствуют cn3,
cn2,
cn1 и
Array, причем
Array, в отличие от всех
остальных, не скопирован (и конструктор его вызывается
через super, а не через
apply).
А теперь
давайте посмотрим, как с помощью
multipleInherit можно реализовать
виртуальные базовые классы.
Собственно говоря, идея очень простая: класс, который мы
хотим объявить виртуальным базовым,
мы указываем в качестве стоп-класса для всех базовых
классов, у которых в цепочке
__proto__ он присутствует.
То есть цепочки
__proto__ этих классов
будут оборваны именно на
виртуальном базовом классе. Останется лишь
указать этот класс в качестве базового (причем
разместить его ближе к концу списка базовых классов -
ближе, чем расположены оба его наследника). Вот код,
который реализует эту идею:
// Эмулируем виртуальные
базовые классы
// Для этого сначала создадим еще один класс -
наследник cn2
cx = function(a){
super(a + "_cx", a + "_bis_cx");
trace("constr cx: " + a);
}
cx.prototype = new cn2();
cx.prototype.cx_f = function(){trace("func: cx_f");}
// И сделаем класс-наследник cm2, cn3, cnx и cn2,
причем
// классам cn3 и cnx в качестве стоп-класса укажем
cn2.
// Как и раньше, оставим Array в качестве системного
// некопируемого базового класса
classDer3 = multipleInherit(
function(a, b, c){
trace("constrOfDerived: " + a + " | " + b + " | "
+ c);
},
[
[cm2, function(){return [arguments[0]];}, cm1],
[cn3, function(){return [arguments[1],
arguments[2]];}, cn2],
[cx, function(){return [arguments[1]];}, cn2],
[cn2, function(){return [arguments[1],
arguments[1]];}]
],
[Array, function(){super(arguments[1], arguments[2]);}]
);
// Посмотрим, что у нас получилось: под строкой из
звездочек
// будет выведен результат работы конструктора
trace("********************************");
od3 = new classDer3("aaaa", "bbbb", "cccc");
trace("-----------------");
// А затем выводим "внутренности" самого объекта
dumpObj(od3, "od3");
Этот код
должен выводить результаты работы конструктора и дамп
объекта od3. Вот они:
constr cn1: _cn2 | _cn2
constr cn2: |
********************************
constr cn1: bbbb_cn2 | bbbb_cn2
constr cn2: bbbb | bbbb
constr cx: bbbb
constr cn3: bbbb | cccc
constr cm2: aaaa
constrOfDerived: aaaa | bbbb | cccc
-----------------
==========================================================
::::::::::: od3 ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
1 : cccc
0 : bbbb
length <hidden>: 2
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: od3.__proto__ ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: ,,,
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: od3.__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
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: od3.__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
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::
od3.__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
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::
od3.__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>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::
od3.__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>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::
od3.__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]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::
od3.__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]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
==========================================================
До черты
из звездочек идут результаты работы конструкторов при
создании прототипа класса cx;
на них мы не будем обращать внимание. А вот дальше
начинаются интересные вещи. Во-первых, мы видим, что
конструкторы отработали в правильном порядке. То есть -
сначала cn1 и
cn2, что соответствует
тому, что cn2 является
виртуальным базовым классом.
Затем - cnx, а потом уже
cn3,
cm2 и конструктор
созданного нами производного класса. Все это вполне
соответствует заданному нами расположению классов в
цепочке.
Наконец,
рассмотрев дамп, мы убедимся, что правильный порядок
базовых классов соблюдается и там: двигаясь по
направлению к корню цепочки, мы обнаружим классы
cm2,
cn3,
cnx, а затем и
cn2,
cn1 и
Array (причем последний -
не скопирован, поскольку указан в аргументах
multipleInherit как
системный базовый класс; по
дампу это, правда, определить нельзя, но можно,
например, добавить какой-нибудь метод в системный
Array и убедиться, что он
сработает и в od3.
Разумеется, фокусы наподобие последнего - с модификацией
системных классов - лучше делать только в тестовых
целях).
Наследование от
полученных классов
Давайте
убедимся, что полученный в результате использования
функции multipleInherit
класс пригоден для наследования от него. Пишем следующий
код:
// Создаем класс, являющийся
наследником classDer3
cd4 = function(a, b, c){
super(a + "_cd4", b + "_cd4", c + "_cd4");
trace("constr cd4: " + a + " | " + b + " | " + c);
}
cd4.prototype = new classDer3();
cd4.prototype.cd4_f = function(){trace("func:
cd4_f");}
// Тестируем
trace("-----------------");
od4 = new cd4("A", "B", "C");
trace("-----------------");
trace("od4 = " + od4);
trace("-----------------");
Мы самым
обычным образом наследовались от класса
classDer3, созданного
ранее с помощью multipleInherit.
Запуск приведенного кода дает следующий результат:
constr cn1: _cn2 | _cn2
constr cn2: |
constr cx:
constr cn3: |
constr cm2:
constrOfDerived: | |
-----------------
constr cn1: B_cd4_cn2 | B_cd4_cn2
constr cn2: B_cd4 | B_cd4
constr cx: B_cd4
constr cn3: B_cd4 | C_cd4
constr cm2: A_cd4
constrOfDerived: A_cd4 | B_cd4 | C_cd4
constr cd4: A | B | C
-----------------
od4 = B_cd4,C_cd4
-----------------
Все, что
выведено до первой длинной черты - это результат работы
конструктора при наследовании (строчка
cd4.prototype = new classDer3()).
А вот после нее напечатано то, что выводит конструктор
класса cd4 при создании
объекта od4. При этом
правильным образом отработали конструкторы всех базовых
классов (так, как они и должны работать в
classDer3). После еще
одной черты помещен результат работы оператора
trace("od4 = " + od4). Мы
видим, что и наследник класса
classDer3 ведет себя как наследник массива.
Теперь
посмотрим на результаты повторного использования
multipleInherit. Добавляем
ко всему предыдущему коду еще один кусок:
// Создаем новый класс cy -
наследник класса cn1,
// чья копия входит в цепочку __proto__ класса
classDer3
cy = function(a){
super(a + "_cy", a + "_bis_cy");
trace("constr cy: " + a);
}
cy.prototype = new cn1();
cy.prototype.cy_f = function(){trace("func: cy_f");}
// А теперь с помощью multipleInherit создаем класс
classDerDer -
// наследника классов cy и cd4 (последний, в свою
очередь,
// является наследником класса classDer3, также
созданного
// с помощью функции multipleInherit).
classDerDer = multipleInherit(
function(a, b, c){
trace("constrOfDerDer: " + a + " | " + b + " | "
+ c);
},
[
[cy, function(){return [arguments[2]];}],
[cd4, function(){return arguments;}]
],
[Array, function(){super(arguments[0], arguments[1],
arguments[2]);}]
);
// Проверяем, что у нас получилось
trace("********************************");
odd = new classDerDer("AA", "BB", "CC");
trace("-----------------");
trace("odd = " + odd);
dumpObj(odd, "odd");
Запустив
все это, получим в консоли следующий новый кусок:
constr cn1: |
********************************
constr cn1: BB_cd4_cn2 | BB_cd4_cn2
constr cn2: BB_cd4 | BB_cd4
constr cx: BB_cd4
constr cn3: BB_cd4 | CC_cd4
constr cm2: AA_cd4
constrOfDerived: AA_cd4 | BB_cd4 | CC_cd4
constr cd4: AA | BB | CC
constr cn1: CC_cy | CC_bis_cy
constr cy: CC
constrOfDerDer: AA | BB | CC
-----------------
odd = AA,BB,CC,BB_cd4,CC_cd4
==========================================================
::::::::::: odd ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
4 : CC_cd4
3 : BB_cd4
2 : CC
1 : BB
0 : AA
length <hidden>: 5
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: odd.__proto__ ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: _cn1,_cn1
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: odd.__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>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: odd.__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>: ,
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::
odd.__proto__.__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>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::
odd.__proto__.__proto__.__proto__.__proto____proto__
::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
isACopy : THAT'S A COPY
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: ,,,
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::
odd.__proto__.__proto__.__proto__.__proto__.
__proto__.__proto__ ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
cm2_f : [type Function]
length <hidden>: 4
isACopy : THAT'S A COPY
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: _cn2_cn1,_cn2_cn1
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::
odd.__proto__.__proto__.__proto__.__proto__.
__proto__.__proto__.__proto__ ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
cn3_g : [type Function]
cn3_f : [type Function]
1 : _cn2_cn1
0 : _cn2_cn1
length <hidden>: 2
isACopy : THAT'S A COPY
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: _cn2_cn1,_cn2_cn1
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: odd.__proto__.__proto__.__proto__.
__proto__.__proto__.__proto__.
__proto__.__proto__ ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
cx_f : [type Function]
1 : _cn2_cn1
0 : _cn2_cn1
length <hidden>: 2
isACopy : THAT'S A COPY
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>: _cn1,_cn1
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: odd.__proto__.__proto__.__proto__.
__proto__.__proto__.__proto__.__proto__.
__proto__.__proto__ ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
cn2_g : [type Function]
cn2_f : [type Function]
1 : _cn1
0 : _cn1
length <hidden>: 2
isACopy : THAT'S A COPY
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: odd.__proto__.__proto__.__proto__.
__proto__.__proto__.__proto__.
__proto__.__proto__.__proto__.
__proto__ ::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
cn1_g : [type Function]
cn1_f : [type Function]
length <hidden>: 0
isACopy : THAT'S A COPY
__constructor__ <hidden>: [type Function]
constructor <hidden>: [type Function]
__proto__ <hidden>:
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::::::::::: odd.__proto__.__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]
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::
odd.__proto__.__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__ и мы ее
получили. В частности, можно проконтролировать, что в
ней присутствуют две копии класса
cn1 - одна попала в цепочку из класса
cy, а другая - из
cd4 (где, в свою очередь
она взялась из classDer3,
а в нем - из cn2). Также
мы видим, что в цепочке ровно один раз присутствует
класс Array, причем сам, а
не его копия. А вот то, что в массиве, которым является
объект odd как наследник
Array, имеется пять
элементов, а не три - это некоторая неожиданность, хотя
и легко объяснимая. Ведь у нас конструктор
Array вызван дважды - один
раз из конструктора класса
classDer3, а второй раз - из самого конструктора
classDerDer. Вообще-то это
может быть неудобно: получается, что если мы наследуемся
от classDer3, мы никак не
можем избавиться от первого из вызовов. И, если уж на то
пошло, нас может не устраивать наличие нескольких копий
одного и того же класса (возможно, с разными
аргументами, переданными в конструктор) в
цепочке
__proto__. Короче говоря,
хотелось бы иметь полноценный механизм
виртуальных базовых классов
и при работе с классами, полученными с
multipleInherit.
Однако,
есть ли здесь вообще поле для деятельности? Казалось бы,
виртуальные базовые классы
уже реализованы нами. Тем не менее, есть одна сложность.
А именно, в цепочке
__proto__ у класса,
сформированного при помощи
multipleInherit лежат копии базовых классов.
Поэтому мы не сможем применить механизм обрыва цепочки,
при обнаружении, что текущий копируемый класс равен
некоему заданному. Можно было бы, конечно, ссылку на
исходный класс сохранить в поле копии, но такое решение
повлекло бы за собой ряд проблем. Потребовалось бы
сохранять эту ссылку при дальнейшем копировании, неясно,
как следовало бы в таком случае поступать с
конструкторами классов-"множественных наследников"
(которые не рассчитаны на то, что в их
цепочке
__proto__ будут что-то
менять), наконец, проблемы с многократным вызовом
конструкторов системных базовых
классов остались бы нерешенными. Поэтому мы
изберем другой путь.
Многократное
множественное наследование и виртуальные базовые классы
Вместо
того чтобы сохранять (лишь слегка модифицируя)
цепочку
__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 принимает
пять аргументов:
-
sourceArray
- исходный массив с информацией о базовых классах
(состоящий из субмассивов по четыре элемента -
класс, функция формирования аргументов, стоп-класс и
режим обрыва цепочки).
-
virtualBaseParams
- массив с данными о
виртуальном базовом классе (содержит, как
обычно, четыре элемента, имеющие тот же смысл, что и
элементы субмассивов
sourceArray).
-
chainsToStop
- массив классов, в данные которых нужно внести
добавляемый виртуальный
базовый класс в качестве стоп-класса.
-
placeNearClass
- класс, перед которым или после которого надо
разместить добавляемый
виртуальный базовый класс.
-
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, получился
гораздо более аккуратным и корректным.
Итак,
можно констатировать, что наша попытка эмулировать при
помощи Флэш МХ основные свойства
множественного наследования увенчалась весьма
убедительным успехом.
|