giovedì 5 dicembre 2013

La OOP in PHP spiegata in un esempio - Parte 7 - Ereditarietà e Costrutto parent::


Riprendendo il codice di test, non si fa altro che disegnare un quadrato. Per farlo però sono state necessarie ben 6 istruzioni. Mentre ne sarebbero dovute occorrere al più due:
  1. Creazione di oggetto poligono rettangolare per il quale definire un punto d'origine (angolo superiore sinistro), altezza, larghezza e colore
  2. invocare il metodo di disegno dell'oggetto
//Com'è
$poligono = new cPoligono($tavolozza['grigio']);
$poligono->setVertice($puntoA);
$poligono->setVertice($puntoB);
$poligono->setVertice($puntoC);
$poligono->setVertice($puntoD);
$poligono->disegna($ad);

//ma potrebbe essere
$rettangolo = new cPoligonoRettangolo($oOrigine, $base, $altezza, $oColore);
$rettangolo->disegna($ad);


//oppure
$rettangolo = new cRettangolo( $oPoligono, $oOrigine, $base, $altezza, $oColore);
$rettangolo->disegna($ad);

//oppure
$rettangolo = cFabbricaPoligoni::rettangolo($oOrignine, $base, $altezza, $oColore);
$rettangolo->disegna($ad);


per ottenere tale risultato si può:
  • specializzare la classe cPoligono in una classe cPoligonoRettangolo tramite l'ereditarietà
  • implementare una nuova classe cRettangolo che richieda tra gli argomenti una classe cPoligono (o meglio formalizzare l'interfaccia di cPoligono e richiedere un argomento che implementi l'interfaccia)
  • Definire l'interfaccia iPoligono e realizzare una classe cFabbricaPoligoni. In tale oggetto fabbrica sono definiti dei metodi statici addetti alla produzione di figure geometriche specifiche.

Caso 1 - Estendere la classe cPoligono con la classe cPoligonoRettangolo

Si tratta di definire una nuova classe cPoligonoRettangolo che estende la classe cPoligono per mezzo della parola chiave extends. Una possibile interfaccia di tale classe può essere:

<?php
require_once './cPoligono.php';
require_once './iCoordinate.php';
require_once './iColore.php';

class cPoligonoRettangolo extends cPoligono{
    private $base,$altezza,$oOrigine ;
 
    public function __construct(\iCoordinate $oOrigine, $base ,$altezza, \iColore $oColore) {}  
    public function setOrigine(\iCoordinate $oOrigine){}
    public function getOrigine(){}
    public function setAltezza($altezza){}
    public function getAltezza(){}
    public function setBase($base){}
    public function getBase(){}         
}
?>

Occorre ridefinire il metodo costruttore (__construct()) perchè acquisisca i nuovi elementi necessari alla produzione di un rettangolo. Ampliamo l'interfaccia di cPoligono con un nuovo metodo delVertici() che ha il compito di cancellare tutti i vertici caricati. Implementiamo i metodi della classe ottenendo:


<?php
require_once './cPoligono.php';
require_once './cCoordinate.php';
require_once './iCoordinate.php';
require_once './iColore.php';

class cPoligonoRettangolo extends cPoligono{
    private $base = 1,$altezza = 1, $oOrigine ;
 
    public function __construct(\iCoordinate $oOrigine, $base ,$altezza, \iColore $oColore) {
        parent::__construct($oColore);
        $this->setOrigine($oOrigine);
        $this->setBase($base);
        $this->setAltezza($altezza);      
    }  
    public function setOrigine(\iCoordinate $oOrigine){
        $this->oOrigine=$oOrigine;
        $this->ricalcoloVertici();
    }
    public function getOrigine(){
        return $this->oOrigine;
    }
    public function setAltezza($altezza){
        $altezza = (int)$altezza;
        if ($altezza>0)
            $this->altezza=$altezza;
        else
        throw new Exception("L'altezza di un rettagnolo deve essere un intero maggiore di 0");
        $this->ricalcoloVertici();
    }
    public function getAltezza(){
        return $this->altezza;
    }
    public function setBase($base){
        $base = (int) $base;
        if($base>0)
            $this->base=$base;
        else
            throw new Exception("La base di un rettangolo deve essere un intero maggiore di 0");
        $this->ricalcoloVertici();
    }
    public function getBase(){
        return $this->base;
    }      
 
    private function ricalcoloVertici(){
        $this->delVertici();
        $this->setVertice($this->getOrigine());
        $this->setVertice(new cCoordinate($this->getOrigine()->getX(), $this->getOrigine()->getY()+$this->getAltezza()-1));
        $this->setVertice(new cCoordinate($this->getOrigine()->getX()+$this->getBase()-1, $this->getOrigine()->getY()+$this->getAltezza()-1));
        $this->setVertice(new cCoordinate($this->getOrigine()->getX()+$this->getBase()-1, $this->getOrigine()->getY()));                            
    }
}
?>

Si osservi il metodo costruttore (__construct). Al suo interno si fa uso di una chiamata particolare ossia parent::__construct($oColore). Il compito di parent::nomeMetodo() è quello di permette d'invocare un metodo della classe genitrice se ridefinito nella classe figlia, ossia definendo un metodo in una classe figlia avente lo stesso nome di un metodo della classe genitrice, è ancora possibile invocare il metodo della classe genitrice tramite il costrutto parent::nomeMetodo(). Nel caso in esame la nuova classe imposta i propri dati nel proprio metodo costruttore, e lascia che la sua genitrice (cPoligono) faccia altrettanto per i propri dati nel proprio metodo costruttore. Il fatto che si parli di metodi costruttori è incidentale, poichè tale meccanismo è valido per qualunque metodo definito nella classe genitrice (superclass) e ridefinito in una figlia (subclass).

Si osservi, che ogni volta che è impostata una nuova base, altezza o punto d'origine, la classe azzera tutti i vertici e li ricalcola in base ai nuovi valori. Ciò ha degli effetti negativi sui metodi ereditati, perchè setVertice, richiamabile su un oggetto cPoligonoRettangolo, può snaturare la figura aggiungendo nuovi vertici oltre i 4 previsti per un rettangolo, ed eventuali vertici aggiunti in fase di esecuzione dello script possono essere perduti nel momento in cui si modifica la base o l'altezza. Una soluzione alternativa, che rende di fatto inefficace il metodo setVertice e impedisce che il rettangolo muti in altro, è la seguente:


<?php
require_once './cPoligono.php';
require_once './cCoordinate.php';
require_once './iCoordinate.php';
require_once './iColore.php';

class cPoligonoRettangolo extends cPoligono{
    private $base = 1,$altezza = 1, $oOrigine ;
    
    public function __construct(\iCoordinate $oOrigine, $base ,$altezza, \iColore $oColore) {
        parent::__construct($oColore);
        $this->setOrigine($oOrigine);
        $this->setBase($base);
        $this->setAltezza($altezza);        
    }    
    public function setOrigine(\iCoordinate $oOrigine){
        $this->oOrigine=$oOrigine;        
    }
    public function getOrigine(){
        return $this->oOrigine;
    }
    public function setAltezza($altezza){
        $altezza = (int)$altezza;
        if ($altezza>0)
            $this->altezza=$altezza;
        else
        throw new Exception("L'altezza di un rettagnolo deve essere un intero maggiore di 0");        
    }
    public function getAltezza(){
        return $this->altezza;
    }
    public function setBase($base){
        $base = (int) $base;
        if($base>0)
            $this->base=$base;
        else
            throw new Exception("La base di un rettangolo deve essere un intero maggiore di 0");        
    }
    public function getBase(){
        return $this->base;
    }        
    
    public function getVertice($numero) {
        $this->calcoloVertici();
        parent::getVertice($numero);
    }
    
    public function setVertice(\iCoordinate $oCoordinate) {}
    
    public function disegna(\iAreaDiDisegno $oAreaDiDisegno) {
        $this->calcoloVertici();
        parent::disegna($oAreaDiDisegno);
    }
    
    private function calcoloVertici(){
        $this->delVertici();
        parent::setVertice($this->getOrigine());
        parent::setVertice(new cCoordinate($this->getOrigine()->getX(), $this->getOrigine()->getY()+$this->getAltezza()-1)); 
        parent::setVertice(new cCoordinate($this->getOrigine()->getX()+$this->getBase()-1, $this->getOrigine()->getY()+$this->getAltezza()-1));
        parent::setVertice(new cCoordinate($this->getOrigine()->getX()+$this->getBase()-1, $this->getOrigine()->getY()));                               
    }
}
?>

In questa soluzione è stato:

  • ridefinito setVertice() rendendolo un metodo privo d'istruzioni. Ciò perchè non ha senso all'interno di una classe cPoligonoRettangolo che definisce la figura tramite una base, un'altezza e un punto d'origine. Il metodo potrà essere ancora invocato senza errore dal client (chi utilizza la classe) ma non avrà alcun effetto. Al più si può sollevare un'eccezione che avverta sull'avvenuta impostazione dei 4 vertici e lasciare al client il compito di decidere cosa fare tramite try-catch.
  • ridefinito getVertice() perchè ricalcoli i vertici prima di invocare il metodo della classe genitrice (parent::getVertice()) che ritorna uno dei 4 vertici del rettangolo
  • ridefinito il metodo disegna() perchè calcoli i vertici prima di procedere al disegno, effettuato tramite la chiamata al metodo disegna della classe genitrice
  • modificato il metodo calcoloVertici() perchè acceda la metodo setVertice della classe genitrice e non al proprio, ormai privo di funzionalità

.