48 – 49 OOP oggetti 4 – ereditarietà 2

trabocchetti di php

Nella lezione precedente, abbiamo creato una classe che era l’unione di 2 classi, una per il dado e l’altra per il dado truccato. Ora, avvicinandoci al concetto di ereditarietà, scomponiamo quella complicata e poco fluida classe unica in altre 2 più elementari, quella del dado normale:

class DadoNormale 
{
 private $valoreAttuale=0, $numeroFacce=6;
 
 public function __construct($numeroFacce)
 {
   if($numeroFacce>0) $this->numeroFacce = $numeroFacce;
 }
 public function lancia()
 {
   $this->valoreAttuale = rand(1, $this->numeroFacce);
   return $this->valoreAttuale;
 }
 public function getValore()
 {   return $this->valoreAttuale;  }
}

e, mantenendo la stessa logica, quella del dado truccato

class DadoTruc
{
 private $valoreAttuale=0, $numeroFacce=6, $facciaTruccata=0,     $quantoTruccata=0 ;

 public function __construct($numeroFacce, $facciaTruccata, $trucco)
 {
 if(numeroFacce>0) $this->numeroFacce=$numeroFacce;

 if ($facciaTruccata<$this->numeroFacce && $trucco>0)
 { $this->facciaTruccata=$facciaTruccata; $this->quantoTruccata = $trucco; }
 }

 public function lancia()
 {
 $this->valoreAttuale = rand(1, $this->numeroFacce + $this->quantoTruccata);
 if ( $this->facciaTruccata>0 && $this->valoreAttuale>$this->numeroFacce)
 $this->valoreAttuale = $this->facciaTruccata;
 return $this->valoreAttuale;
 }

 public function getValore()
 { return $this->valoreAttuale; }
}

creiamo i 2 nuovi oggetti e facciamo i nostri 100 lanci

$unDadoNormale = new DadoNormale(6);
$unDadoTruccato = new DadoTruc(6,3,10);

for($lanci=1; $lanci<100; $lanci++)
echo $unDadoNormale->lancia()." - ". $unDadoTruccato->lancia().io::NL;

una considerazione che si può subito notare è che abbiamo recuperato in semplicità e maggiore chiarezza nella logica, ma abbiamo perso in ridondanza nel codice, infatti abbiamo diverse similitudini tra le 2 classi, se faccio una modifica o una aggiunta o ho un errore mi si ripete anche nelle altre classi. Ecco che entra in gioco quel meccanismo che mi permette di utilizzare le stesse proprietà e gli stessi metodi pubblici da una classe all’altra: l’ereditarietà.

Il concetto è quello di stabilire quelle caratteristiche comuni che le classi prese in esame devono avere e selezionare una classe di base con tutti gli elementi di base che le altre classi dovranno avere. Nel nostro esempio il dado normale è quello che fungerà da classe madre:

class DadoNormale // superclasse, madre, antenata, base
{
 private $valoreAttuale=0, $numeroFacce=6;

 public function __construct($numeroFacce)
 { if(numeroFacce>0) $this->numeroFacce=$numeroFacce; }

 public function lancia()
 { $this->valoreAttuale=rand(1, $this->numeroFacce);
 return $this->valoreAttuale; }

 public function getValore()
 { return $this->valoreAttuale; }
}

la classe madre può essere chiamata anche classe base, superclasse, antenata e non presenta differenze rispetto a come era definita prima.

class DadoTruccato extends DadoNormale // sottoclasse, figlia, derivata
{
 
}

la classe che discende può essere chiamata figlia, sottoclasse, derivata. e la keyord che ci permetterà di fare ereditare le caratteristiche da quella madre è : extends. Infatti se provo a creare l’oggetto sulla classe DadoTruccato eseguendo un metodo lancia, il tutto funzionerà senza alcun codice apparentemente inserito nella classe:

$unDadoTruccato = new DadoTruccato(7);
for($lanci=0; $lanci<10; $lanci++)
 echo $unDadoTruccato->lancia().io::NL;

Il dado truccato, quindi in partenza, senza specificare null’altro, fa già tutto quello che fa il dado truccato, ora andremo a modificarlo, ricordando che le caratteristiche private della classe madre non vengono ereditate, di conseguenza non sono accessibili dalla classe figlia. Per fare ereditare una variabile locale di una classe madre a quella figlia, anziché private, la possiamo definire protected.

class DadoNormale // superclasse, madre, antenata, base
{
 protected $valoreAttuale=0, $numeroFacce=6;

 public function __construct($numeroFacce)
 {
 if($numeroFacce>0) $this->numeroFacce = $numeroFacce;
 }
 public function lancia()
 {
 $this->valoreAttuale = rand(1, $this->numeroFacce);
 return $this->valoreAttuale;
 }
 public function getValore()
 { return $this->valoreAttuale; }
}

Queste proprietà protected, saranno accessibili solo dalle classi figlie, ma non al di fuori delle classi. Infatti se non cambiavo la proprietà nella classe madre lasciandola private, nella classe figlia andavo a modificare la proprietà $this->numeroFacce della classe madre, php non avrebbe generato errore, ma avrebbe creato una nuova proprietà $numeroFacce indipendente da quella della classe madre. Con l’uso di protected il problema si risolve e possiamo aggiungere nella classe figlia DadoTruccato il codice necessario a truccare il dado:

class DadoTruccato extends DadoNormale // sottoclasse, figlia, derivata
{
 private $facciaTruccata=0, $quantoTruccata=0 ;
 
 function __construct($numeroFacce, $facciaTruccata, $trucco)
 {
  if ($numeroFacce>0) $this->numeroFacce=$numeroFacce;
 
  if ($facciaTruccata<$this->numeroFacce && $trucco>0)
  {
  $this->facciaTruccata = $facciaTruccata; $this->quantoTruccata =  $trucco;
  }
 }
}

la riga di codice all’interno del costruttore della classe figlia

 if ($numeroFacce>0) $this->numeroFacce=$numeroFacce;

è un controllo già presente nel costruttore della classe madre, di conseguenza avremmo del codice duplicato, tanto vale sfruttare quello della classe madre. Per cui per passare il parametro numeroFacce direttamente al costruttore madre lo devo invocare con la keyword: parent e gli passo al costruttore il parametro $numeroFacce. La sintassi è la seguente:

 parent::__construct($numeroFacce);

quindi i metodi della classe figlia possono costruire, previa invocazione (parent) sui metodi della classe madre. Ora creiamo il metodo lancia della classe figlia:

public function lancia() //overraide del metodo lancia
{
  $this->valoreAttuale = rand(1, $this->numeroFacce + $this- >quantoTruccata);
 
  if($this->valoreAttuale>$this->numeroFacce)
    $this->valoreAttuale = $this->facciaTruccata;
  return $this->valoreAttuale;
}

come si nota metodo lancia() della classe figlia ha lo stesso nome di quella madre. Il richiamo di un metodo dalla classe figlia con lo stesso nome, lo sovrascrive cioè si sta effettuando un override del metodo lancia. Sarebbe anche possibile invocarlo, anche se in questo caso avrebbe poco senso:

parent::lancia()

Qualora si volesse, per qualsiasi motivo, impedire l’override di un metodo della classe madre, bisogna inserire nel metodo madre la keyword final, a questo punto il metodo lancia() della classe figlia dovrà cambiare il suo nome

final public function lancia()
 {
 $this->valoreAttuale = rand(1, $this->numeroFacce);
 return $this->valoreAttuale;

final non può essere utilizzato sulle variabili. Il suo utilizzo su una intera classe sbarra la porta alla sua ereditarietà.