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