Con questo articolo approfondiamo l’ereditarietà in PHP. Come sappiamo una classe derivata, ovvero una sottoclasse, eredita tutti i metodi della classe genitore, la classe base. Ora spiegherò più in dettaglio cosa e come viene ereditato.
Ereditare il costruttore e il distruttore
Chi viene da C++ potrebbe rimanere sorpreso da un titolo del genere. Ebbene si, il costruttore e il distruttore sono ereditabili. Ciò permette quindi di evitare di definire il costruttore e il distruttore nelle sottoclassi, poichè viene automaticamente usato quello della classe base. Per mostrare ciò riprendo l’esempio dell’articolo PHP 5 ad oggetti: usare l’ereditarietà.
<?php
// Definizione della classe Animale
class Animale {
private $specie;
// Costruttore
public function __construct($specie){
$this->specie = $specie;
echo "animale";
}
// Ritorna la specie
public function getSpecie(){
return $this->specie;
}
// Imposta la specie
public function setSpecie($specie){
$this->specie = $specie;
}
}
// Definizione della classe Mammifero
class Mammifero extends Animale {
private $corna;
public function haCorna(){
return $this->corna;
}
public function setCorna($corna){
$this->corna = $corna;
}
}
// Definizione della classe Uccello
class Uccello extends Animale {
private $rapace;
public function isRapace(){
return $this->rapace;
}
public function setRapace($rapace){
$this->rapace = $rapace;
}
}
// Costruisco un Mammifero e un Uccello
$mamm = new Mammifero("Leone"); // Stampa "animale"
$ucc = new Uccello("Gufo"); // Stampa "animale"
?>
Eliminando i costruttori delle sottoclassi abbiamo risparmiato codice, e quindi ridotto il rischio di bug. Dato che stampa "animale" sia nella creazione di un Mammifero che di un Uccello, se ne deduce che viene richiamato il costruttore della superclasse.
Ovviamente in questo caso specifico sarebbe stato meglio tenere i costruttori per inizializzare gli attributi $corna (in Mammifero) e $rapace (in Uccello). Per garantire che anche l’attributo $specie sia impostato correttamente è possibile richiamare il costruttore della classe genitore tramite la parola chiave parent, che identifica appunto la superclasse Animale. Ecco l’esempio rivisto (riporto solo la parte rivista della classe Mammifero per brevità):
<?php
// Definizione della classe Mammifero
class Mammifero extends Animale {
private $corna;
public function __construct($specie){
parent::__construct($specie); // chiamo il costruttore di Animale
$corna = false; // inizializzo $corna
}
}
?>
Overriding di metodi, del costruttore, e del distruttore
Se si vuole ridefinire un metodo della superclasse in una sottoclasse, è possibile farlo. Ciò permette di avere versioni più specializzate dei metodi. Questo è quelllo che si dice overriding dei metodi.
<?php
// Definizione della classe Mammifero
class Mammifero extends Animale {
private $corna;
public function __construct($specie){
$this->setSpecie($specie);
}
public function setSpecie($specie){
if ($specie != "leone" && $specie != "leonessa"){
die();
}
parent::setSpecie($specie);
}
public function haCorna(){
return $this->corna;
}
public function setCorna($corna){
$this->corna = $corna;
}
}
// Creo un "leone" e una "leonessa"
$mamm = new Mammifero("leone"); // Ok
$mamm = new Mammifero("leonessa"); // Ok
// Creo una "tigre"
$mamm = new Mammifero("tigre"); // die()
// Creo una tigre come se fosse un animale qualunque
$mamm = new Animale("tigre"); // Ok, nel setSpecie() di Animale non ci sono controlli
?>
Nell’esempio abbiamo creato una versione specializzata di setSpecie(), che permette di impostare solamente "leone" o "leonessa". Per impostare il valore abbiamo sfruttanto il metodo setSpecie() della superclasse Animale, tramite parent.
E’ importante notare che se si ridefinisce un metodo, anche con parametri diversi, tutti i metodi ereditati con lo stesso nome vengono nascosti! Ciò vale anche per il costruttore. Ad esempio (riporto solo la versione modificata di setSpecie() e del costruttore):
<?php
// Definizione della classe Mammifero
class Mammifero extends Animale {
// ... resto della classe
public function __construct($specie, $corna){
$this->setSpecie();
$this->setCorna($corna);
}
public function setSpecie(){
parent::setSpecie("leone");
}
// ... resto della classe
}
// Creo un "leone" e una "leonessa"
$mamm = new Mammifero("leone"); // Errore, non c'è un costruttore con un solo parametro
$mamm->setSpecie("leonessa"); // Errore, setSpecie() non prende parametri
?>
Ho modificato setSpecie() in modo che non prenda parametri, e il costruttore in modo che prenda due parametri. Creano oggetti Mammifero come si faceva prima si hanno errori, poichè le versioni ereditate di setSpecie() e del costruttore non sono più visibili.
La keyword final per bloccare l’ereditarietà
E’ possibile impedire che una classe abbia sottoclassi, o che sia possibile l’override di un metodo. Basta specificare la keyword final prima nella dichiarazione della classe o del metodo, in questo modo:
<?php
// Una classe non ereditabile
final class MyFinalClass {
// metodo con override non permesso
public final function metodo(){
// ... codice del metodo ...
}
}
// Una classe ereditabile
class MyClass {
// metodo con override non permesso
public final function metodo(){
// ... codice del metodo ...
}
}
// Tento di estendere MyFinalClass
class MyFinalClass2 extends MyFinalClass { // Fatal error, MyFinalClass non è ereditabile
// ... codice della classe ...
}
// Estendo MyClass
class MyClass2 extends MyClass { // Ok, MyClass è ereditabile
// Tento l'override di metodo()
public function metodo(){ // Errore, metodo() non permette l'override
// ... codice del metodo ...
}
}
?>
In questo modo si può bloccare l’ereditarietà e la specializzazione di metodi particolari.