Ereditarietà

L'ereditarietà è una forma di riutilizzo del codice che consente ai programmatori di sviluppare nuove classi basate sulle classi esistenti. Le classi esistenti vengono spesso definite classi base o superclassi, mentre le nuove classi sono generalmente chiamate sottoclassi. Uno dei principali vantaggi dell'ereditarietà è che consente di riutilizzare il codice di una classe di base, senza modificare il codice esistente. Inoltre, l'ereditarietà non richiede di modificare il modo in cui le altre classi interagiscono con la classe di base. Anziché modificare una classe esistente già ampiamente testata o già in uso, l'impiego dell'ereditarietà consente di trattare tale classe come un modulo integrato da estendere con proprietà o metodi aggiuntivi. Di conseguenza, la parola chiave extends viene utilizzata per indicare che una classe eredita da un'altra classe.

L'ereditarietà consente inoltre di sfruttare i vantaggi del polimorfismo all'interno del codice. Si definisce polimorfismo la capacità di usare un unico nome metodo per un metodo in grado di comportarsi in modo differente se applicato a diversi tipi di dati. Un semplice esempio è costituito da una classe base denominata Shape con due sottoclassi denominate Circle e Square. La classe Shape definisce un metodo chiamato area() che restituisce l'area della figura geometrica. Se è implementato il polimorfismo, potete chiamare il metodo area() sugli oggetti di tipo Circle e Square e ottenere i calcoli corretti. L'ereditarietà abilita il polimorfismo consentendo alle sottoclassi di ereditare e ridefinire (sostituire) i metodi della classe base. Nell'esempio seguente, il metodo area() viene ridefinito dalle classi 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

Poiché ogni classe definisce un tipo di dati, l'uso dell'ereditarietà crea una speciale relazione tra la classe base e la classe che la estende. Una sottoclasse possiede sempre tutte le proprietà della sua classe base, di conseguenza una qualsiasi istanza di una sottoclasse può sempre essere sostituita con un'istanza della classe base. Ad esempio, se un metodo definisce un parametro di tipo Shape, è consentito il passaggio di un argomento di tipo Circle, in quanto Circle è un'estensione di Shape, come indicato di seguito:

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

Proprietà di istanza ed ereditarietà

Una proprietà di istanza, sia che venga dichiarata con la parola chiave function, var o const, viene ereditata da tutte le sottoclassi a condizione che essa non sia stata dichiarata con l'attributo private nella classe base. Ad esempio, la classe Event in ActionScript 3.0 ha un numero di sottoclassi che ereditano proprietà comuni a tutti gli oggetti evento.

Per alcuni tipi di eventi, la classe Event contiene tutte le proprietà necessarie per la definizione dell'evento. Questi tipi di eventi non richiedono proprietà di istanza oltre a quelle definite nella classe Event. Esempi di tali eventi sono l'evento complete, che si verifica quando i dati vengono caricati correttamente, e l'evento connect, che si verifica quando viene stabilita una connessione di rete.

L'esempio seguente è un estratto della classe Event che illustra alcune delle proprietà e dei metodi ereditati dalle sottoclassi. Poiché vengono ereditate, le proprietà sono accessibili da una qualsiasi istanza di una sottoclasse.

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 {} 
    ... 
}

Vi sono poi altri tipi di eventi che richiedono proprietà univoche non disponibili nella classe Event. Tali eventi vengono definiti utilizzando le sottoclassi della classe Event, in modo da consentire l'aggiunta di nuove proprietà alle proprietà già definite nella classe Event. Un esempio di tali sottoclassi è la classe MouseEvent, che aggiunge proprietà univoche per gli eventi associate ai movimenti o ai clic del mouse, quali gli eventi mouseMove e click. L'esempio seguente è un estratto della classe MouseEvent che mostra la definizione di proprietà esistenti all'interno della sottoclasse, ma non nella 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 {} 
    ... 
}

Specificatori del controllo di accesso ed ereditarietà

Se viene dichiarata con la parola chiave public, una proprietà risulta visibile al codice ovunque si trovi. Ciò significa che la parola chiave public, a differenza delle parole chiave private, protected e internal, non pone limiti all'ereditarietà delle proprietà.

Se viene dichiarata con la parola chiave private, una proprietà risulterà visibile solo nella classe che la definisce e non verrà ereditata da alcuna delle sottoclassi. Ciò non accadeva nelle versioni precedenti di ActionScript, dove la parola chiave private si comportava in modo simile alla parola chiave protected di ActionScript 3.0.

La parola chiave protected indica che una proprietà è visibile non solo all'interno della classe che la definisce, ma anche in tutte le sue sottoclassi. A differenza della parola chiave protected del linguaggio di programmazione Java, la parola chiave protected di ActionScript 3.0 non rende una proprietà visibile a tutte le altre classi dello stesso pacchetto. In ActionScript 3.0, solo le sottoclassi possono accedere a una proprietà dichiarata con la parola chiave protected. Inoltre, una proprietà protetta è visibile a una sottoclasse, sia che questa si trovi nello stesso pacchetto della classe base che in un pacchetto differente.

Per limitare la visibilità di una proprietà al pacchetto nel quale essa è stata definita, utilizzate la parola chiave internal oppure non utilizzate nessun specificatore del controllo di accesso. Lo specificatore del controllo di accesso internal è lo specificatore predefinito che viene applicato quando non ne viene specificato nessuno. Se viene contrassegnata come internal, una proprietà viene ereditata unicamente dalle sottoclassi che risiedono nello stesso pacchetto.

L'esempio seguente mostra come ogni specificatore del controllo di accesso può modificare l'ereditarietà nell'ambito dei pacchetti. Il codice seguente definisce una classe di applicazione principale chiamata AccessControl e due altre classi chiamate Base ed Extender. La classe Base si trova in un pacchetto denominato foo, mentre la classe Extender, che è una sottoclasse della classe Base, si trova in un pacchetto chiamato bar. La classe AccessControl importa unicamente la classe Extender e crea un'istanza di Extender che tenta di accedere a una variabile chiamata str definita nella classe Base. La variabile str è dichiarata come public, di conseguenza il codice viene compilato ed eseguito come nell'estratto seguente:

// 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 
        } 
    } 
}

Per vedere come gli altri specificatori del controllo di accesso possono modificare la compilazione e l'esecuzione dell'esempio precedente, impostate lo specificatore del controllo di accesso alla variabile str su private, protected o internal, dopo avere eliminato o escluso mediante commento la seguente riga dalla classe AccessControl:

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

Sostituzione delle variabili non consentita

Le proprietà dichiarate con le parole chiave var o const vengono ereditate, ma non possono essere sostituite. Sostituire una proprietà significa ridefinirla in una sottoclasse. I solo tipi di proprietà che è possibile sostituire sono proprietà accessor get e set, vale a dire le proprietà dichiarate con la parola chiave function. Sebbene non sia possibile sostituire una variabile di istanza, potete ottenere un risultato simile mediante la creazione di metodi getter e setter per la variabile di istanza, quindi sostituire tali metodi.

Sostituzione di metodi

Sostituire un metodo significa ridefinire il comportamento di un metodo ereditato. I metodi statici non vengono ereditati e non possono essere sostituiti. I metodi di istanza, tuttavia, vengono ereditati dalle sottoclassi e possono essere sostituiti a condizione che vengano soddisfatti i seguenti criteri:

  • Il metodo di istanza non deve essere dichiarato con la parola chiave final nella classe base. Se utilizzata con un metodo di istanza, la parola chiave final indica l'intenzione del programmatore di impedire alle sottoclassi di sostituire il metodo.

  • Il metodo di istanza non deve essere dichiarato con lo specificatore del controllo di accesso private nella classe base. Se un metodo viene contrassegnato come private nella classe base, non è necessario utilizzare la parola chiave override per la definizione di un metodo con nome identico nella sottoclasse, in quanto il metodo della classe base non è visibile alla sottoclasse.

    Per sostituire un metodo di istanza che soddisfi i criteri di cui sopra, è necessario che nella definizione del metodo all'interno della sottoclasse venga utilizzata la parola chiave override e che tale definizione corrisponda alla versione del metodo della superclasse, nei modi indicati di seguito:

  • Il metodo di sostituzione deve avere lo stesso livello di controllo di accesso del metodo della classe base. I metodi contrassegnati come interni hanno lo stesso livello di controllo di accesso dei metodi che non presentano alcuno specificatore del controllo di accesso.

  • Il metodo di sostituzione deve avere lo stesso numero di parametri del metodo della classe base.

  • I parametri del metodo di sostituzione devono avere le stesse annotazioni di tipo di dati dei parametri del metodo della classe base.

  • Il metodo di sostituzione deve avere lo stesso tipo restituito del metodo della classe base.

I nomi dei parametri del metodo di sostituzione, tuttavia, non devono corrispondere ai nomi dei parametri del metodo della classe base, a condizione che il numero dei parametri e il tipo di dati di ciascun parametro corrisponda.

Istruzione super

Quando sostituiscono un metodo, i programmatori spesso intendono aggiungere qualcosa al comportamento del metodo della superclasse da sostituire, anziché rimpiazzare completamente tale comportamento. Ciò richiede un meccanismo che consenta a un metodo in una sottoclasse di chiamare la versione di se stesso presente nella superclasse. L'istruzione super consente tale operazione, in quanto contiene un riferimento all'immediata superclasse. Nell'esempio seguente viene definita una classe chiamata Base contenente un metodo chiamato thanks() e una sottoclasse della classe Base denominata Extender che sostituisce il metodo thanks(). Il metodo Extender.thanks() impiega l'istruzione super per chiamare 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"; 
    } 
}

Sostituzione di getter e setter

Nonostante non sia possibile sostituire le variabili definite in una superclasse, è invece possibile sostituire le funzioni getter e setter. Ad esempio, il codice seguente consente di sostituire una funzione getter denominata currentLabel definita nella classe MovieClip in 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; 
        } 
    } 
}

Il risultato dell'istruzione trace() nella funzione di costruzione della classe OverrideExample è Override: null, che mostra come l'esempio sia riuscito a sostituire la proprietà currentLabel ereditata.

Proprietà statiche non ereditate

Le proprietà statiche non vengono ereditate dalle sottoclassi. Ciò significa che non è possibile accedere alle proprietà statiche attraverso un'istanza di una sottoclasse. Una proprietà statica è accessibile unicamente attraverso l'oggetto di classe sul quale viene definita. Ad esempio, il codice seguente definisce una classe base chiamata Base e una sottoclasse che la estende denominata Extender. Nella classe Base viene definita una variabile statica chiamata test. Il codice riportato nell'estratto seguente non viene compilato in modalità rigorosa e genera un errore di runtime in modalità standard.

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 { }

Il solo modo per accedere alla variabile statica test è mediante l'oggetto di classe, come illustrato nel codice seguente:

Base.test;

È tuttavia consentito definire una proprietà di istanza con lo stesso nome della proprietà statica. Tale proprietà di istanza può essere definita nella stessa classe della proprietà statica o in una sua sottoclasse. Ad esempio, la classe Base dell'esempio precedente potrebbe avere una proprietà di istanza denominata test. Il codice seguente viene compilato ed eseguito perché la proprietà di istanza viene ereditata dalla classe Extender. Il codice verrebbe compilato ed eseguito anche se la definizione della variabile di istanza test venisse spostata, ma non copiata, nella 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 {}

Proprietà statiche e catena delle aree di validità

Nonostante non vengano ereditate, le proprietà statiche rientrano in una catena delle aree di validità della classe che definisce tali proprietà e tutte le sottoclassi della classe. Di conseguenza, le proprietà statiche vengono dette nell'area di validità della classe nella quale vengono definite e di tutte le sue sottoclassi. Ciò significa che una proprietà statica risulta direttamente accessibile all'interno del corpo della classe che definisce la proprietà statica e tutte le sottoclassi della classe.

Nell'esempio che segue vengono modificate le classi definite nell'esempio precedente, al fine di mostrare come la variabile statica test definita nella classe Base si trovi nell'area di validità della classe Extender. In altre parole, la classe Extender può accedere alla variabile statica test senza aggiungervi un prefisso corrispondente al nome della classe che definisce 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 viene definita una proprietà di istanza che impiega lo stesso nome di una proprietà statica presente nella stessa classe o in una superclasse, la proprietà di istanza ha una precedenza maggiore nella catena delle aree di validità. La proprietà di istanza prevarica la proprietà statica, ovvero il valore della proprietà di istanza viene utilizzato al posto del valore della proprietà statica. Ad esempio, il codice seguente mostra che, se la classe Extender definisce una variabile di istanza denominata test, l'istruzione trace() impiega il valore della variabile di istanza anziché quello della variabile statica.

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 
    } 
     
}