Herança

Herança é uma forma de reutilização de código que permite que programadores desenvolvam novas classes com base em classes existentes. As classes existentes são sempre conhecidas como classes base ou superclasses, enquanto as novas classes são chamadas de subclasses. A vantagem principal da herança é que ela permite reutilizar código de uma classe base e ainda deixar o código existente inalterado. Além disso, a herança não requer nenhuma alteração no modo como as outras classes interagem com a classe base. Em vez de modificar uma classe existente que pode ter sido completamente testada ou já estar em uso, usando herança você pode tratar essa classe como um módulo integrado que pode ser estendido com propriedades ou métodos adicionais. De forma correspondente, você usa a palavra-chave extends para indicar que uma classe herda de outra classe.

A herança também permite que você se beneficie com o polimorfismo do código. Polimorfismo é a habilidade de usar um único nome de método para um método que se comporta de maneira diferente ao ser aplicado a diferentes tipos de dados. Um exemplo simples é uma classe base denominada Shape com duas subclasses denominadas Circle e Square. A classe Shape define um método denominado area(), que retorna a área da forma. Se o polimorfismo estiver implementado, você poderá chamar o método area() em objetos do tipo Circle e Square e fazer com que os cálculos corretos sejam feitos para você. A herança ativa o polimorfismo permitindo que as subclasses sejam herdadas e redefinidas, ou substituam, métodos da classe base. No exemplo a seguir, o método area() é redefinido pelas classes Circle e Square:

class Shape 
{ 
    public function area():Number 
    { 
        return NaN; 
    } 
} 
 
class Circle extends Shape 
{ 
    private var radius:Number = 1; 
    override public function area():Number 
    { 
        return (Math.PI * (radius * radius)); 
    } 
} 
 
class Square extends Shape 
{ 
    private var side:Number = 1; 
    override public function area():Number 
    { 
        return (side * side); 
    } 
} 
 
var cir:Circle = new Circle(); 
trace(cir.area()); // output: 3.141592653589793 
var sq:Square = new Square(); 
trace(sq.area()); // output: 1

Como cada classe define um tipo de dados, o uso da herança cria um relacionamento especial entre a classe base e a classe que a estende. Uma subclasse garantidamente possui todas as propriedades de sua classe base, o que significa que uma ocorrência de uma subclasse pode ser sempre substituída por uma ocorrência da classe base. Por exemplo, se um método definir um parâmetro do tipo Shape, é válido passar um argumento do tipo Circle, porque Circle estende Shape, da seguinte maneira:

function draw(shapeToDraw:Shape) {} 
 
var myCircle:Circle = new Circle(); 
draw(myCircle);

Propriedades da ocorrência e herança

Uma propriedade da ocorrência, se definida com as palavras-chave function, var ou const, será herdada por todas as subclasses desde que a propriedade não seja declarada com o atributo private na classe base. Por exemplo, a classe Event no ActionScript 3.0 tem várias subclasses que herdam propriedades comuns a todos os objetos de eventos.

Para alguns tipos de eventos, a classe Event contém todas as propriedades necessárias para definir o evento. Esses tipos de eventos não exigem propriedades da ocorrência além daquelas definidas na classe Event. Exemplos desses eventos são o evento complete, que ocorre quando os dados foram carregados com êxito, e o evento connect, que ocorre quando uma conexão de rede foi estabelecida.

O exemplo a seguir é um fragmento da classe Event que mostra algumas das propriedades e métodos herdados por subclasses. Como as propriedades são herdadas, uma ocorrência de qualquer subclasse pode acessar essas propriedades.

public class Event 
{ 
    public function get type():String; 
    public function get bubbles():Boolean; 
    ... 
 
    public function stopPropagation():void {} 
    public function stopImmediatePropagation():void {} 
    public function preventDefault():void {} 
    public function isDefaultPrevented():Boolean {} 
    ... 
}

Outros tipos de eventos exigem propriedades exclusivas não estão disponíveis na classe Event. Esses eventos são definidos usando subclasses da classe Event para que novas propriedades possam ser adicionadas às propriedades definidas na classe Event. Um exemplo dessa subclasse é a classe MouseEvent, que adiciona propriedades exclusivas a eventos associados a movimento ou a cliques do mouse, como os eventos mouseMove e click. O exemplo a seguir é um fragmento da classe MouseEvent que mostra a definição de propriedades que existem na subclasse, mas não na classe base:

public class MouseEvent extends Event 
{ 
    public static const CLICK:String= "click"; 
    public static const MOUSE_MOVE:String = "mouseMove"; 
    ... 
 
    public function get stageX():Number {} 
    public function get stageY():Number {} 
    ... 
}

Especificadores de controle de acesso e herança

Se uma propriedade for declarada com a palavra-chave public, ela será visível ao código em qualquer lugar. Isso significa que a palavra-chave public, ao contrário das palavras-chave private, protected e internal, não coloca nenhuma restrição sobre a herança da propriedade.

Se uma propriedade for declarada com a palavra-chave private, ela será visível apenas na classe que a define, o que significa que não será herdada por nenhuma subclasse. Esse comportamento é diferente nas versões anteriores do ActionScript, em que a palavra-chave private se comportava de maneira mais semelhante à palavra-chave protected do ActionScript 3.0.

A palavra-chave protected indica que uma propriedade é visível não apenas dentro da classe que a define, mas também a todas as subclasses. Ao contrário da palavra-chave protected na linguagem de programação Java, a palavra-chave protected no ActionScript 3.0 não torna a propriedade visível para todas as outras classes no mesmo pacote. No ActionScript 3.0, apenas as subclasses podem acessar uma propriedade declarada com a palavra-chave protected. Além disso, uma propriedade protegida será visível para uma subclasse, se a subclasse estiver no mesmo pacote da classe base ou em um pacote diferente.

Para limitar a visibilidade de uma propriedade para o pacote no qual ela está definida, use a palavra-chave internal ou não use nenhum especificador de controle de acesso. O especificador de controle de acesso internal é o especificador padrão aplicado quando um não está especificado. Uma propriedade marcada como internal só é herdada por uma subclasse que reside no mesmo pacote.

Você pode usar o exemplo a seguir para ver como cada um dos especificadores de controle de acesso afeta a herança entre limites de pacotes. O código a seguir define uma classe de aplicativo principal denominada AccessControl e duas outras classes denominadas Base e Extender. A classe Base está em um pacote denominado foo e a classe Extender, que é uma subclasse da classe Base, está em um pacote denominado bar. A classe AccessControl importa apenas a classe Extender e cria uma ocorrência de Extender que tenta acessar uma variável denominada str definida na classe Base. A variável str é declarada como public para que o código seja compilado e executado, conforme mostrado no seguinte trecho:

// Base.as in a folder named foo 
package foo 
{ 
    public class Base 
    { 
        public var str:String = "hello"; // change public on this line 
    } 
} 
 
// Extender.as in a folder named bar 
package bar 
{ 
    import foo.Base; 
    public class Extender extends Base 
    { 
        public function getString():String { 
            return str; 
        } 
    } 
} 
 
// main application class in file named AccessControl.as 
package 
{ 
    import flash.display.MovieClip; 
    import bar.Extender; 
    public class AccessControl extends MovieClip 
    { 
        public function AccessControl() 
        { 
            var myExt:Extender = new Extender(); 
            trace(myExt.str);// error if str is not public 
            trace(myExt.getString()); // error if str is private or internal 
        } 
    } 
}

Para ver como os outros especificadores de controle de acesso afetam a compilação e a execução do exemplo anterior, altere o especificador de controle de acesso da variável str para private, protected ou internal após excluir ou comentar a linha seguinte da classe AccessControl:

trace(myExt.str);// error if str is not public

Substituição de variáveis não permitidas

As propriedades declaradas com as palavras-chave var ou const são herdadas, mas não podem ser substituídas. Substituir uma propriedade significa redefini-la em uma subclasse. O único tipo de propriedade que pode ser substituído são os acessadores get e set (propriedades declaradas com a palavra-chave function). Embora não seja possível substituir uma variável de ocorrência, você pode alcançar funcionalidade semelhante criando os métodos getter e setter para a variável de ocorrência e substituindo os métodos.

Substituição de métodos

Substituir um método significa redefinir o comportamento de um método herdado. Métodos estáticos não são herdados e não podem ser substituídos. No entanto métodos de ocorrência são herdados por subclasses e podem ser substituídos desde que os dois seguintes critérios sejam atendidos:

  • O método da ocorrência não é declarado com a palavra-chave final na classe base. Quando usada com um método da ocorrência, a palavra-chave final indica a intenção do programador de impedir que as subclasses substituam o método.

  • O método da ocorrência não é declarado com o especificador de controle de acesso private na classe base. Se um método estiver marcado como private na classe base, não haverá necessidade de usar a palavra-chave override ao definir um método nomeado de maneira idêntica na subclasse, porque o método da classe base não é visível para a subclasse.

    Para substituir um método da ocorrência que atenda a esses critérios, a definição do método na subclasse deve usar a palavra-chave override e deve corresponder à versão da superclasse do método das seguintes maneiras:

  • O método de substituição deve ter o mesmo nível de controle de acesso do método da classe base. Métodos marcados como internos têm o mesmo nível de controle de acesso que os métodos que não têm nenhum especificador de controle de acesso.

  • O método de substituição deve ter o mesmo número de parâmetros que o método da classe base.

  • Os parâmetros do método de substituição devem ter as mesmas anotações de tipo de dados que os parâmetros do método da classe base.

  • O método de substituição deve ter o mesmo tipo de retorno que o método da classe base.

No entanto os nomes dos parâmetros no método de substituição não precisam corresponder aos nomes dos parâmetros na classe base, desde que o número de parâmetros e o tipo de dados de cada parâmetro correspondam.

A instrução super

Ao substituir um método, os programadores sempre querem aumentar o comportamento do método da superclasse que estão substituindo, em vez de substituir completamente o comportamento. Isso requer um mecanismo que permita que um método em uma subclasse chame a versão da superclasse de si próprio. A instrução super fornece esse mecanismo, já que ela contém uma referência à superclasse imediata. O exemplo a seguir define uma classe denominada Base que contém um método denominado thanks() e uma subclasse da classe Base denominada Extender que substitui o método thanks(). O método Extender.thanks() usa a instrução super para chamar Base.thanks().

package { 
    import flash.display.MovieClip; 
    public class SuperExample extends MovieClip 
    { 
        public function SuperExample() 
        { 
            var myExt:Extender = new Extender() 
            trace(myExt.thanks()); // output: Mahalo nui loa 
        } 
    } 
} 
 
class Base { 
    public function thanks():String 
    { 
        return "Mahalo"; 
    } 
} 
 
class Extender extends Base 
{ 
    override public function thanks():String 
    { 
        return super.thanks() + " nui loa"; 
    } 
}

Substituição de getters e setters

Embora não seja possível substituir variáveis definidas em uma superclasse, você pode substituir getters e setters. Por exemplo, o código a seguir substitui um getter denominado currentLabel que é definido na classe MovieClip no ActionScript 3.0:

package 
{ 
    import flash.display.MovieClip; 
    public class OverrideExample extends MovieClip 
    { 
        public function OverrideExample() 
        { 
            trace(currentLabel) 
        } 
        override public function get currentLabel():String 
        { 
            var str:String = "Override: "; 
            str += super.currentLabel; 
            return str; 
        } 
    } 
}

A saída da instrução trace() no construtor da classe OverrideExample é Override: null, que mostra que o exemplo pôde substituir a propriedade currentLabel herdada.

Propriedades estáticas não herdadas

Propriedades estáticas não são herdadas por subclasses. Isso significa que as propriedades estáticas não podem ser acessadas por meio de uma ocorrência de uma subclasse. Uma propriedade estática pode ser acessada apenas por meio do objeto da classe no qual ela é definida. Por exemplo, o código a seguir define uma classe base denominada Base e uma subclasse denominada Extender que estende a Base. Uma variável estática denominada test é definida na classe Base. O código conforme escrito no fragmento a seguir, não é compilado no modo estrito e gera um erro em tempo de execução no modo padrão.

package { 
    import flash.display.MovieClip; 
    public class StaticExample extends MovieClip 
    { 
        public function StaticExample() 
        { 
            var myExt:Extender = new Extender(); 
            trace(myExt.test);// error 
        } 
    } 
} 
 
class Base { 
    public static var test:String = "static"; 
} 
 
class Extender extends Base { }

A única maneira de acessar a variável estática test é por meio do objeto da classe, conforme mostrado no código a seguir:

Base.test;

No entanto é permitido definir uma propriedade da ocorrência usando o mesmo nome de uma propriedade estática. Essa propriedade da ocorrência pode ser definida na mesma classe que a propriedade estática ou em uma subclasse. Por exemplo, a classe Base no exemplo anterior podia ter uma propriedade da ocorrência denominada test. O código a seguir é compilado e executado porque a propriedade da ocorrência é herdada pela classe Extender. O código também será compilado e executado, se a definição da variável da ocorrência de teste for movida, mas não copiada, para a classe Extender.

package 
{ 
    import flash.display.MovieClip; 
    public class StaticExample extends MovieClip 
    { 
        public function StaticExample() 
        { 
            var myExt:Extender = new Extender(); 
            trace(myExt.test);// output: instance 
        } 
    } 
} 
 
class Base 
{ 
    public static var test:String = "static"; 
    public var test:String = "instance"; 
} 
 
class Extender extends Base {}

Propriedades estáticas e a cadeia de escopos

Embora as propriedades estáticas não sejam herdadas, elas estão dentro da cadeia do escopo da classe que as define e em qualquer subclasse dessa classe. Como tal, diz-se que as propriedades estáticas estão in scope da classe na qual elas são definidas e em qualquer subclasse. Isso significa que uma propriedade estática pode ser acessada diretamente dentro do corpo da classe que a define e em qualquer subclasse dessa classe.

O exemplo a seguir modifica as classes definidas no exemplo anterior para mostrar que a variável estática test definida na classe Base está no escopo da classe Extender. Em outras palavras, a classe Extender pode acessar a variável estática test sem prefixar a variável com o nome da classe que define test.

package 
{ 
    import flash.display.MovieClip; 
    public class StaticExample extends MovieClip 
    { 
        public function StaticExample() 
        { 
            var myExt:Extender = new Extender(); 
        } 
    } 
} 
 
class Base { 
    public static var test:String = "static"; 
} 
 
class Extender extends Base 
{ 
    public function Extender() 
    { 
        trace(test); // output: static 
    } 
     
}

Se for definida uma propriedade de ocorrência que usa o mesmo nome que uma propriedade estática na mesma classe ou em uma superclasse, a propriedade de ocorrência terá precedência mais alta na cadeia do escopo. Diz-se que a propriedade da ocorrência sombreia a propriedade estática, o que significa que o valor da propriedade da ocorrência é usado no lugar do valor da propriedade estática. Por exemplo, o código a seguir mostra que se a classe Extender definir uma variável da ocorrência denominada test, a instrução trace() usará o valor da variável da ocorrência em vez do valor da variável estática:

package 
{ 
    import flash.display.MovieClip; 
    public class StaticExample extends MovieClip 
    { 
        public function StaticExample() 
        { 
            var myExt:Extender = new Extender(); 
        } 
    } 
} 
 
class Base 
{ 
    public static var test:String = "static"; 
} 
 
class Extender extends Base 
{ 
    public var test:String = "instance"; 
    public function Extender() 
    { 
        trace(test); // output: instance 
    } 
     
}