venerdì 6 dicembre 2013

La OOP in PHP spiegata in un esempio - Parte 8


Caso 2 - Creazione di un oggetto cRettangolo che non estenda cPoligono

Il secondo caso prevede la creazione di un oggetto cRettangolo che si ponga sullo stesso piano di cPoligono. La classe cRettangolo definisce, come nel caso precedente, un punto d'origine, base, altezza e colore. A questi si aggiunge la richiesta di un oggetto la cui classe implementi l'interfaccia iPoligono. Iniziamo quindi con il definire l'interfaccia iPoligono.  Partendo dal codice di cPoligono si tolga tutto ciò che non è un metodo pubblico, e dai metodi pubblici tutto ciò che è implementazione di altre interfacce. Ciò che resta è:

<?php
interface iPoligono {      
    public function delVertici();
    public function setVertice(\iCoordinate $oCoordinate);
    public function getVertice($numero);
}
?>

Modifichiamo la classe cPoligono perchè indichi che sta implementando l'interfaccia iPoligono:

<?php

require_once './iFigura.php';
require_once './iColore.php';
require_once './iColorabile.php';
require_once './iPoligono.php';
require_once './tColore.php';
require_once './tColorabile.php';

class cPoligono implements iFigura, Iterator, iColore, iColorabile, iPoligono {
...

Una possibile interfaccia della classe cRettangolo può essere:


<?php
require_once './iFigura.php';
require_once './iColorabile.php';
require_once './iColore.php';
require_once './tColorabile.php';

class cRettangolo implements iFigura, iColorabile {
    
    //Interfaccia iColorabile gestita dal trait tColorabile
    use tColorabile;
    
    private $oPoligono, $base, $altezza, $oOrigine;

    public function __construct(\iCoordinate $oOrigine, $base, $altezza, \iColore $oColore, \iPoligono $oPoligono) {}
    public function setPoligono(\iPoligono $oPoligono) {}
    private function getPoligono() {}
    public function setOrigine(\iCoordinate $oOrigine) {}
    public function getOrigine() {}
    public function setAltezza($altezza) {}
    public function getAltezza() {}
    public function setBase($base) {}
    public function getBase() {}
    private function calcoloVertici() {}

    //Interfaccia iFigura
    public function disegna(\iAreaDiDisegno $oAreaDiDisegno) {}  
}

?>


Scrivendo il codice dei metodi otteniamo:

<?php
require_once './iFigura.php';
require_once './iColorabile.php';
require_once './iColore.php';
require_once './tColorabile.php';

class cRettangolo implements iFigura, iColorabile {
    
    use tColorabile;
    
    private $oPoligono, $base, $altezza, $oOrigine;

    public function __construct(\iCoordinate $oOrigine, $base, $altezza, \iColore $oColore, \iPoligono $oPoligono) {
        $this->setOrigine($oOrigine);
        $this->setBase($base);
        $this->setAltezza($altezza);
        $this->setColore($oColore);
        $this->setPoligono($oPoligono);
    }

    public function setPoligono(\iPoligono $oPoligono) {
        $this->oPoligono = $oPoligono;
    }

    private function getPoligono() {
        return $this->oPoligono;
    }

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

    private function calcoloVertici() {
        $this->getPoligono()->delVertici();
        $this->getPoligono()->setVertice($this->getOrigine());
        $this->getPoligono()->setVertice(new cCoordinate($this->getOrigine()->getX(), $this->getOrigine()->getY() + $this->getAltezza() - 1));
        $this->getPoligono()->setVertice(new cCoordinate($this->getOrigine()->getX() + $this->getBase() - 1, $this->getOrigine()->getY() + $this->getAltezza() - 1));
        $this->getPoligono()->setVertice(new cCoordinate($this->getOrigine()->getX() + $this->getBase() - 1, $this->getOrigine()->getY()));
    }

    //Interfaccia iFigura
    public function disegna(\iAreaDiDisegno $oAreaDiDisegno) {
        $this->calcoloVertici();
        $oAreaDiDisegno->tracciaSegmento($this->getPoligono()->getVertice(0), $this->getPoligono()->getVertice(1), $this->getColore());
        $oAreaDiDisegno->tracciaSegmento($this->getPoligono()->getVertice(1), $this->getPoligono()->getVertice(2), $this->getColore());
        $oAreaDiDisegno->tracciaSegmento($this->getPoligono()->getVertice(2), $this->getPoligono()->getVertice(3), $this->getColore());
        $oAreaDiDisegno->tracciaSegmento($this->getPoligono()->getVertice(3), $this->getPoligono()->getVertice(0), $this->getColore());
    }  
}

?>

I metodi d'accesso allo stato dell'oggetto sono come sempre molto banali. Il metodo privato calcoloVertici(), a differenza del caso precedente, accede al proprio oggetto poligono (che implementa l'interfaccia iPoligono) per memorizzare le informazioni sui vertici. Il metodo disegna merita un po' di attenzione. In prima istanza si potrebbe essere tentati di delegare all'oggetto $oPoligono sia la gestione del colore, quindi senza specificare la use tColore, e la gestione del disegno trasformando la funzione disegna in:

public function disegna(\iAreaDiDisegno $oAreaDiDisegno) {
    $this->calcoloVertici();
    $this->getPoligono()->disegna($oAreaDiDisegno);
}  

Questa forma non sarebbe corretta logicamente, perchè può dare luogo ad errori inaspettati. Se il client dovesse passare non un oggetto cPoligono, ma un altro oggetto la cui classe implementa iPoligono senza implementare iFigura (quindi rispetta il requisito del metodo setPoligono(\iPoligono $oPoligono) ma è priva del metodo disegna(\iAreaDiDisegno $oAreaDiDisegno), al momento dell'esecuzione il metodo potrebbe non esistere e otterremo un errore in esecuzione. Invece nel modo su esposto l'oggetto $oPoligono è utilizzato in tutti e soli i metodi definiti dall'interfaccia iPoligono, $oAreaDiDisegno è sfruttato per tutti e soli i metodi definiti per l'interfaccia iAreaDiDisegno e la gestione del colore è data in pasto al trait tColorabile.