Казахстан, Алматы, Гимназия им. Ахмета Байтурсынова №139, 2008 |
Наследование во Flash MX
Наследование от необычных типов
Зачем нужны подобные изощрения? Мы отдаем себе отчет, что применять то, что описано в этом параграфе (а, возможно, и в предыдущем), вам, скорее всего, не придется. И при первом чтении параграф этот вполне можно пропустить. Тем не менее, упустить шанс показать, какие проблемы нас ждут при попытке реализовать наследование (хотя бы и в урезанном виде) без особой поддержки языка, мы не можем. Ведь именно сделав что-то "не так, как всегда", начинаешь это "как всегда" гораздо лучше понимать. А сие понимание нам особенно понадобится в следующей лекции, когда мы вплотную приступим к реализации множественного наследования. В конце концов, если вы не любите головоломные задачи, зачем же вы пошли в программисты?
Пример наследования от Number
Давайте возьмем кусочек приведенного в этой лекции примера наследования и заменим при создании прототипа new Object() на new Number(5) (число 5 здесь, очевидно, выбрано случайно). Посмотрим на две вещи: работает ли наследование и сохранились ли у наследников свойства Number. Пишем следующий код:
superclass = function(){} superclass.prototype = new Number(5); superclass.prototype.say = function(text){trace(text + "superclass")} subclass1 = function(){super.say();} subclass1.prototype = new superclass(); subclass1.prototype.say = function(text){trace(text + "subclass1")} a = new subclass1(); trace("---------------"); a.say("type = "); trace("\nНаследование работает. Теперь тестируем, "); trace("сохранились ли у наследников свойства Number.\n"); x = new superclass(); trace("x + 4 = " + (x + 4)); trace("x = " + x); trace("Теперь выводим просто х с помощью trace(x)"); trace(x); trace("\nА теперь смотрим, как ведет себя x.__proto__"); trace("x.__proto__ = " + x.__proto__); trace("x.__proto__ + 4 = " + (x.__proto__ + 4));
На выходе немедленно получаем:
superclass --------------- type = subclass1 Наследование работает. Теперь тестируем, сохранились ли у наследников свойства Number. x + 4 = 4 x = Теперь выводим просто х с помощью trace(x) [type Object] А теперь смотрим, как ведет себя x.__proto__ x.__proto__ = 5 x.__proto__ + 4 = 9
Видим, что наследование работает, а вот свойства Number у наследников никак не проявляются. (На всякий случай мы проверили, что у прототипа все в порядке с этими свойствами). Заметьте, что для наследования мы создавали объект Number при помощи new. Только так мы можем получить не read-only объект типа Number, в который можно добавить функции.
Наследование от Function
Для начала, конечно, неплохо бы сообразить, что может нам дать подобное наследование. Оказывается, существуют реальные задачи, в которых от него можно получить пользу. В первую очередь это те задачи, в которых мы передаем свои функции для вызова их уже готовой подсистемой. Мы уже обсуждали два важных примера подобного рода: функции реакции на события и математические функции, используемые для построения графиков. Как в том, так и в другом случае может возникнуть надобность в построении сложной иерархии объектов. Представим себе, к примеру, что речь идет о параметризованных функциях, причем объект-функция может находиться в различных состояниях, определяемых упомянутыми параметрами. И переходы между этими состояниями могут инициироваться при помощи вызова методов объекта-функции. В данном случае мы попробуем сделать небольшой кусочек иерархии объектов, которую можно было бы использовать при конструировании выражений, зависящих от некоторого аргумента (чтобы зат ем передавать эти выражения в подсистему для построения графиков, например). При этом важно, чтобы объекты, с которыми мы имеем дело, можно было передавать для вызова в соответствующую подсистему наряду с обыкновенными функциями. Функциями, созданными как "вручную", так и уже имеющимися, скажем, в объекте Math. Так что действительно здесь уместно наследование от функции.
Иерархия у нас будет такая: базовый объект, представляющий собой двуместный оператор (или выражение из двух частей, связанное этим оператором). От него, при помощи конкретизации вида оператора будут произведены наследники (в данном случае - выражения со сложением и с делением). И уже от них - конкретные классы-функции, в которых заданы первая и вторая части выражения.
Нужно заметить, что нас поджидают две сложности. Первая: оператор new не может создать новый объект типа Function. К счастью, вместо него можно использовать оператор function, создающий функции "на лету". Вторая сложность: до производного объекта-функции нужно добраться в момент вызова самой базовой функции (не ее методов) - иначе мы не сможем реализовать полиморфизм. Цепочка __proto__, даже правильно выстроенная, нам здесь не поможет: она обеспечивает работу this, но сама ссылка this внутри функции указывает не на объект этой функции, а на объект, к которому она принадлежит. Так что ссылку на производную функцию-объект в базовую нужно передавать "вручную". К счастью, этот механизм мы можем встроить в момент создания нового объекта в функции newFunc, которая в этой программе будет выполнять роль оператора new в пр именении к функциям.
Вот код, который реализует изложенные выше идеи:
/* ==================================================== */ /* === Функция для создания новых объектов-функций ====*/ /* ==================================================== */ // Функция, аналогичная оператору new // Передавать второй аргумент необязательно, // это делается здесь только в отладочных целях _global.newFunc = function(somefunc, name){ var f = function(){ // Мы должны передать исходный объект // (ведь у нас нет здесь полиморфного this) var newArgs = arguments; // Проверяем, что мы в самом дальнем подклассе if (arguments[arguments.length - 1] != "Derived class arg passed") // И передаем в аргументах ссылку на себя // и информацию об этом newArgs = arguments.concat( arguments.callee, "Derived class arg passed" ); // При реальном использовании надо убрать эти // отладочные вызовы trace("------------"); trace("function: " + arguments.callee.name); trace("arguments = " + arguments); trace("newArgs = " + newArgs); trace("------------"); // Вызываем исходную функцию для нашего объекта return somefunc.apply(this, newArgs); } // Копируем все поля и подобъекты, которые есть у нашей // функции (это тоже могут быть функции) copyObj(f, somefunc); // В отладочных целях присваиваем строковое поле с именем if (name != undefined) f.name = name; return f; } /* ==================================================== */ /* ========= Важная утилита - функция копирования ===== */ /* ===================================================== */ // Усовершенствованная функция рекурсивного копирования объектов _global.copyObj = function(toObj, fromObj){ for (var fieldName in fromObj){ var type = typeof(fromObj[fieldName]); // Ссылки на объекты требуют копирования этих объетов if (type == "object" ){ // клипы игнорируем if ((fromObj[fieldName]) instanceof Array){ // Если это массив, его надо скопировать с помощью slice toObj[fieldName] = fromObj[fieldName].slice( 0, fromObj[fieldName].length ); } else{ // Иначе вызываем copyObj рекурсивно toObj[fieldName] = {}; copyObj(toObj[fieldName], fromObj[fieldName]); } } else if (type == "function"){ // Объекты-функции копируем отдельно toObj[fieldName] = newFunc(fromObj[fieldName]); } else{ // Ссылки на примитивные объекты toObj[fieldName] = fromObj[fieldName]; } } } /* ==================================================== */ /* ========= Определяем базовую класс-функцию ========= */ /* ==================================================== */ // Исходная ("базовая") функция-объект _global.twoExprFunc = function(x, funcThis, derPassed){ // Выводим отладочную информацию о том, куда мы попали trace("function: " + arguments.callee.name); trace("arguments = " + arguments); trace("Original object: = " + funcThis.name); // Исходный объект подкласса. Если сюда не передали // информацию о производном классе-функции, для которого // функция вызвана, значит она вызвана сама по себе. if (funcThis == undefined) var funcThis = arguments.callee; // Вместо this, который в функциях работает в расчете // на обычные объекты, используем полученный в аргументах // указатель funcThis - указатель на реальный подкласс, // для которого внешний код произвел вызов функции. return funcThis.resFunc(funcThis.firstExpr(x), funcThis.secondExpr(x)); } // Устанавливаем отладочную строчку с именем вручную, поскольку // эту функцию мы не создавали с помощью newFunc twoExprFunc.name = "twoExprFunc"; // Методы базового объекта-функции twoExprFunc.setResFunction = function(resFunc){ trace("********** settingResFunction: " + resFunc.name + " ***********"); // Выводим пустую строку, чтобы отделить от вывода других функций trace(""); this.resFunc = resFunc; } // Строчку с именем ставим вручную twoExprFunc.setResFunction.name = "setResFunction"; twoExprFunc.setExpressionsFuncs = function(firstExpr, secondExpr){ trace("************* settingExpressions **************"); // Выводим пустую строку, чтобы отделить от вывода других функций trace(""); this.firstExpr = firstExpr; this.secondExpr = secondExpr; } // Строчку с именем ставим вручную twoExprFunc.setExpressionsFuncs.name = "setExpressionsFuncs"; /* ==================================================== */ /* ========= Определяем производные класс-функции ===== */ /* ===================================================== */ plusFunc = newFunc(twoExprFunc, "plusFunc"); var resFuncPlus = function(a, b){ return a + b; } // Строчку с именем ставим вручную resFuncPlus.name = "resFuncPlus"; plusFunc.setResFunction(resFuncPlus); divFunc = newFunc(twoExprFunc, "divideFunc"); var resFuncDiv = function(a, b){ return a / b; } // Строчку с именем ставим вручную resFuncDiv.name = "resFuncDiv"; divFunc.setResFunction(resFuncDiv); particularFunc = newFunc(plusFunc, "particularFunc"); particularFunc.setExpressionsFuncs(Math.round, Math.sin); // В результате particularFunc вычисляет sin(x) + round(x) anotherFunc = newFunc(divFunc, "anotherFunc"); anotherFunc.setExpressionsFuncs(Math.abs, function(x) {return x*x*x}); // В результате anotherFunc вычисляет sign(x)/x^2, где sign(x) // дает 1 для положительных чисел и -1 - для отрицательных. // В случае x == 0 наша anotherFunc не определена (NaN). /* ==================================================== */ /* =========== Проверка работоспособности ============= */ /* ==================================================== */ trace( ":::::::::::::::::::: particularFunc(Math.PI/2) = " + particularFunc(Math.PI/2) + " ::::::::::::::::::::" ); // Аргумент здесь чуть больше полутора, синус его равен 1, // так что в результате должно получиться 3. trace(""); // Выводим пустую строку, чтобы отделить от вывода других функций trace( ":::::::::::::::::::: anotherFunc(-2) = " + anotherFunc(-2) + " ::::::::::::::::::::" ); // Должно получиться -1/4.7.1.
Запускаем этот код. Он выводит в консоль довольно много отладочной информации, попробуем в ней разобраться. Вот содержимое консоли после запуска:
------------ function: setResFunction arguments = [type Function] newArgs = [type Function],[type Function],Derived class arg passed ------------ ********** settingResFunction: resFuncPlus *********** ------------ function: setResFunction arguments = [type Function] newArgs = [type Function],[type Function],Derived class arg passed ------------ ********** settingResFunction: resFuncDiv *********** ------------ function: setExpressionsFuncs arguments = [type Function],[type Function] newArgs = [type Function],[type Function],[type Function],Derived class arg passed ------------ ------------ function: setExpressionsFuncs arguments = [type Function],[type Function],[type Function],Derived class arg passed newArgs = [type Function],[type Function],[type Function],Derived class arg passed ------------ ************* settingExpressions ************** ------------ function: setExpressionsFuncs arguments = [type Function],[type Function] newArgs = [type Function],[type Function],[type Function],Derived class arg passed ------------ ------------ function: setExpressionsFuncs arguments = [type Function],[type Function],[type Function],Derived class arg passed newArgs = [type Function],[type Function],[type Function],Derived class arg passed ------------ ************* settingExpressions ************** ------------ function: particularFunc arguments = 1.5707963267949 newArgs = 1.5707963267949,[type Function],Derived class arg passed ------------ ------------ function: plusFunc arguments = 1.5707963267949,[type Function],Derived class arg passed newArgs = 1.5707963267949,[type Function],Derived class arg passed ------------ function: twoExprFunc arguments = 1.5707963267949,[type Function],Derived class arg passed Original object: = particularFunc ------------ function: resFuncPlus arguments = 2,1 newArgs = 2,1,[type Function],Derived class arg passed ------------ :::::::::::::::::::: particularFunc(Math.PI/2) = 3 :::::::::::::::::::: ------------ function: anotherFunc arguments = -2 newArgs = -2,[type Function],Derived class arg passed ------------ ------------ function: divideFunc arguments = -2,[type Function],Derived class arg passed newArgs = -2,[type Function],Derived class arg passed ------------ function: twoExprFunc arguments = -2,[type Function],Derived class arg passed Original object: = anotherFunc ------------ function: resFuncDiv arguments = 2,-8 newArgs = 2,-8,[type Function],Derived class arg passed ------------ :::::::::::::::::::: anotherFunc(-2) = -0.25 ::::::::::::::::::::7.2.
Разбирать содержимое консоли начнем с конца. Видим, что нужный результат для anotherFunc получен, и что были последовательно совершены вызовы основной функции, начиная от объекта-функции anotherFunc, через divideFunc и заканчивая twoExprFunc. Та в свою очередь вызвала определенный в производном классе divideFunc метод resFunc (а именно, конкретную функцию resFuncDiv ). Причем, поскольку этот метод вызывался на самом деле через самого последнего наследника, то есть через anotherFunc, то была вызвана копия, сделанная при помощи newFunc (отсюда и отладочная информация), а эта копия уже, в свою очередь, вызвала оригинальный метод.
Примерно то же самое произошло и при вызове particularFunc, который тоже дал правильное значение. Заметим, что вызовы setResFunction и setExpressionsFuncs также предваряются выводом большого количества отладочной информации, так как эти методы были изначально заведены еще в самом базовом классе-функции и затем копировались в наследники при помощи newFunc.
Естественно, когда мы сталкиваемся с таким большим числом скопированных функций, сразу возникает вопрос, который мы вынесли в заголовок следующего подпараграфа.