Январь 2011

Руководство по actionscript. часть 1, стр. 100

// Создать интервал, выполняющий функцию doSomething( ) каждые // 50 миллисекунд. Присвоить возвращаемый идентификатор интервала // переменной interval ID. var interval ID = set Interval(doSomething. 50);

// …Далее в программе прекратить автоматический вызов функции // doSomething( ) clearlnterval(interval ID);

Класс Timer, рассматриваемый в разд. «Пользовательские события» гл. 12 и разд. «Создание

$ 1 щ анимации с использованием события TimerEvent.71MER» гл. 24, предоставляет гораздо более широкие возможности управления периодическим выполнением функций или методов.

В следующем коде продемонстрирован простейший класс Clock, который выводит отладочное сообщение «Tick! » один раз в секунду. Обратите внимание на использование литерала функции и собственных функций set Interval ( ) и trace ( ).

package { import flash. utils. set Interval;

public class Clock { public function Clock ( ) { // Выполнять литерал функции один раз в секунду

ниже:

setlnterval(function ( ) {

traceC’Tick!»); }. 1000):

}

}

}

Стоит отметить, что литералы функций используются исключительно ради удобства. Предыдущий код можно легко переписать с помощью вложенной функции, как показано далее:

package { import flash. utils. setlnterval;

public class Clock { public function Clock ( ) { // Выполнять функцию tick( ) один раз в секунду setlnterval(tick. 1000);

function tick ( ):void { traceC’Tick 1″);

}

}

}

}

Можно утверждать, что версия класса Clock, реализованная с помощью вложенной функции, легче для чтения. Литералы широко используются при связывании функций с динамическими переменными экземпляра — этот вопрос рассматривается в разд. «Динамическое добавление нового поведения в экземпляр» гл. 15.

Рекурсивные функции

Рекурсивная функция — это функция, вызывающая саму себя. Следующий код демонстрирует простейший пример рекурсии. Всякий раз при выполнении функции trouble ( ) происходит ее повторный вызов:

function trouble ( ) { troubleC );

}

Если рекурсивная функция безусловно вызывает саму себя, как функция trouble ( ) в нашем случае, возникает бесконечная рекурсия (то есть такое состояние, когда функция не прекратит вызывать саму себя никогда). Без проверки условия бесконечная рекурсия теоретически привела бы к тому, что программа оказалась бы в бесконечном циклическом процессе выполнения функции. На практике, чтобы избежать подобной ситуации, рекурсивные функции вызывают сами себя только при выполнении заданного условия. Одним из классических примеров применения рекурсии является вычисление факториала, который представляет собой произведение всех целых положительных чисел, меньших либо равных данному числу. Например, факториал числа 3 (на математическом языке это записывается как 3!) равен 3 х 2 х 1, то есть 6. Факто-

риал числа 5 равен 5x4x3x2x1, то есть 120. В листинге 5.1 продемонстрирована функция для вычисления факториала числа, реализованная с помощью рекурсии.

Листинг 5.1. Вычисление факториалов с помощью рекурсии

function factorial (n) { if (n < 0) {

return; // Неправильное число, завершаем работу } else if (n <= 1) {

return 1; } else {

return n * factorial(n-1);

}

// Использование в программе; factorial(3); // Возвращает: 6 factorial(5); // Возвращает: 120

Вычислить факториал можно и с помощью цикла, полностью заменяющего рекурсию, как показано в листинге 5.2.

Листинг 5.2. Вычисление факториала без использования рекурсии

function factorial (n) { if (n < 0) {

return; // Неправильное число, завершаем работу } else { var result = 1;

for (var i = 1; i <= n; i++) { result = result * i;

}

return result;

}

}

В листингах 5.1 и 5.2 представлены два различных способа решения одной задачи. Рекурсивный подход гласит: «Факториал числа 6 равен числу 6, умноженному на факториал числа 5. Факториал числа 5 равен числу 5, умноженному на факториал числа 4…» и т. д. Нерекурсивный подход предполагает выполнение цикла для чисел от 1 до я, где происходит перемножение этих чисел, в результате чего получается одно большое число.

Использование рекурсивных функций считается элегантным подходом, поскольку оно обеспечивает простое решение сложных задач — циклический вызов одной и той же функции. Тем не менее повторяющиеся вызовы функции являются менее эффективными, чем итерации цикла. Нерекурсивный подход, применяемый для вычисления факториалов, во много раз эффективнее рекурсивного. Кроме того, нерекурсивный подход исключает достижение максимальной глубины рекурсии, равной по умолчанию 1000, которая, однако, может быть изменена аргументом компилятора def ault-script-limit s.

Руководство по actionscript. часть 1, стр. 101

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

Использование функций в программе «Зоопарк»

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

Если помните, в последней версии нашей программы по созданию виртуального зоопарка животные могли только употреблять пищу (то есть накапливать калории), но не переваривать ее (то есть терять калории). Чтобы наши животные могли переваривать пищу, мы добавим новый метод digest ( ) в класс VirtualPet. Метод digest ( ) будет вычитать калории из того объекта VirtualPet, над которым осуществляется вызов данного метода. Чтобы имитировать переваривание пищи в течение времени, мы создадим интервал, используемый для вызова метода digest ( ) один раз в секунду. Количество калорий, потребляемых при каждом вызове метода digest ( ), будет определяться новой статической переменной caloriesPerSecond. Присвоим переменной caloriesPerSecond значение 100, позволяя животному прожить максимум 20 секунд на «полный желудок».

Следующий код демонстрирует описание переменной caloriesPerSecond: private static var caloriesPerSecond = 100;

Далее представлено описание метода digest ( ). Обратите внимание, что, поскольку переваривание пищи является внутренним процессом, метод digest ( ) объявлен с использованием модификатора управления доступом private.

private function digest ( ) { currentCalories -= VirtualPet. caloriesPerSecond;

}

Чтобы создать интервал, вызывающий метод digest ( ) один раз в секунду, воспользуемся собственной функцией setlnterval ( ). Каждое животное должно приступать к перевариванию пищи сразу после его создания, поэтому поместим вызов функции setlnterval ( ) в метод-конструктор класса Virtual Pet. Кроме того, сохраним идентификатор интервала, возвращаемый функцией setlnterval ( ), в новой переменной экземпляра digestlntervallD, чтобы в дальнейшем при необходимости можно было удалить созданный интервал.

Следующий код демонстрирует описание переменной digestlntervallD: private var digestlntervallD;

Измененный конструктор класса VirtualPet выглядит так:

public function VirtualPet (name) { setName(name);

// Вызывать метод digest( ) один раз в секунду digestlntervallD — setlnterval(digest, 1000);

}

Теперь, когда объекты класса VirtualPet могут переваривать пищу, воспользуемся глобальной функцией t race ( ), чтобы сообщать о текущем состоянии каждого животного в процессе отладки. Будет выдаваться сообщение о состоянии всякий

раз при выполнении методов digest ( ) или eat ( ). Обновленная версия метода digest ( ) выглядит следующим образом:

private function digest ( ) { currentCalories -= VirtualPet. caloriesPerSecond:

trace(getName( ) + » digested some food. It now has » + currentCalories + » calories remaining.»);

}

Обновленная версия метода eat ( ):

public function eat (numberOfCalories) { var newCurrentCalories = currentCalories + numberOfCalories; if (newCurrentCalories > VirtualPet. maxCalories) {

currentCalories = VirtualPet. maxCalories; } else {

currentCalories = newCurrentCalories;

}

trace(getName( ) + » ate some food. It now has » + currentCalories + » calories remaining.»);

}

Если бы мы запустили нашу программу «Зоопарк» прямо сейчас, то увидели бы следующие сообщения в окне Output (Вывод) (среда разработки Flash) или Console (Консоль) (Flex Builder):

Stan digested some food. It now has 900 calories remaining. Stan digested some food. It now has 800 calories remaining. Stan digested some food. It now has 700 calories remaining. Stan digested some food. It now has 600 calories remaining. Stan digested some food. It now has 500 calories remaining. Stan digested some food. It now has 400 calories remaining. Stan digested some food. It now has 300 calories remaining. Stan digested some food. It now has 200 calories remaining. Stan digested some food. It now has 100 calories remaining. Stan digested some food. It now has 0 calories remaining. Stan digested some food. It now has -100 calories remaining. Stan digested some food. It now has -200 calories remaining. Stan digested some food. It now has -300 calories remaining.

Руководство по actionscript. часть 1, стр. 102

Но в чем же дело? Количество калорий у животных не должно принимать отрицательных значений. Вместо этого животные должны умирать, когда значение переменной currentCalories достигнет 0. В нашей программе состояние смерти будет выражаться следующим образом.

? Если значение переменной currentCalories равно 0, программа будет игнорировать любые попытки увеличить значение переменной currentCalories путем вызова метода eat ( ).

? Когда значение переменной currentCalories станет равным 0, программа удалит интервал, с помощью которого вызывается метод diges t ( ), и отобразит сообщение о «смерти животного».

Сначала модифицируем метод еа t ( ). С поставленной задачей должен справиться простой условный оператор:

public function eat (numberOfCalories) { // Если это животное мертво, if (currentCalories = 0) {

// …завершить метод, не изменяя значение переменной

// currentCalories

trace(getName( ) + » is dead. You can’t feed it.»); return;

}

var newCurrentCalories = currentCalories + numberOfCalories; if (newCurrentCalories > Virtual Pet. maxCalories) {

currentCalories = VirtualPet. maxCalories; } else {

currentCalories = newCurrentCalories;

}

trace(getName( ) + » ate some food. It now has » + currentCalories + » calories remaining.»);

}

Теперь мы должны остановить процесс вызова метода diges t ( ), когда значение переменной currentCalories достигнет 0. Для этого воспользуемся функцией flash. utils. clearlnterval ( ):

private function digest ( ) { // Если в результате потребления очередной порции калорий значение // переменной currentCalories станет равным 0 или меньше… if (currentCalories — VirtualPet. caloriesPerSecond <= 0) {

// …прекратить вызов метода digest( )

clearlnterval(digestlntervallD);

// После чего очистить желудок животного

currentCalories = 0;

// и сообщить о смерти животного

trace(getName( ) + » has died.»); } else {

// …иначе употребить оговоренное количество калорий currentCalories -= VirtualPet. caloriesPerSecond;

// и сообщить о новом состоянии животного trace(getName( ) + » digested some food. It now has » + currentCalories + » calories remaining.»);

}

}

В листинге 5.3 представлен целиком весь код класса VirtualPet, включая все внесенные изменения.

Листинг 5.3. Класс VirtualPet

package zoo { import flash. utils. set Interval; import flash. utils. clearInterval;

internal class VirtualPet { private static var maxNameLength = 20; private static var maxCalories = 2000; private static var caloriesPerSecond = 100;

private var petName;

private var currentCalories = VirtualPet. maxCalories/2; private var digestlntervallD;

public function VirtualPet (name) { setName(name);

digestlntervallD = setlnterval (digest. 1000);

}

public function eat (numberOfCalories) { if (currentCalories == 0) { trace(getName( ) + » is dead. You can’t feed it.»); return;

}

var newCurrentCalories = currentCalories + numberOfCalories; if (newCurrentCalories > Virtual Pet. maxCalories) {

currentCalories = VirtualPet. maxCalories; } else {

currentCalories = newCurrentCalories;

}

trace(getName( ) + » ate some food. It now has » + currentCalories + » calories remaining.»);

}

public function getHunger ( ) { return currentCalories / Virtual Pet. maxCalories;

}

public function setName (newName) { // Если длина заданного нового имени больше maxNameLength // символов…

if (newName. length > Virtual Pet. maxNameLength) { // …обрезать имя

newName = newName. substr(0. VirtualPet. maxNameLength); } else if (newName == «») { // …в противном случае, если заданное новое имя является // пустой строкой, завершить выполнение метода, не изменяя // значения переменной petName return;

}

// Присвоить новое, проверенное имя переменной petName petName = newName;

public function getName ( ) { return petName;

}

private function digest ( ) { // Если в результате потребления очередной порции калорий значение // переменной currentCalories животного станет равным 0 или меньше… if (currentCalories — Virtual Pet. caloriesPerSecond <= 0) {

// …прекратить вызов метода digest( )

clearlnterval(digestlntervalID);

// После чего очистить желудок животного

currentCalories = 0;

// и сообщить о смерти животного

trace(getName( ) + » has died.»); } else {

// …иначе употребить оговоренное количество калорий currentCalories -= VirtualPet. caloriesPerSecond;

// и сообщить о новом состоянии животного trace(getName( ) + » digested some food. It now has + currentCalories + » calories remaining.»);

Обратно к классам

Мы рассмотрели функции в языке ActionScript. В следующей главе мы вернемся к теме классов, уделив особое внимание наследованию при создания отношений между двумя или более классами. Разобравшись с вопросами наследования, мы сможем подготовить нашу программу по созданию виртуального зоопарка для компиляции и выполнения.

ГЛАВА 6

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

В объектно-ориентированном программировании термин «наследование» обозначает формальное отношение между двумя или более классами, при котором один класс заимствует (или наследует) описания переменных и методов другого класса. С практической, или технической, точки зрения наследование просто позволяет использовать код одного класса в другом классе.

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

Руководство по actionscript. часть 1, стр. 103

Изучение этой главы мы начнем с рассмотрения синтаксиса и примеров использования наследования. Разобравшись с наследованием с практической точки зрения, мы рассмотрим его преимущества и альтернативы. И наконец, мы применим наследование к нашей программе по созданию виртуального зоопарка.

Пример наследования

Рассмотрим простой абстрактный пример, чтобы получить общее представление о том, как работает наследование (примеры практического применения наследования будут рассмотрены после того, как мы познакомимся с базовым синтаксисом). Существует класс А с одним методом экземпляра m ( ) и одной переменной экземпляра v:

public class А { public var v = 10;

public function m ( ) { trace(«Method m( ) was called»);

}

}

Как обычно, мы можем создать экземпляр класса А, вызвать метод m ( ) и обратиться к переменной v следующим образом:

var alnstance = new A( );

alnstance. m( ); // Выводит: Method m( ) was called trace(alnstance. v); // Выводит: 10

Пока ничего нового. Теперь добавим второй класс В, который наследует метод m ( ) и переменную v класса А. Для создания отношения наследования между классами А и В используется ключевое слово extends:

public class В extends А { // Никакие методы и переменные не определены

}

Поскольку класс В расширяет (унаследован от) класс А, экземпляры класса В могут автоматически использовать метод m ( ) и переменную v (даже несмотря на то, что в самом классе В этот метод и переменная не определены):

var bInstance:B = new В( );

blnstance. m( ); // Выводит: Method m( ) was called trace(blnstance. v); // Выводит: 10

При выполнении инструкции blnstance. m( ) среда Flash проверяет, определен ли метод m ( ) в классе В. Не найдя метода m ( ) в классе В, Flash продолжает его поиск в суперклассе класса В (то есть в том классе, который расширяется классом В). Среда выполнения находит метод m ( ) в классе А и вызывает его над переменной blnstance.

Обратите внимание, что в самом классе В не определены никакие методы или переменные. На практике определение класса, не добавляющего ничего нового в расширяемый класс, не имеет большого смысла, поэтому, как правило, это делать не рекомендуется. В обычном же случае, помимо методов и переменных, унаследованных от класса А, класс В определял бы свои собственные методы и/или переменные. Иными словами, подкласс на самом деле представляет собой расширенный набор возможностей, доступных в его суперклассе. Подкласс обладает всем, что доступно в суперклассе, а также дополнительными возможностями. Рассмотрим новую версию класса В, унаследовавшего метод m ( ) и переменную v от класса А и определяющего свой собственный метод п ( ):

Руководство по actionscript. часть 1, стр. 104

Public class В extends А { public function n ( ) { trace(«Method n( ) was called»);

}

}

Теперь экземпляры класса В могут использовать все методы и переменные не только класса В, но и его суперкласса А:

var blnstance = new В( );

// Вызов унаследованного метода, определенного в классе А blnstance. m( ); // Выводит: Method m( ) was called // Вызов метода, определенного в классе В blnstance. п( ); // Выводит: Method n( ) was called // Обращение к унаследованной переменной trace(blnstance. v); // Выводит: 10

В данном случае говорят, что класс В специализирует класс А. Класс В использует возможности класса А в качестве своей основы, добавляя свои собственные возможности или даже — как мы увидим далее — перекрывая возможности класса А измененными для собственных нужд версиями. Таким образом, в отношении наследования между двумя классами расширяемый класс (в нашем случае А) называется базовым, а расширяющий класс (в нашем случае В) — производным. Иногда для обозначения базового и производного классов также используются понятия «предок» и «потомок» соответственно.

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

public class С extends В { public function о ( ) { trace(«Method o( ) was called»);

}

}

// Использование:

var clnstance = new C( );

// Вызов метода, унаследованного от класса А

clnstance. m( ); // Выводит: Method m( ) was called

// Вызов метода, унаследованного от класса В

clnstance. п( ); // Выводит: Method n( ) was called

// Вызов метода, определенного в классе С

clnstance. о( ); // Выводит: Method о( ) was called

// Обращение к переменной, унаследованной от класса А.

trace(clnstance. v); // Выводит: 10

Более того, каждый суперкласс может иметь любое количество подклассов (однако у суперкласса нет никакой возможности узнать, какие подклассы расширяют его возможности). Следующий код добавляет в наш пример четвертый класс D. Как и В, класс D наследуется непосредственно от А. Класс D может использовать методы и переменные, определенные в нем самом и в его суперклассе А.

Руководство по actionscript. часть 1, стр. 105

Public class D extends A { public function p ( ) { trace(«Method p( ) was called»);

}

}

Четыре класса из нашего примера образуют так называемое дерево наследования, или иерархию классов. Наглядно эта иерархия представлена на рис. 6.1. Обратите внимание, что подкласс не может иметь более одного непосредственного суперкласса.

Все приложения, разработанные с использованием объектно-ориентированного подхода, могут быть описаны с помощью диаграммы классов наподобие той, что

изображена на рис. 6.1. На самом деле многие разработчики перед тем, как приступить к написанию кода, создают диаграммы классов. Эти диаграммы могут быть неформальными, нарисованными в соответствии с собственной иконографией разработчика, или формальными, нарисованными в соответствии со спецификацией изображений диаграмм, к которой относится, например, язык UML (Unified Modeling Language) (дополнительную информацию можно получить по адресу http://www. uml. org).

V

т()

Рис. 6.1. Иерархия классов

Аналогично тому, как мы разрабатываем собственные иерархии классов для наших приложений, реализуемых с помощью объектно-ориентированного подхода, язык ActionScript тоже организует свои собственные классы в соответствии с иерархией. На самом деле любой класс в языке ActionScript (как собственный, так и пользовательский) унаследован прямо или косвенно от корневого элемента внутренней иерархии языка — класса Object. Класс Object определяет несколько базовых методов и переменных, доступных всем классам через наследование. Например, любой класс может воспользоваться методом Ob j ect. toString ( ), возвращающим строковое представление объекта.

Статические методы и статические переменные не наследуются. В отличие от методов и переменных экземпляра, подкласс не наследует статические методы и статические переменные своего суперкласса.

Например, в следующем коде мы определяем статический метод s ( ) в классе А. Метод s ( ) не наследуется подклассом В класса А, и, следовательно, к этому методу нельзя обратиться в виде В. s ( ).

public class А { public static function s ( ) { trace(«A. s( ) was called»);

}

}

public class В extends A { public function В ( ) { B. s( ); // Ошибка! Недопустимая попытка обращения // к методу A. s( ) через класс В

Тем не менее в теле любого из классов А или в к статическим методам и переменным, определенным в классе А, можно обращаться непосредственно, не указывая имя класса, например s ( ) вместо А. s ( ). Но несмотря на это, при обращении к статическим методам или статическим переменным вообще разумно указывать имя класса. Когда указано имя класса, становится совершенно ясно, к какому классу относится метод или переменная.

Руководство по actionscript. часть 1, стр. 106

Перекрытие методов экземпляра

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

Л_

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

щ

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

j&_

Язык ActionScript 3.0 позволяет переопределять методы экземпляра, но не допускает переопределения переменных экземпляра, статических переменных и статических методов.

Чтобы перекрыть метод экземпляра суперкласса, мы должны добавить в подкласс описание метода экземпляра с таким же именем, предварив его ключевым словом override. Например, рассмотрим следующий код, в котором создается класс А с методом экземпляра m ( ):

public class А { // Объявление метода экземпляра в суперклассе public function m ( ) { traceCA’s m( ) was called»);

}

}

Рассмотрим также следующий код, в котором создается класс В, унаследованный от класса А:

// Класс В является подклассом класса А

public class В extends А {

}

Чтобы перекрыть метод m ( ) в классе В, мы используем следующий код:

public class В extends А { // Перекрытие метода суперкласса т( )

override public function m ( ) { traceC’B's m( ) was called»):

}

}

Обратите внимание, что версия метода m ( ) класса В обладает не только таким же именем, как у версии метода класса А, но и таким же модификатором управления доступом (то есть public).

~~ » «

^ I Для успешного перекрытия метода необходимо, чтобы у перекрывающей версии метода м$ 4 * и У перекрываемого метода совпадали имя, модификатор управления доступом, список —параметров и возвращаемый тип (возвращаемые типы будут рассмотрены в гл. 8). В противном случае произойдет ошибка.

Когда метод m ( ) вызывается через экземпляр класса А, среда выполнения Flash использует описание метода из класса А. Однако когда метод m ( ) вызывается через экземпляр класса В, среда Flash использует описание метода из класса В вместо описания из класса А:

var alnstance = new А( );

alnstance. m( ); // Выводит: A’s m( ) was called var blnstance = new B( );

blnstance. m( ); // Выводит: B’s m( ) was called

Рассмотрим более реальный пример. Предположим, что мы разрабатываем геометрическую программу, отображающую прямоугольники и квадраты. Прямоугольники в нашей программе будет представлять класс Rectangle, продемонстрированный в следующем коде:

public class Rectangle { protected var w = 0: protected var h = 0:

public function setSize (newW, newH) { w = newW; h = newH;

}

public function getArea ( ) { return w * h:

}

}

Для представления квадратов в программе мы могли бы создать совершенно независимый класс Square. Однако квадрат на самом деле представляет собой не что иное, как прямоугольник с равными сторонами. Чтобы воспользоваться этим подобием, мы создадим класс Square, расширяющий класс Rectangle, но при этом модифицирующий метод set Size ( ) для предотвращения присваивания значений переменным w и h в тех случаях, когда значения параметров newW и newH не равны между собой. Это ограничение относится только к квадратам, а не к прямоугольникам вообще, поэтому оно не реализуется в классе Rectangle.

Рассмотрим код класса Square, в котором продемонстрирован перекрытый метод setSize ( ):

public class Square extends Rectangle { override public function setSize (newW, newH) { // Это ограничение, накладываемое классом Square if (newW == newH) { w = newW; h = newH;

}

}

}

При вызове метода setSize ( ) через экземпляр класса Square или Rectangle среда выполнения Flash использует ту версию метода, которая соответствует фактическому классу экземпляра. Например, в следующем коде мы вызываем метод s е t S i z е ( ) через экземпляр класса Rectangle. Среда Flash знает, что классом экземпляра является Rectangle, поэтому вызывается версия метода setSize ( ) из этого класса:

Руководство по actionscript. часть 1, стр. 107

Var г = new RectangleC ); r. setSize(4,5):

trace(r. getArea( )); // Выводит: 20

В отличие от этого, в следующем коде мы вызываем метод setSize ( ) через экземпляр класса Square. На этот раз среда выполнения Flash знает, что классом экземпляра является Square, поэтому версия метода setSize ( ) вызывается именно из этого класса, а не из класса Rectangle:

var s = new Square( ): s. setSize(4,5):

trace (s. getArea( )); // Выводит: 0 (Метод setSize( ) предотвращает

// присваивание недопустимых значений)

В предыдущем коде результат вызова метода s. get Area ( ) равен 0. Это говорит о том, что значения переменных w и h не были установлены при вызове метода s. setSize ( ). В версии метода setSize ( ) класса Square значения переменным w и h присваиваются только в том случае, когда значения параметров newW и newH равны между собой.

Вызов перекрытого метода экземпляра. Когда подкласс перекрывает метод экземпляра, версия этого метода, определенная в суперклассе, не теряется. Экземпляры подкласса могут обращаться к этой версии метода посредством оператора super, позволяющего вызывать перекрытый метод следующим образом:

super. имяМетода (аргумент!, аргумент2… аргумент);

В этом коде имяМетода обозначает имя вызываемого перекрытого метода, а аргумент!, аргумент2. . . аргумент — список аргументов, передаваемых в этот метод (другие варианты использования оператора super будут рассмотрены далее в этой главе).

В качестве примера вызова перекрытого метода вернемся к сценарию с классами Square и Rectangle. В предыдущем разделе наш метод Square. setSize ( ) без необходимости дублировал код метода Rectangle. setSize ( ). Рассмотрим версию метода, определенную в классе Rectangle:

public function setSize (newW, newH) { w = newW; h = newH;

}

В версии метода setSize ( ), определенной в классе Square, просто добавлен оператор if:

override public function setSize (newW, newH) { if (newW == newH) { w = newW; h = newH;

}

}

Чтобы избежать дублирования кода, используемого в обоих методах для присваивания значений переменным w и h, мы можем воспользоваться оператором super, как показано в следующей модифицированной версии метода Square. setSize ( ):

override public function setSize (newW, newH) { if (newW == newH) { // Вызов метода setSize( ) суперкласса над текущим экземпляром super. setSize(newW, newH);

}

}

Модифицированный метод setSize ( ) KnaccaSquare проверяет, одинаковы ли значения параметров newW и newH. Если значения одинаковы, то вызывается метод setSize ( ) класса Rectangle над текущим экземпляром. Метод setSize ( ) класса Rectangle позаботится о присваивании значений переменным w и п.

Приведенный пример с методом setSize ( ) демонстрирует, как подкласс может перекрывать метод для ограничения его поведения. Кроме того, подкласс может перекрывать метод для дополнения его поведения. Например, следующий код создает класс ScreenRectangle, являющийся подклассом класса Rectangle и отображающий на экране прямоугольник. Подкласс ScreenRectangle перекрывает метод s е t S i z е ( ), сохраняя поведение перекрываемого метода, но при этом добавляя вызов метода draw ( ), в результате чего размеры прямоугольника на экране изменяются всякий раз при вызове метода s е t S i z е ( ):

public class ScreenRectangle extends Rectangle { override public function setSize (newW, newH) { // Вызов версии метода setSize( ) класса Rectangle super. setSize(newW, newH);

// Теперь отображаем прямоугольник на экране draw( );

}

public function draw ( ) { // Здесь размещается код, отображающий прямоугольник на экране

Перекрытие можно использовать и для аннулирования поведения метода. Методика очень проста: версия перекрытого метода подкласса не выполняет никаких действий. Например, следующий код демонстрирует подкласс ReadOnlyRectangle, блокирующий метод setSize ( ) класса Re с tangle, в результате чего исключается возможность изменения размера для экземпляра этого подкласса:

Руководство по actionscript. часть 1, стр. 108

Public class ReadOnlyRectangle extends Rectangle { // Следующее определение фактически блокирует метод setSizeC ) // для экземляров класса ReadOnlyRectangle. override public function setSize (newW, newH) { // Никаких действий

}

}

Методы-конструкторы в подклассах

Теперь, когда мы рассмотрели поведение методов и переменных экземпляра относительно наследования, обратим наше внимание на методы-конструкторы.

Вспомним, что метод-конструктор инициализирует экземпляры класса следующими способами:

? вызывая методы, которые выполняют задачи настройки;

? присваивая значения переменным созданного объекта.

Когда происходит расширение класса, подкласс может определять собственный метод-конструктор. На конструктор подкласса возлагаются следующие функции:

? выполнять задачи настройки, относящиеся к подклассу;

? присваивать значения переменным, описанным в подклассе;

? вызывать метод-конструктор подкласса (иногда называемый суперконструктором).

Если в подклассе определен метод-конструктор, в нем обязательно должен вызываться конструктор суперкласса с помощью ключевого слова super. Более того, конструктор суперкласса должен вызываться до обращения к любой переменной или методу экземпляра. Если конструктор суперкласса не будет вызван явно, компилятор автоматически добавит вызов конструктора суперкласса без аргументов. И наконец, ключевое слово super не должно использоваться в методе-конструкторе более одного раза.

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

? исключается вызов методов над объектом, который еще не был проинициали-зирован;

? устраняется доступ к переменным объекта, который еще не был проинициали-зирован;

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

Не путайте две разновидности оператора super. Первая разновидность — super() — вызывает метод-конструктор суперкласса. Вторая разновидность — super. имяМетода() — вызывает метод суперкласса. Использование первой разновидности допустимо только в методе-конструкторе. Вторая разновидность может многократно применяться в любом месте метода-конструктора или метода экземпляра.

Рассмотрим применение оператора super для вызова метода-конструктора суперкласса в простейшем случае. Следующий код описывает класс А с пустым методом-конструктором:

public class А { public function А ( ) { }

}

Следующий код описывает класс В, который расширяет класс А. Внутри метода-конструктора класса В мы используем оператор super для вызова метода-конструктора класса А:

public class В extends А { // Конструктор подкласса public function В ( ) {

// Вызов метода-конструктора суперкласса

super( );

}

}

С точки зрения функциональности следующие описания двух методов-конструкторов являются синонимами. В первом случае метод-конструктор суперкласса вызывается явно; во втором случае среда выполнения Flash вызывает метод-конструктор суперкласса неявно.

public function В ( ) { // Явный вызов метода-конструктора суперкласса super( );

}

public function В ( ) { // Вызов конструктора отсутствует. // Среда Flash вызовет конструктор автоматически

}

Если в подклассе метод-конструктор не определен вообще, то компилятор языка ActionScript автоматически создаст метод-конструктор и добавит в него одну инструкцию — вызов оператора super. Следующие два описания класса В функционально являются идентичными. Первое описание в явном виде представляет то, что для второго описания автоматически создает компилятор:

Руководство по actionscript. часть 1, стр. 109

// Определение конструктора в явном виде public class В extends А {

// Явное объявление конструктора

public function В ( ) { // Явный вызов метода-конструктора суперкласса

super( );

}

}

// Позволить компилятору автоматически создать конструктор по умолчанию

public class В extends А {

}

Метод-конструктор подкласса может (а зачастую так и происходит) иметь другие параметры, нежели его коллега из суперкласса. Например, в нашем классе Rectangle можно было бы определить конструктор с параметрами width и height, а класс Square мог бы иметь собственный конструктор с одним параметром side (у квадратов ширина и высота совпадает, поэтому не нужно указывать оба значения). В листинге 6.1 продемонстрирован этот код.

Листинг 6.1. Конструкторы классов Rectangle и Square

public class Rectangle { protected var w = 0; protected var h = 0;

// Конструктор класса Rectangle public function Rectangle (width, height) { setSize(width, height);

}

public function setSize (newW, newH) { w = newW; h = newH;

}

public function getArea ( ) { return w * h;

}

}

public class Square extends Rectangle { // Конструктор класса Square public function Square (side) {

// Передаем параметр side в конструктор класса Rectangle

super(side. side);

}

override public function setSize (newW, newH) { if (newW == newH) { // Вызов метода setSize( ) суперкласса над текущим экземпляром super. setSize(newW, newH);

}

}

}

Кстати, вы могли бы задаться вопросом, а не лучше ли определить метод s е t S i z е ( ) класса Square с одним параметром s ide, чем иметь два отдельных параметра width

и height. Это демонстрирует следующая версия метода setSize ( ) (обратите внимание, что в методе больше не нужна проверка значений параметров newW и newH на равенство).

override public function setSize (side) { // Вызов метода setSize( ) суперкласса над текущим экземпляром super. setSize(side, side);

}

Хотя приведенная версия метода setSize ( ), несомненно, является более подходящей для класса Square, она приведет к ошибке, поскольку имеет меньшее количество параметров, чем версия метода setSize ( ) класса Rectangle (помните, что количество параметров, определенных в перекрывающем методе, должно совпадать с количеством параметров перекрываемого метода). Позднее, в подразд. «Наследование в сравнении с композицией» разд. «Теория наследования», мы рассмотрим альтернативный допустимый вариант реализации версии метода setSize( )с одним параметром в классе Square.

При определении метода-конструктора подкласса обязательно указывайте все требуемые аргументы для конструктора суперкласса. Метод-конструктор класса ColoredBall из следующего примера определен неправильно, поскольку в метод-конструктор суперкласса не передается необходимая информация:

public class Ball { private var r;

public function Ball (radius) { r = radius;

}

}

public class ColoredBall extends Ball { private var c;

// Это проблематичный конструктор… public function ColoredBall (color) {

// Ой! Отсутствует вызов оператора super( ). Здесь произойдет ошибка,

// поскольку в конструктор класса Ball необходимо передать аргумент

// для параметра radius

С = color;

}

}

Далее приводится исправленная версия класса ColoredBall, в которой требуемый аргумент передается в конструктор класса Ball:

public class ColoredBall extends Ball { private var c;

// Все исправлено…

public function ColoredBall (radius, color) { super(radius); с = color;

Обратите внимание, что, если придерживаться хорошего тона программирования, в конструкторе подкласса сначала перечисляются параметры суперкласса (в данном случае radius), а затем дополнительные аргументы конструктора подкласса (в данном случае color).

Исключение возможности расширения классов и перекрытия методов

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

final public class А { }

Поскольку класс А описан с использованием атрибута final, попытка расширить этот класс следующим образом:

public class В extends А { }

завершится ошибкой на этапе компиляции программы:

Base class is final. (Базовый класс является конечным.)

Подобным образом следующий код описывает метод m ( ), не допускающий перекрытия:

public class А { final public function m ( ) { }

}

Поскольку метод m ( ) описан с помощью атрибута f in а 1, попытка перекрыть этот метод следующим образом:

public class В extends А { override public function m ( ) { }

}

завершится ошибкой на этапе компиляции программы:

Cannot redefine a final method. (Невозможно переопределить конечный метод.)

В языке ActionScript атрибут final используется по нескольким причинам.

? В некоторых ситуациях методы, описанные с помощью атрибута final, выполняются быстрее, чем методы, описанные без него. Если вы хотите улучшить производительность вашего приложения всеми возможными способами, попробуйте описать методы, используя атрибут final. Однако стоит отметить, что в будущих версиях среды выполнения Flash корпорация Adobe планирует увеличить скорость выполнения методов, описанных без использования атрибута final, в результате чего она не будет отличаться от скорости выполнения методов, описанных с помощью атрибута final.



Полезные ссылки
Случайные записи
  • 22.03.2011">Руководство по actionscript. часть 2, стр. 017
  • 16.03.2011">Руководство по actionscript. часть 3, стр. 007
  • 23.02.2011">Руководство по actionscript. часть 7, стр. 039
  • 11.05.2010">Самоучитель по креативному веб-дизайну. Книга 1, стр.14
  • 01.03.2011">Руководство по actionscript. часть 5, стр. 124
  • 02.03.2011">Руководство по actionscript. часть 5, стр. 107
  • 22.07.2011">Интернет – кладезь для меломанов
  • 07.03.2011">Руководство по actionscript. часть 4, стр. 114
  • 13.03.2011">Руководство по actionscript. часть 3, стр. 106
  • 08.03.2011">Руководство по actionscript. часть 4, стр. 075
  • 11.05.2010">Самоучитель по креативному веб-дизайну. Книга 1, стр.31
  • 07.03.2011">Руководство по actionscript. часть 4, стр. 105
  • 10.05.2010">Самоучитель по креативному веб-дизайну. Книга 1, стр.53
  • 15.07.2010">Полезные термины для новичков.
  • 23.01.2011">Руководство по actionscript. часть 1, стр. 073
Опрос

Какие цвета вы предпочитаете?

View Results

Loading ... Loading ...