Tópicos avançados

Histórico do suporte da OOP ao ActionScript

Como o ActionScript 3.0 foi criado sobre versões anteriores do ActionScript, é útil compreender como o modelo de objeto do ActionScript evoluiu. O ActionScript começou como um mecanismo de script simples para versões anteriores do Flash Professional. Depois, os programadores começaram a criar aplicativos cada vez mais complexos com o ActionScript. Em resposta às necessidades desses programadores, cada versão subseqüente adicionou recursos de linguagem que facilitam a criação de aplicativos complexos.

ActionScript 1.0

O ActionScript 1.0 é a versão da linguagem usada no Flash Player 6 e anterior. Mesmo na primeira fase de desenvolvimento, o modelo de objeto do ActionScript era baseado no conceito do objeto como um tipo de dados fundamental. Um objeto do ActionScript é um tipo de dados composto por um grupo de propriedades. Ao discutir o modelo de objeto, o termo propriedades inclui tudo o que está conectado a um objeto, como variáveis, funções ou métodos.

Embora essa primeira geração do ActionScript não ofereça suporte à definição de classes com uma palavra-chave class, é possível definir uma classe usando um tipo especial de objeto chamado objeto de protótipo. Em vez de usar uma palavra-chave class para criar uma definição de classe abstrata que você instancia em objetos concretos, como o faz em linguagens baseadas em classe, como Java e C++, as linguagens baseadas em protótipo como o ActionScript 1.0 usam um objeto existente como um modelo (ou protótipo) para outros objetos. Enquanto objetos em uma linguagem baseada em classe podem apontar para uma classe que serve como seu modelo, objetos em uma linguagem baseada em protótipo apontam para outro objeto, seu protótipo, que serve como seu modelo.

Para criar uma classe no ActionScript 1.0, defina uma função de construtor para essa classe. No ActionScript, as funções são objetos reais, não apenas definições abstratas. A função de construtor que você cria serve como o objeto de protótipo para ocorrências dessa classe. O código a seguir cria uma classe denominada Shape e define uma propriedade denominada visible que, por padrão, é definida como true:

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

Essa função de construtor define uma classe Shape que você pode instanciar com o operador new, da seguinte maneira:

myShape = new Shape();

Do mesmo modo como o objeto de função de construtor Shape() serve como protótipo para ocorrências da classe Shape, ele também pode servir como o protótipo para subclasses de Shape, isto é, outras classes que estendem a classe Shape.

A criação de uma classe que é uma subclasse da classe Shape é um processo de duas etapas. Primeiro, crie a classe definindo uma função de construtor para a classe, da seguinte maneira:

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

Segundo, use o operador new para declarar que a classe Shape é o protótipo para a classe Circle. Por padrão, qualquer classe criada usa a classe Object como seu protótipo, o que significa que Circle.prototype atualmente contém um objeto genérico (uma ocorrência da classe Object). Para especificar que o protótipo de Circle é Shape em vez de Object, use o código a seguir para alterar o valor de Circle.prototype para que ele contenha um objeto Shape em vez de um objeto genérico:

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

As classes Shape e Circle estão agora vinculadas em conjunto em um relacionamento de herança que é conhecido como a cadeia de protótipos. O diagrama ilustra os relacionamentos em uma cadeia de protótipos:

A classe base no final de cada cadeia de protótipos é a classe Object. A classe Object contém uma propriedade estática denominada Object.prototype que aponta para o objeto de protótipo base de todos os objetos criados no ActionScript 1.0. O próximo objeto no exemplo da cadeia de protótipos é o objeto Shape. Isso ocorre porque a propriedade Shape.prototype nunca foi definida explicitamente, portanto ela ainda mantém um objeto genérico (uma ocorrência da classe Object). O link final nessa cadeia é a classe Circle que está vinculada a seu protótipo, a classe Shape (a propriedade Circle.prototype mantém um objeto Shape).

Se você criar uma ocorrência da classe Circle, como no seguinte exemplo, a ocorrência herdará a cadeia de protótipos da classe Circle:

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

Lembre-se de que o exemplo incluía uma propriedade denominada visible como um membro da classe Shape. Neste exemplo, a propriedade visible não existe como parte do objeto myCircle, apenas como membro do objeto Shape, apesar da linha seguinte do código produzir true:

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

O tempo de execução pode verificar se o objeto myCircle herda a propriedade visible percorrendo a cadeia de protótipos. Ao executar este código, o tempo de execução primeiro pesquisa nas propriedades do objeto myCircle uma propriedade denominada visible, mas não encontra essa propriedade. Em seguida, ele verifica o objeto Circle.prototype, mas ainda não encontra uma propriedade denominada visible. Continuando na cadeia de protótipos, ele finalmente encontra a propriedade visible definida no objeto Shape.prototype e fornece o valor daquela propriedade.

Para fins de simplicidade, muitos dos detalhes e complicações da cadeia de protótipos são omitidos. No lugar, o objetivo é fornecer informações suficientes para ajudá-lo a entender o modelo de objetos do ActionScript 3.0.

ActionScript 2.0

O ActionScript 2.0 introduziu novas palavras-chave, como class, extends, publice private, que permitiram definir classes de uma maneira que seja familiar a qualquer pessoa que trabalhe com linguagens baseadas em classes como Java e C++. É importante compreender que o mecanismo da herança subjacente não foi alterado entre o ActionScript 1.0 e o ActionScript 2.0. O ActionScript 2.0 simplesmente adicionou uma nova sintaxe para definir classes. A cadeia de protótipos funciona da mesma maneira nas duas versões da linguagem.

A nova sintaxe introduzida pelo ActionScript 2.0, mostrada no trecho a seguir, permite definir classes de uma maneira que é considerada intuitiva por muitos programadores:

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

Observe que o ActionScript 2.0 também introduziu anotações de tipo para uso com verificação de tipos em tempo de compilação. Isso permite declarar que a propriedade visible no exemplo anterior deve conter apenas um valor booleano. A nova palavra-chave extends também simplifica o processo de criação de uma subclasse. No exemplo a seguir, o processo em duas etapas necessário no ActionScript 1.0 é executado em uma etapa com a palavra-chave extends:

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

O construtor está agora declarado como parte da definição da classe e as propriedades de classes id e radius também devem ser declaradas explicitamente.

O ActionScript 2.0 também adicionou suporte para a definição de interfaces, o que permite refinar ainda mais os programas orientados a objetos com protocolos definidos formalmente para comunicação entre objetos.

Objeto de classe do ActionScript 3.0

Um paradigma comum da programação orientada a objetos, mais freqüentemente associado a Java e C++, usa classes para definir tipos de objetos. Linguagens de programação que adotam esse paradigma também tendem a usar classes para construir ocorrências do tipo de dados que a classe define. O ActionScript usa classes para duas dessas finalidades, mas suas raízes como uma linguagem com base em protótipo adicionam uma característica interessante. O ActionScript cria para cada definição de classe um objeto de classe especial que permite o compartilhamento do comportamento e do estado. No entanto, para muitos programadores do ActionScript, esta distinção não pode ter nenhuma implicação de codificação prática. O ActionScript 3.0 é projetado de forma que você possa criar aplicativos do ActionScript orientados a objetos sofisticados sem usar, ou mesmo compreender, esses objetos de classes especiais.

O diagrama a seguir mostra a estrutura de um objeto de classe que representa uma classe simples denominada A que é definida com a instrução class A {}:

Cada retângulo no diagrama representa um objeto. Cada objeto no diagrama tem um caractere subscrito A para representar que ele pertence à classe A. O objeto de classe (CA) contém referências a vários outros objetos importantes. Um objeto com características da ocorrência (TA) armazena as propriedades da ocorrência que são definidas dentro de uma definição de classe. Um objeto de características da classe (TCA) representa o tipo interno da classe e armazena as propriedades estáticas definidas pela classe (o caractere subscrito C representa a “classe”). O objeto de protótipo (PA) sempre significa o objeto da classe ao qual ele era originalmente anexado por meio da propriedade constructor.

Objeto de características

O objeto de características, novo no ActionScript 3.0, foi implementado tendo em mente o desempenho. Em versões anteriores do ActionScript, a pesquisa de nome era um processo demorado pois o Flash Player percorria a cadeia de protótipos. No ActionScript 3.0, a pesquisa de nome é muito mais eficiente e menos demorada, porque as propriedades herdadas são copiadas das superclasses no objeto de características de subclasses.

O objeto de características não pode ser acessado diretamente pelo código do programador, mas sua presença pode ser sentida pelos aprimoramentos no desempenho e no uso de memória. O objeto de características fornece ao AVM2 informações detalhadas sobre o layout e o conteúdo de uma classe. Com esse conhecimento, o AVM2 pode reduzir significativamente o tempo de execução, porque pode gerar freqüentemente instruções de máquina diretas para acessar propriedades ou chamar métodos diretamente sem uma pesquisa de nome demorada.

Graças ao objeto de características, uma superfície de memória do objeto pode ser significativamente menor do que a de um objeto semelhante em versões anteriores do ActionScript. Por exemplo, se uma classe estiver selada (isto é, a classe não está declarada dinâmica), uma ocorrência da classe não precisará de uma tabela hash para propriedades adicionadas dinamicamente, e poderá manter um pouco mais do que um ponteiro para os objetos de características e alguns slots para as propriedades fixas definidas na classe. Como resultado, um objeto que exigia 100 bytes de memória no ActionScript 2.0 pode exigir apenas 20 bytes de memória no ActionScript 3.0.

Nota: O objeto de características é um detalhe da implementação interna, e não há nenhuma garantia de que ele não seja alterado ou mesmo que desapareça em versões futuras do ActionScript.

Objeto de protótipo

Cada objeto de classe do ActionScript tem uma propriedade denominada prototype, que é uma referência ao objeto de protótipo da classe. O objeto de protótipo é um herança das raízes do ActionScript como linguagem com base em protótipo. Para obter mais informações, consulte Histórico do suporte da OOP ao ActionScript.

A propriedade prototype é somente leitura, o que significa que não pode ser modificada para apontar para objetos diferentes. Ela é diferente da propriedade prototype da classe em versões anteriores do ActionScript, em que o protótipo podia ser reatribuído para que apontasse para uma classe diferente. Embora a propriedade prototype seja somente leitura, o objeto de protótipo ao qual ela faz referência não é. Em outras palavras, novas propriedades podem ser adicionadas ao objeto de protótipo. Propriedades adicionadas ao objeto de protótipo são compartilhadas entre todas as ocorrências da classe.

A cadeia de protótipos, que era o único mecanismo de herança em versões anteriores do ActionScript, serve apenas uma função secundária no ActionScript 3.0. O mecanismo de herança principal, herança de propriedade fixa, é manipulado internamente pelo objeto de características. Uma propriedade fixa é uma variável ou método que é definida como parte de uma definição de classe. A herança de propriedade fixa também é de chamada herança de classe, porque ela é o mecanismo de herança associado a palavras-chave, como class, extends e override.

A cadeia de protótipos fornece um mecanismo de herança alternativa que é mais dinâmico do que a herança de propriedade fixa. Você pode adicionar propriedades a um objeto de protótipo de classe não apenas como parte da definição da classe, mas também em tempo de execução por meio da propriedade prototype do objeto de classe. No entanto, observe que se você definir o compilador como modo estrito, talvez não seja possível acessar propriedades adicionadas a um objeto de protótipo, a não ser que você declare uma classe com a palavra-chave dynamic.

Um bom exemplo de uma classe com várias propriedades anexadas ao objeto de protótipo é a classe Object. Os métodos toString() e valueOf() da classe Object são realmente funções atribuídas às propriedades do objeto de protótipo da classe Object. A seguir está um exemplo de como pode ser a aparência da declaração desses métodos, em teoria, (a implementação real é um pouco diferente, por causa dos detalhes da implementação):

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

Conforme mencionado anteriormente, é possível anexar uma propriedade a um objeto de protótipo de classe fora da definição de classe. Por exemplo, o método toString() também pode ser definido fora da definição da classe Object, da seguinte maneira:

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

No entanto, ao contrário da herança de propriedade fixa, a herança de protótipo não exigirá a palavra-chave override, se você desejar redefinir um método em uma subclasse. Por exemplo. se você desejar redefinir o método valueOf() em uma subclasse da classe Object, terá três opções. Primeiro, você pode definir um método valueOf() no objeto de protótipo de subclasse dentro da definição de classe. O código a seguir cria uma subclasse de Object denominada Foo e redefine o método valueOf() no objeto de protótipo de Foo como parte da definição de classe. Como cada classe é herdada de Object, não é necessário usar a palavra-chave extends.

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

Segundo, você pode definir um método valueOf() no objeto de protótipo de Foo fora da definição de classe, conforme mostrado no código a seguir:

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

Terceiro, você pode definir uma propriedade fixa denominada valueOf() como parte da classe Foo. Essa técnica é diferente das outras já que ela mescla herança de propriedade fixa com herança de protótipo. Qualquer subclasse de Foo que precise redefinir valueOf() deve usar a palavra-chave override. O código a seguir mostra valueOf() definido como uma propriedade fixa em Foo:

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

Espaço para nomes AS3

A existência de dois mecanismos de herança separados, herança de propriedade fixa e herança de protótipo, cria uma desafio de compatibilidade interessante em relação às propriedades e métodos das classes principais. A compatibilidade com a especificação de linguagem do ECMAScript na qual o ActionScript é baseado exige o uso de herança de protótipo, o que significa que as propriedades e métodos de uma classe principal são definidos no objeto de protótipo dessa classe. Por outro lado, a compatibilidade com o ActionScript 3.0 exige o uso de herança de propriedade fixa, o que significa que as propriedades e métodos de uma classe principal são definidos na definição da classe usando as palavras-chave const, var e function. Além disso, o uso de propriedades fixas, em vez das versões do protótipo pode levar a aumentos significativos no desempenho em tempo de execução.

O ActionScript 3.0 resolve esse problema usando herança de protótipo e herança de propriedade fixa para as classes principais. Cada classe principal contém dois conjuntos de propriedades e métodos. Um conjunto é definido no objeto de protótipo para compatibilidade com a especificação do ECMAScript, e o outro conjunto é definido com propriedades fixas e o espaço para nomes AS3 para compatibilidade com o ActionScript 3.0.

O espaço para nomes AS3 fornece um mecanismo conveniente para escolher entre os dois conjuntos de propriedades e métodos. Se você não usar o espaço para nomes AS3, uma ocorrência de uma classe principal herdará as propriedades e métodos definidos no objeto de protótipo da classe principal. Se você decidir usar o espaço para nomes AS3, uma ocorrência de uma classe principal herdará as versões do AS3, porque as propriedades fixas são sempre preferidas sobre as propriedades de protótipo. Em outras palavras, sempre que uma propriedade fixa estiver disponível, ela será sempre usada no lugar de uma propriedade de protótipo nomeada de forma idêntica.

Você pode usar seletivamente a versão do espaço para nomes AS3 de uma propriedade ou método qualificando-a com o espaço para nomes AS3. Por exemplo, o código a seguir usa a versão do AS3 do método Array.pop():

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

Como alternativa, você pode usar a diretiva use namespace para abrir o espaço para nomes AS3 para todas as definições dentro de um bloco de código. Por exemplo, o código a seguir usa a diretiva use namespace para abrir o espaço para nomes AS3 para os métodos pop() e push():

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

O ActionScript 3.0 também fornece opções de compilador para cada conjunto de propriedades para que você possa aplicar o espaço para nomes AS3 ao programa inteiro. A opção de compilador -as3 representa o espaço para nomes AS3, e a opção de compilador -es representa a opção de herança de protótipo (es representa o ECMAScript). Para abrir o espaço para nomes AS3 para o programa inteiro, defina a opção de compilador -as3 como true e a opção de compilador -es como false. Para usar as versões de protótipo, defina as opções do compilador como os valores opostos. As configurações do compilador padrão do Flash Builder e do Flash Professional são -as3 = true e -es = false.

Se você planejar estender qualquer uma das classes principais e substituir qualquer método, deverá compreender como o espaço para nomes AS3 pode afetar o modo como você deve declarar um método substituído. Se você estiver usando o espaço para nomes AS3, qualquer substituição de método de um método da classe principal também deve usar o espaço para nomes AS3 juntamente com o atributo override. Se não estiver usando o espaço para nomes AS3 e desejar redefinir um método da classe principal em uma subclasse, você não deverá usar o espaço para nomes AS3 ou a palavra-chave override.