Дополнительные темы

История объектно-ориентированного программирования на языке ActionScript

Так как язык ActionScript 3.0 создан на основе предыдущих версий ActionScript, будет полезно проследить развитие объектной модели ActionScript. Язык ActionScript сначала представлял собой простой механизм создания скриптов для ранних версий Flash Professional. Впоследствии программисты стали создавать с помощью ActionScript более сложные приложения. В ответ на потребности таких программистов в каждом новом выпуске расширялись возможности языка, упрощающие создание сложных приложений.

ActionScript 1.0

ActionScript 1.0 — это версия языка, используемого во Flash Player 6 и более ранних версиях. С самого начала объектная модель языка ActionScript строилась на базе концепции, согласно которой объект является основополагающим типом данных. Объект ActionScript представляет собой составной тип данных с группой свойств . Применительно к объектной модели термин свойства включает все, что присоединяется к объекту, то есть переменные, функции или методы.

Хотя первое поколение языка ActionScript не поддерживает определение классов с помощью ключевого слова class , класс можно определить с помощью объекта особого типа, так называемого прототипа. Вместо того чтобы определять с помощью ключевого слова class абстрактного класса, из которого затем можно создать конкретные экземпляры, как в языках на базе классов, таких как Java и C++, языки на базе прототипов, такие как ActionScript 1.0, используют в качестве модели (или прототипа) для новых объектов существующий объект. В языках на базе классов объект может указывать на класс, используемый в качестве его шаблона, а в языках на базе прототипа объект указывает на другой объект, свой прототип, который является его шаблоном.

Чтобы создать класс в ActionScript 1.0, необходимо определить функцию-конструктор для этого класса. В ActionScript функции — это не абстрактные определения, а конкретные объекты. Созданная функция-конструктор служит в качестве прототипичного объекта для экземпляров этого класса. Следующий код создает класс Shape и определяет одно свойство visible , которому задается значение по умолчанию true .

// base class 
function Shape() {} 
// Create a property named visible. 
Shape.prototype.visible = true;

Эта функция-конструктор определяет класс Shape и создает его экземпляр с помощью оператора new , как показано ниже.

myShape = new Shape();

Объект функции-конструктора Shape() может служить прототипом для экземпляров не только класса Shape, но и его подклассов, то есть классов, расширяющих класс Shape.

Создание подкласса для класса Shape происходит в два этапа. На первом этапе создается класс посредством определения его функции-конструктора, как показано ниже.

// child class 
function Circle(id, radius) 
{ 
this.id = id; 
this.radius = radius; 
}

На втором этапе с помощью оператора new класс Shape объявляется в качестве прототипа класса Circle. По умолчанию любой создаваемый класс использует в качестве прототипа класс Object, то есть Circle.prototype пока содержит родовой объект (экземпляр класса Object). Чтобы указать, что прототипом класса Circle является класс Shape, а не Object, используйте следующий код, который изменяет значение Circle.prototype так, что оно вместо родового объекта будет содержать объект Shape.

// Make Circle a subclass of Shape. 
Circle.prototype = new Shape();

Теперь классы Shape и Circle связаны друг с другом отношением наследования, часто называемым цепочкой прототипов . На следующей схеме представлены отношения в цепочке прототипов.

Базовым классом, который находится в конце любой цепочки прототипов, является класс Object. Класс Object содержит статическое свойство Object.prototype , которое указывает на базовый прототипичный объект для всех объектов, созданных в ActionScript 1.0. Следующим объектом в примере цепочки прототипов является объект Shape. Свойство Shape.prototype не задавалось явно, поэтому оно по-прежнему содержит родовой объект (экземпляр класса Object). Последняя ссылка в этой цепочке — класс Circle, связанный со своим прототипом, классом Shape (свойство Circle.prototype содержит объект Shape).

Если создается экземпляр класса Circle, как в следующем примере, он наследует цепочку прототипов класса Circle.

// Create an instance of the Circle class. 
myCircle = new Circle();

Вспомним пример, в котором свойство visible использовалось в качестве члена класса Shape. В этом примере свойство visible не существует в объекте myCircle , являясь членом только для объекта Shape. Несмотря на это, следующая строка кода возвращает значение true :

trace(myCircle.visible); // output: true

Среда выполнения может установить, что объект myCircle наследует свойство visible , поднявшись по цепочке прототипов. Выполняя код, среда выполнения сначала ищет свойство visible среди свойств объекта myCircle , но не находит его. После этого она выполняет дальнейший поиск в объекте Circle.prototype , но в нем также не оказывается свойства visible . Поднимаясь выше по цепочке прототипов, она наконец находит свойство visible , определенное в объекте Shape.prototype , и выводит его значение.

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

ActionScript 2.0

В язык ActionScript 2.0 были введены новые ключевые слова, такие как class , extends , public и private , которые позволяют определять классы так, как это привыкли делать все, кто работает с языками Java и C++. Важно понимать, что в основе языков ActionScript 1.0 и ActionScript 2.0 лежит один и тот же механизм наследования. В версии ActionScript 2.0 просто добавлены новые элементы синтаксиса для определения классов. Цепочка прототипов работает одинаково в обеих версиях языка.

Новый синтаксис, введенный в ActionScript 2.0, позволяет определять классы более простым для многих программистов способом, как показано ниже.

// base class 
class Shape 
{ 
var visible:Boolean = true; 
}

Обратите внимание, что в языке ActionScript 2.0 были введены аннотации типов для использования при проверке типов во время компиляции. Это позволяет объявить, что свойство visible в предыдущем примере должно содержать только логическое значение. Новое ключевое слово extends также упрощает процесс создания подклассов. В следующем примере, двухэтапный процесс в ActionScript 1.0 выполняется одним действие с помощью ключевого слова extends .

// child class 
class Circle extends Shape 
{ 
    var id:Number; 
    var radius:Number; 
    function Circle(id, radius) 
    { 
        this.id = id; 
        this.radius = radius; 
        } 
}

Теперь конструктор объявляется в составе определения класса, и свойства класса id и radius также должны быть явно объявлены.

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

Объект класса в ActionScript 3.0

Общая парадигма объектно-ориентированного программирования, которая чаще всего ассоциируется с языками Java и C++, использует классы для определения типов объектов. Языки программирования, применяющие эту парадигму, как правило, используют классы также для создания экземпляров типа данных, определяемого классом. Язык ActionScript использует классы в обеих целях, но, будучи разработанным на базе прототипов, он также имеет еще одну интересную отличительную черту. ActionScript создает для каждого определения класса особый объект класса, который обеспечивает совместное использование поведения и состояния. Однако для многих программистов, работающих с языком ActionScript, это отличие не применяется на практике. Язык ActionScript 3.0 разработан таким образом, что создавать сложные объектно-ориентированные приложения ActionScript можно, не используя этих специальных объектов классов и даже не имея о них никакого понятия.

На следующей схеме показана структура объекта класса, представляющего простой класс A, определенный с помощью инструкции class A {} .

Каждый прямоугольник в схеме представляет объект. Каждый объект в схеме имеет подстрочный символ «А», который указывает на то, что он принадлежит классу А. Объект класса (СА) содержит ссылки на ряд других важных объектов. Объект признаков экземпляра (ТА) хранит свойства экземпляра, объявленные в определении класса. Объект признаков класса (ТСА) представляет внутренний тип класса и хранит статические свойства, определенные классом (подстрочный символ «С» обозначает «класс»). Объектом прототипа (PA) всегда называется объект класса, к которому он изначально был присоединен с помощью свойства constructor .

Объект признаков

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

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

Благодаря объекту признаков, объем памяти, занимаемый объектом, может быть намного меньше, чем у подобных объектов в предыдущих версиях ActionScript. Например, если класс не объявлен как динамический (то есть является фиксированным), его экземпляру не требуется хэш-таблица для динамически добавляемых свойств, и он может содержать лишь указатель на объекты признаков и несколько слотов для фиксированных свойств, определенных в классе. В результате, объект, который требовал 100 байтов памяти в ActionScript 2.0, в версии ActionScript 3.0 будет требовать лишь 20 байтов.

Примечание. Объект признаков — это внутренняя деталь реализации. Нельзя гарантировать, что он не изменится или совсем не исчезнет в будущих версиях ActionScript.

Объект прототипа

Каждый объект класса ActionScript имеет свойство prototype , которое представляет собой на объект прототипа этого класса. Объект прототипа является наследием ActionScript как языка на базе прототипов. Дополнительные сведения см. в разделе «История объектно-ориентированного программирования на языке ActionScript».

Свойство prototype предназначено только для чтения: его нельзя изменить так, чтобы оно указывало на другие объекты. А в предыдущих версиях ActionScript свойство prototype можно было переназначить, чтобы оно указывало на другой класс. Хотя свойство prototype доступно только для чтения, это ограничение не распространяется на указываемый им объект прототипа. Другими словами, к объекту прототипа можно добавлять новые свойства. Свойства, добавляемые к объекту прототипа, совместно используются всеми экземплярами класса.

Цепочка прототипов, которая являлась единственным механизмом наследования в предыдущих версиях ActionScript, выполняет лишь второстепенную роли в ActionScript 3.0. Основной механизм наследования, то есть наследование фиксированных свойств, осуществляется на внутреннем уровне объектом признаков. Фиксированное свойство — это переменная или метод, объявленный как часть определения класса. Наследование фиксированных свойств также называется наследованием класса, так как этот механизм наследования связан с такими ключевыми словами, как class , extends и override .

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

Рассмотрим присоединение нескольких свойств к объекту прототипа на примере класса Object. Методы toString() и valueOf() класса Object на самом деле являются функциями, которые назначены свойствам объекта прототипа класса Object. Ниже приводится пример того, как может выглядеть объявление этих методов теоретически (фактическая реализация выглядит по-другому из-за деталей реализации).

public dynamic class Object 
{ 
    prototype.toString = function() 
    { 
        // statements 
    }; 
    prototype.valueOf = function() 
    { 
        // statements 
    }; 
}

Как говорилось выше, свойство можно присоединить к объекту прототипа класса за рамками определения класса. Например, метод toString() можно также определить за пределами определения класса Object, как показано ниже.

Object.prototype.toString = function() 
{ 
    // statements 
};

Однако, в отличие от наследования фиксированных свойств, наследование прототипа не требует ключевого слова override , если требуется переопределить метод в подклассе. Например. если требуется переопределить метод valueOf() в подклассе класса Object, это можно сделать одним из трех методов. Во-первых, можно определить метод valueOf() для объекта прототипа подкласса в определении класса. Следующий код создает подкласс Foo класса Object и переопределяет метод valueOf() объекта прототипа Foo в составе определения класса. Так как каждый класс является наследником класса Object, использовать ключевое слово extends не требуется.

dynamic class Foo 
{ 
    prototype.valueOf = function() 
    { 
        return "Instance of Foo"; 
    }; 
}

Во-вторых, можно определить метод valueOf() для объекта прототипа класса Foo в составе определения класса, как в следующем коде.

Foo.prototype.valueOf = function() 
{ 
    return "Instance of Foo"; 
};

В-третьих, можно определить фиксированное свойство valueOf() в составе класса Foo. Этот метод отличается от остальных тем, что в нем объединено наследование фиксированных свойств с наследованием прототипов. Если в каком-либо подклассе класса Foo потребуется переопределить метод valueOf() , необходимо использовать ключевое слово override . Следующий код демонстрирует определение метода valueOf() в качестве фиксированного свойства класса Foo.

class Foo 
{ 
    function valueOf():String 
    { 
        return "Instance of Foo"; 
    } 
}

Пространство имен AS3

В результате существования двух механизмов наследования, наследования фиксированных свойств и наследования прототипов, появляется интересная проблема совместимости в отношении свойств и методов базовых классов. Для совместимости со спецификацией языка ECMAScript, на которой основан ActionScript, требуется наследование прототипов, то есть методы и свойства базового класса должны определяться в объекте прототипа класса. С другой стороны, для совместимости с ActionScript 3.0 требуется наследование фиксированных свойств, то есть свойства и методы базового класса присоединяются через определение класса с помощью ключевых слов const , var и function . Кроме того, использование фиксированных свойств вместо свойств прототипов может значительно повысить производительность во время выполнения.

ActionScript 3.0 решает эту проблему с помощью использования для основных классов как наследования прототипов, так и наследования фиксированных свойств. Каждый базовый класс содержит два набора свойств и методов. Один набор определяется для объекта прототипа с целью обеспечения совместимости со спецификацией ECMAScript, а другой определяется с использование фиксированных свойств и пространством имен AS3 для обеспечения совместимости с ActionScript 3.0.

Пространство имен AS3 обеспечивает удобный механизм выбора одного из двух наборов свойств и методов. Если не используется пространство имен AS3, экземпляр базового класса наследует свойства и методы, определенные в объекте прототипа базового класса. Если используется пространство имен AS3, экземпляр базового класса наследует версии AS3, так как фиксированные свойства всегда имеют более высокий приоритет, чем свойства прототипа. Другими словами, если доступно фиксированное свойство, оно всегда используется вместо одноименного свойства прототипа.

Можно избирательно использовать AS3-версию свойства или метода, указав его с помощью пространства имен AS3. Например, следующий код использует AS3-версию метода Array.pop() .

var nums:Array = new Array(1, 2, 3); 
nums.AS3::pop(); 
trace(nums); // output: 1,2

Также можно использовать директиву use namespace , чтобы открыть пространство имен AS3 для всех определений в блоке кода. Например, следующий код использует директиву use namespace , чтобы открыть пространство имен AS3 для методов pop() и push() .

use namespace AS3; 
 
var nums:Array = new Array(1, 2, 3); 
nums.pop(); 
nums.push(5); 
trace(nums) // output: 1,2,5

ActionScript 3.0 также предоставляет параметры компилятора для каждого набора свойств, чтобы пространство AS3 можно было применить ко всей программе. Параметр компилятора -as3 обозначает пространство имен AS3, а параметр компилятора -es обозначает наследование прототипов ( es — это сокращение от ECMAScript). Чтобы открыть пространство имен AS3 для всей программы, задайте параметру компилятора -as3 значение true , а параметру компилятора -es — значение false . Чтобы использовать версии прототипа, задайте параметрам компилятора противоположные значения. По умолчанию в компиляторах Flash Builder и Flash Professional используются следующие настройки: -as3 = true и -es = false .

Если вы планируете расширять базовые классы и переопределять методы, то необходимо разобраться в том, как пространство имен AS3 может повлиять на способ объявления переопределенного метода. Если используется пространство имен AS3, в любом переопределении метода базового класса также должно использоваться пространство имен AS3 вместе с атрибутом override . Если пространство имен AS3 не используется, то при переопределении метода базового класса в подклассе не следует использовать пространство имен AS3 и ключевое слово override .