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

Package { import flash. system.*;

public function isLinux ( ) { return Capabilities. os == «Linux»;

}

}

Следующий код демонстрирует модифицированную версию класса Welcome из предыдущего раздела, в котором вместо функции isMac ( ) используется функция isLinux ( ). Обратите внимание, что перед применением функцию импортировать не нужно.

package setup { public class Welcome { public function Welcome ( ) { // Воспользоваться функцией isLinux( ) if (isLinux( )) { // Выполнить специфические для Linux действия

}

}

}

}

Многие функции уровня пакета и глобальные функции являются собственными для каждой отдельно взятой среды выполнения Flash. Список доступных функций можно найти в документации корпорации Adobe по интересуемой среде выполнения Flash.

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

trace (аргумент!, аргумент2… аргумент)

Функция trace ( ) представляет собой простейший инструмент для поиска ошибок в программе (то есть для отладки). Она позволяет выводить указанные аргументы либо в окно среды разработки, либо в файл журнала. Например, при выполнении программы в тестовом режиме в среде разработки Flash с помощью команды Control > Test Movie (Управление > Проверка фильма) результаты всех вызовов функции trace ( ) появятся в окне Output (Вывод). Подобным образом при выполнении программы в тестовом режиме в приложении Flex Builder с помощью команды Run > Debug (Выполнить > Отладка) результаты всех вызовов функции trace ( ) появятся в окне Console (Консоль). Информацию по конфигурированию отладочной версии приложения Flash Player для вывода аргументов функции trace ( ) в текстовый файл можно найти по адресу http://livedocs. macromedia. com/ flex/2/docs/00001531.html.

Вложенные функции

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

или функции, в которых она определена. Следующий код демонстрирует базовый пример вложенной функции b ( ), описанной внутри метода экземпляра а ( ). Вложенная функция b ( ) может быть использована только внутри метода а ( ); за пределами метода а ( ) функция b ( ) недоступна.

// Описание метода а( ) public function а ( ) {

// Вызов вложенной функции Ь( )

Ь( );

// Описание вложенной функции Ь( ) function b ( ) { // Здесь должно размещаться тело функции

}

}

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

Следующий код демонстрирует более реальный пример метода, содержащего вложенную функцию. Метод getRandomPoint ( ) возвращает объект типа Point, который представляет произвольную точку в заданном прямоугольнике. Чтобы получить произвольную точку, этот метод использует вложенную функцию getRandoml ntедег ( ) для вычисления случайных координат по осям X и Y. Обратите внимание, что в функции getRandomlnteger ( ) применяются собственные статические методы Math. random ( ) и Math. floor ( ). Первый метод возвращает случайное число с плавающей запятой, большее либо равное 0, но меньшее 1. Второй метод устраняет дробную часть числа с плавающей запятой. Дополнительную информацию по статическим методам класса Math можно найти в справочнике по языку ActionScript корпорации Adobe.

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

Public function getRandomPoint (rectangle) { var randomX = getRandomlnteger(rectangle. left, rectangle. right); var randomY = getRandomlnteger(rectangle. top. rectangle. bottom);

return new Point(randomX. randomY);

function getRandomlnteger (min. max) { return min + Math. floor(Math. random( )*(max+l — min));

}

}

Функции уровня исходного файла

Если описание функции размещается на верхнем уровне исходного файла за пределами тела пакета, то будет создана функция, доступная только внутри данно-

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

package {

// Функцию f( ) можно использовать здесь class А {

// Функцию f( ) можно использовать здесь public function А ( ) { // Функцию f( ) можно использовать здесь

// Функцию f( ) можно использовать здесь

function f ( ) { }

В предыдущем коде обратите внимание на то, что описание функции f ( ) не содержит и не должно содержать никаких модификаторов управления доступом (publ i с, internal ит. д.).

rtp* 1 Модификаторы управления доступом не должны применяться при описании функций

м$ i« уровня исходного файла.

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

Доступ к описаниям из функции

Место размещения функции в программе влияет на возможность обращения к описаниям этой программы из данной функции (то есть к классам, переменным, методам, пространствам имен, интерфейсам и другим функциям). Подробное описание того, к чему можно и к чему нельзя обращаться из кода функций, можно найти в разд. «Область видимости функций» гл. 16.

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

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

public function m ( ) { var currentObject = this:

function f ( ) { // Здесь можно обращаться к переменной currentObject trace(currentObject): // Отображает объект, через который был // вызван метод т( )

}

}

Функции в качестве значений

В языке ActionScript любая функция представляется экземпляром класса Function. По существу, функция может быть присвоена переменной, передана в функцию или возвращена из нее точно так же, как и любое другое значение. Например, в следующем коде описывается функция а ( ), после чего она присваивается переменной Ь. Обратите внимание, что оператор круглых скобок ( ) опущен; в противном случае переменной b было бы просто присвоено возвращаемое значение функции а ( ).

function а ( ) { }

var b = а;

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

Ь( );

Функции-значения обычно используются при создании динамических классов и объектов, которые рассматриваются в разд. «Динамическое добавление нового поведения в экземпляр» и «Использование объектов-прототипов для дополнения классов» гл. 15.

Синтаксис литералов функций

Экземпляры класса Function, как и многих предопределенных классов языка ActionScript, можно создавать с помощью синтаксиса литералов. Он практически ничем не отличается от синтаксиса стандартного объявления функций, за исключением отсутствующего имени функции. Вот его общий вид:

function (параметр1, параметр2… параметра) { }

Здесь параметр1, параметр2. . . параметра — это необязательный список параметров.

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

var некаяПеременная = function (параметр!, параметр2… параметра) {

После этого вызывать функцию можно через данную переменную, как показано

некаяПеременная (аргумент!, аргумент2… аргумента)

Например, в следующем коде для создания функции, которая возводит число в квадрат, используется литерал функции, а созданная функция присваивается переменной square:

var square = function (n) { return n * n;

Для вызова функции из предыдущего примера применяется следующий код:

// Возводит в квадрат число 5 и возвращает результат square(5)

Литералы функций иногда применяются совместно с собственной функцией flash, utils. set Interval ( ), которая имеет следующий вид:

setlnterval (Фуш/ияйли/^год. задержка)

Функция set Interval ( ) создает интервал, используемый для автоматического вызова указанной функции или метода (ФункцияИлиМетод) каждые задержка миллисекунд. Каждому создаваемому интервалу присваивается число, возвращаемое функцией setlnterval( )и называемое идентификатором интервала. Идентификатор интервала может быть присвоен переменной, что в дальнейшем позволит удалить соответствующий интервал вызовом функции clear Interval ( ), как показано в следующем примере кода:

Руководство по 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 ( ) из этого класса:



Полезные ссылки
Случайные записи
  • 10.05.2010">Самоучитель по креативному веб-дизайну. Книга 1, стр.79
  • 13.03.2011">Руководство по actionscript. часть 3, стр. 098
  • 11.05.2010">Самоучитель по креативному веб-дизайну. Книга 1, стр.6
  • 26.02.2011">Руководство по actionscript. часть 6, стр. 066
  • 03.06.2010">Самоучитель по креативному веб-дизайну. Книга 3, стр.81
  • 16.06.2010">Самоучитель по креативному веб-дизайну. Книга 4, стр.28
  • 07.03.2011">Руководство по actionscript. часть 4, стр. 123
  • 24.02.2011">Руководство по actionscript. часть 6, стр. 110
  • 19.08.2010">Веб-дизайн и реклама
  • 28.02.2011">Руководство по actionscript. часть 6, стр. 001
  • 10.05.2010">Самоучитель по креативному веб-дизайну. Книга 1, стр.59
  • 27.02.2011">Руководство по actionscript. часть 6, стр. 040
  • 21.03.2011">Руководство по actionscript. часть 2, стр. 047
  • 10.05.2010">Самоучитель по креативному веб-дизайну. Книга 1, стр.122
  • 18.05.2010">Самоучитель по креативному веб-дизайну. Книга 2, стр.90
Опрос

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

View Results

Loading ... Loading ...