Опубликован: 23.12.2005 | Уровень: специалист | Доступ: платный | ВУЗ: Московский физико-технический институт
Лекция 7:

Наследование во Flash MX

Альтернативное наследование ("альянс мятежников")

Мы познакомились с тем интересным способом (называемым "прототипным наследованием "), с помощью которого делается наследование во Флэш МХ. Однако вы уже, видимо, заметили в этом способе некоторое неудобство. Состоит оно в том, что при создании объекта базового класса, записываемого в прототип класса производного, неминуемо вызывается конструктор. Сейчас мы приведем слегка модифицированный (и укороченный) пример наследования, уже встречавшийся в этой лекции, и затем попытаемся его исправить.

superclass = function(){
	trace("constructor of superclass");
}
superclass.prototype.say = function(text){trace(text + 
	"superclass")} 
subclass1 = function(){
	super();
	super.say();
}
// Вот обычное наследование
subclass1.prototype = new superclass();
subclass1.prototype.say = function(text){trace(text + 
	"subclass1")} 
a = new subclass1();
trace("---------------");
a.say("type = ");

Запустим это код и получим:

constructor of superclass
constructor of superclass
superclass
---------------
type = subclass1

Мы еще раз убедились, что обычное наследование неудобно из-за двойного вызова конструкторов. Но есть ряд вещей, которые нам помогут избежать этого! Во-первых, учтем, что при создании функции сразу же создается дополнительный пустой объект, на который указывает ссылка prototype. То есть при создании самого базового класса вовсе необязательно писать superclass.prototype = new Object() (и этим фактом мы регулярно пользовались). Во-вторых, нам надо добиться, чтобы объект, на который указывает prototype, воспринимался как объект определенного класса ( базового ); этот объект мы будем затем модифицировать. Мы знаем, что для этого нужна запись наподобие subclass1.prototype.__proto__ = superclass.prototype. Наконец, мы говорили, что выполнение оператора new не только создает новый объект и устанавливает ему свойство __proto__, но и инициализирует новому объекту еще два свойства по имени constructor и __constructor__. Свойство constructor нам сейчас не понадобится, а вот без __constructor__ не работает вызов базового конструктора через super (). Соответственно, именно на конструктор базового класса и должен указывать __constructor__. Итак, модифицируем вышеприведенный код следующим образом:

superclass = function(){
	trace("constructor of superclass");
}
superclass.prototype.say = function(text){trace(text + 
	"superclass")} 
subclass1 = function(){
	super();
	super.say();
}
// Следующие две строки - это и есть альтернативное наследование
subclass1.prototype.__proto__ = superclass.prototype;
subclass1.prototype.__constructor__ = superclass;
subclass1.prototype.say = function(text){trace(text + 
	"subclass1")} 
a = new subclass1();
trace("---------------");
a.say("type = ");

Запускаем этот код и получаем:

constructor of superclass
superclass
---------------
type = subclass1

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

Function.prototype.cExtends = function(base){
	this.prototype.__proto__ = base.prototype;
	this.prototype.__constructor__ = base; 
}
superclass = function(){
	trace("constructor of superclass");
}
superclass.prototype.say = function(text){trace(text + 
	"superclass")} 
subclass1 = function(){
	super();
	super.say();
}
// Сокращенная форма альтернативного наследования
subclass1.cExtends(superclass);
subclass1.prototype.say = function(text){trace(text + 
	"subclass1")} 
a = new subclass1();
trace("---------------");
a.say("type = ");

Запустив этот код, мы получим то же самое, что и в прошлый раз - функция cExtends замечательным образом сработала. Но нет предела совершенству - можно еще улучшить нашу замечательную функцию cExtends. Во-первых, неплохо бы сделать, чтобы поле __constructor__ было скрыто - так же, как и в том случае, когда его создает оператор new. Для этого надо в cExtends добавить строчку

ASSetPropFlags(this.prototype, "__constructor__", 1);

Для __proto__ ничего такого делать не надо - это свойство уже присутствует (и является скрытым) в prototype с самого начала. Во-вторых, вы, наверное, помните, что в прошлой лекции мы обсуждали, как сделать статические (принадлежащие целому классу) свойства. При этом свойства у нас хранились в полях функции-конструктора класса. Можно ли сделать эти статические свойства наследуемыми? Оказывается, да, и для этого функцию cExtends нужно модифицировать следующим образом:

Function.prototype.cExtends = function(base){
	this.prototype.__proto__ = base.prototype;
	this.prototype.__constructor__ = base; 
	ASSetPropFlags(this.prototype, "__constructor__", 1);
	this.__proto__ = base;
}

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

Приведенный только что вариант функции cExtends придуман опять-таки T. Гролео. Он же приводит в своей статье http://timotheegroleau.com/Flash/articles/private_static.htm довольно романтическую историю изобретения альтернативного наследования. Мы дополнили эту историю своими изысканиями, и сейчас сообщим ее вам. Группа исследователей Флэш под предводительством Дэйва Янга (чью статью об альтернативном наследовании вы также можете почитать вот по этой ссылке: http://www.quantumwave.com/flash/inheritance.html ), собравшаяся вокруг сайта FlashСoders, поставила себе целью разработать механизм наследования во Флэше, лишенный недостатков стандартного варианта. Эта группа взяла себе название Rebel Alliance ("альянс мятежников") - по мотивам Rebel Alliance из "Звездных войн". Было придумано несколько различных вариантов наследования. Однако все они имели определенные недостатки. Наконец, молодой джедай Питер Эдвардс обнаружил, что единственная серьезная дырка наследования при помощи __proto__ - невозможность вызвать базовый конструктор через super () - затыкается с помощью недокументированной ссылки __constructor__. После этого подвига даже выдвигаласть идея предложить Эдвардсу пост магистра Йоды (невзирая на то, что Эдвардс наверняка не подходит, ибо настоящий Йода должен быть исключительно мал ростом и очень уродлив). Так или иначе, альянс одержал убедительную победу.

Однако, похоже, что империя нанесла ответный удар. Сайт FlashСoders http://chattyfig.figleaf.com/flashcoders-wiki закрыт (а ведь на него указывали едва ли не все ссылки Интернета, касающиеся недокументированных возможностей Флэш). Закрыты и остальные сайты подобного плана (например сайт, с которого можно было скачать онлайн-документацию по недокументированным функциям - в формате, пригодном для установки в среду Флэш МХ). Последним прибежищем истинных джедаев остается интернет-архив http://web.archive.org, на котором, в частности сохранились документы конференции flashcoders-wiki. Архив заглавной страницы находится здесь: http://web.archive.org/web/20040605070406/chattyfig.figleaf.com/flashcoders-wiki/.

Еще остался список рассылки Flashcoders, подписаться на который можно здесь: http://chattyfig.figleaf.com/mailman/listinfo/flashcoders, а вот здесь http://chattyfig.figleaf.com/pipermail/flashcoders/ находится его архив переписки.

Так что не будем унывать - во-первых, мы-то с вами знаем все, что нужно, дабы устраивать всякие необычные фокусы во Флэше (без них настоящему программисту и жизнь не мила)! Во-вторых, документированных возможностей гораздо больше, чем недокументированных, так что потери не так уж велики. В-третьих, вы, наверное, в курсе, чем закончились "Звездные войны". Победа будет за нами!

алексеи федорович
алексеи федорович
Беларусь, рогачёв
Тамара Ионова
Тамара Ионова
Россия, Нижний Новгород, НГПУ, 2009