mercoledì 11 dicembre 2013

La OOP in PHP spiegata in un esempio - Parte 10 - Ampliamo le Funzionalità


Prima di procedere oltre, realizziamo un ampliamento delle funzionalità di alcune classi. in particolare faremo definiremo delle operazioni di trasformazione per l'interfaccia iCoordinate e iFigura. Tali operazioni sono traslazione, scala e rotazione.

A tal fine iniziamo con il modificare le due interfacce in modo da ottenere:

<?php
interface iCoordinate {  
    public function setX($x);
    public function setY($y);
    public function getX();
    public function getY();
    public function setXY($x, $y);
    public function setXYArray($XY);  
    public function getXYArray();
    public function trasla($dx, $dy);  
    public function scala(\iCoordinate $oPuntoDiFuga, $scalaX, $scalaY);  
    public function ruota(\iCoordinate $oCentroRotazione, $angolo);
}
?>


<?php
interface iFigura{
    public function disegna(\iAreaDiDisegno $oAreaDiDisegno);
    public function trasla($dx,$dy);
    public function ruota(\iCoordinate $oCentroRotazione, $angolo);
    public function scala(\iCoordinate $oPuntoDiFuga, $scalaX, $scalaY);
}
?>

Dato che iCoordinate è implementata da cCoordinate e da cPunto tramite il trait tCoordinate modificheremo la classe cCoordinate e tCoordinate (trait di ereditarietà per delega di oggetti che implementano l'interfaccia cCoordinate). Prima di vedere il codice si precisa che con la traslazione si sposta un punto di un certo delta x,y, ossia alle coordinate iniziali sono aggiunte le relative coordinate dx,dy. Con ruota si effettua la rotazione di un punto rispetto ad un centro, passato come argomento, di un certo angolo espresso in gradi. La scala su un punto non ha molto senso, e si traduce nello scivolamento del punto di coordinate x,y di una certa percentuale di scala, indicata dai parametri (se di uguale valore), lungo il segmento che congiunge il punto che si scala con il punto di fuga. Il codice che otterremo è:

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

class cCoordinate implements iCoordinate{

    ...
 
    public function trasla($dx, $dy) {
        $dx = (int) $dx;
        $dy = (int) $dy;
        $this->setX($this->getX()+$dx);
        $this->setY($this->getY()+$dy);
    }
 
    public function scala(\iCoordinate $oPuntoDiFuga, $scalaX, $scalaY) {      
        $x = ($this->getX()-$oPuntoDiFuga->getX())*$scalaX;
        $y = ($this->getY()-$oPuntoDiFuga->getY())*$scalaY;
        $this->setX($x+$oPuntoDiFuga->getX());
        $this->setY($y+$oPuntoDiFuga->getY());      
    }
 
    public function ruota(\iCoordinate $oCentroRotazione, $angolo) {
        $this->trasla(-$oCentroRotazione->getX(), -$oCentroRotazione->getY());
        $x = $this->getX() * cos(deg2rad($angolo)) - $this->getY() * sin(deg2rad($angolo));
        $y = $this->getY() * cos(deg2rad($angolo)) + $this->getX() * sin(deg2rad($angolo));
        $this->setX($x);
        $this->setY($y);
        $this->trasla($oCentroRotazione->getX(), $oCentroRotazione->getY());
    }

}

?>


<?php
trait tCoordinate {
    //implementazine dell'interfaccia iCoordinate
   ...
 
    public function trasla($dx, $dy) {
        $this->getCoordinate()->trasla($dx, $dy);
    }
 
    public function scala(\iCoordinate $oPuntoDiFuga, $scalaX, $scalaY) {      
        $this->getCoordinate()->scala($oPuntoDiFuga, $scalaX, $scalaY);
    }
 
    public function ruota(\iCoordinate $oCentroRotazione, $angolo) {
        $this->getCoordinate()->ruota($oCentroRotazione, $angolo);
    }
}
?>

Come di può vedere è cCoordinate che contiene il vero codice mentre tCoordinate, come per gli altri metodi in esso contenuti, delega un oggetto che implementa l'interfaccia iCoordinate a compiere l'operazione.

A questo punto definiamo dei metodi di trasformazione (trasla, ruota e scala) anche per l'interfaccia iFigura, di modo che ogni classe dedicata all'implementazione di una figura rappresentabile in un oggetto iAreaDiDisegno possa effettuare tali trasformazioni.

<?php
interface iFigura{
    public function disegna(\iAreaDiDisegno $oAreaDiDisegno);
    public function trasla($dx,$dy);
    public function ruota(\iCoordinate $oCentroRotazione, $angolo);
    public function scala(\iCoordinate $oPuntoDiFuga, $scalaX, $scalaY);
}
?>

Nel nostro attuale codice iFigura è implementato da cPoligono (cPoligonoRettangolo per ereditarietà) e cRettangolo. Quindi occorre modificare queste due classi affinché implementino i nuovi metodi dichiarati nell'interfaccia. 

In particolare, dato che i metodi hanno esattamente gli stessi argomenti sia per iCoordinate che per iFigura, e dato che un poligono non è altro che un insieme di vertici (oggetti iCoordinate), i nostri metodi nella classe cPoligono non devono far altro che richiamare il metodo di trasformazione per ogni vertice. Infine aggiungiamo all'interfaccia iPoligono, e implementiamo nella classe cPoligono ,un metodo di utilità numVertici() che ci dica da quanti vertici è composto il poligono. La classe cPoligono diventa:

<?php

...

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

    ...   
 
    public function numVertici() {
        return count($this->vertici);
    }


    ...      

    //Interfaccia iFigura
    public function disegna(\iAreaDiDisegno $oAreaDiDisegno) {
        $ultimo = $primo = null;
        foreach ($this as $punto)
            if (!is_null($ultimo)) {
                $oAreaDiDisegno->tracciaSegmento($ultimo, $punto, $this->getColore());
                $ultimo = $punto;
            }
            else
                $primo = $ultimo = $punto;
        $oAreaDiDisegno->tracciaSegmento($primo, $ultimo, $this->getColore());
    }
 
    public function trasla($dx,$dy){
        $dx = (int) $dx;
        $dy = (int) $dy;       
        foreach ($this as $vertice)
            $vertice->trasla($dx, $dy);              
    }
 
    public function scala(\iCoordinate $oPuntoDiFuga, $scalaX, $scalaY) {
        foreach ($this as $vertice)
            $vertice->scala ($oPuntoDiFuga, $scalaX, $scalaY);
    }
 
    public function ruota(\iCoordinate $oCentroRotazione, $angolo) {
        foreach ($this as $vertice)
            $vertice->ruota($oCentroRotazione, $angolo);
    }
   

    ...
}

?>

Invece nella classe cRettangolo, che fa uso di un oggetto iPoligono, si potrebbe essere tentati di chiamare i metodi di trasla, ruota e scala direttamente sul poligono memorizzato all'interno della classe. Ciò è logicamente errato, poiché tali metodi non sono presenti nell'interfaccia iPoligono richiesta dalla classe cRettangolo al proprio oggetto interno $oPoligono. Invece $oPoligono è certo un iPoligono che ha come metodo setVertice(\iCoordinate $vertice). Quindi i suoi vertici di certo avranno tali metodi. D'altra parte sussiste un'ulteriore complicazione derivante dalla necessità di calcolare i vertici prima del disegno e dal non perdere le trasformazioni in caso di cambio delle dimensioni di base o altezza. Per tale motivo, così come i vertici sono calcolati appena prima della rappresentazione nell'area di disegno, allo stesso modo le trasformazioni vanno accumulate per poi essere applicate appena prima del disegno e subito dopo il calcolo dei vertici. Ne consegue che:

<?php
...

class cRettangolo implements iFigura, iColorabile {
 
    ...


    private $trasform=array();

    ...
   

    //Interfaccia iFigura
    public function disegna(\iAreaDiDisegno $oAreaDiDisegno) {
        $this->calcoloVertici();
        foreach ($this->trasform as $func)
            for($v=0; $v<$this->getPoligono()->numVertici();$v++)
                switch ($func[0]) {
                    case "ruota":
                        $this->getPoligono()->getVertice($v)->ruota($func[1][0], $func[1][1]);
                        break;
                    case "trasla":
                        $this->getPoligono()->getVertice($v)->trasla($func[1][0], $func[1][1]);
                        break;
                    case "scala":
                        $this->getPoligono()->getVertice($v)->scala($func[1][0], $func[1][1], $func[1][2]);
                        break;
                }
                                 
        $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());
    }
 
    public function trasla($dx, $dy) {
        $this->trasform[]=["trasla",[$dx,$dy]];
    }
 
    public function scala(\iCoordinate $oPuntoDiFuga, $scalaX, $scalaY) {
        $this->trasform[]=["scala",[$oPuntoDiFuga, $scalaX, $scalaY]];      
    }
 
    public function ruota(\iCoordinate $oCentroRotazione, $angolo) {
        $this->trasform[]=["ruota",[$oCentroRotazione,$angolo]];              
    }
}

?>

Ora che abbiamo apportato le modifiche necessarie mettiamo alla prova le nostre classi, aggiungendo del codice al nostro script index.php, affinchè:

  • sia disegnato un rettangolo e ruotato, rappresentandolo ogni volta, di 360° a passi di 45°. 
  • sia disegnato un rettangolo blu e al suo interno lo stesso rettangolo ridotto in scala del 50%.
A tal fine modifichiamo il nostro script come segue:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Forme Geometriche</title>
    </head>
    <body>
        <?php
            require_once './cAreaDiDisegnoHTML.php';
            require_once './cPoligono.php';
            require_once './cColore.php';
            require_once './cCoordinate.php';
            require_once './cPunto.php';
            require_once './cPoligonoRettangolo.php';
            require_once './cRettangolo.php';
            require_once './cFabbricaPoligoni.php';
         
            $tavolozza = [
                'rosso'  => new cColore(255, 0, 0),
                'verde'  => new cColore(0, 255, 0),
                'blu'    => new cColore(0, 0, 255),
                'grigio' => new cColore(127, 127, 127),
                'nero'   => new cColore(0,0,0)
            ];
         
            $ad = new cAreaDiDisegnoHTML(100, 100);
         
            $puntoA=new cPunto(new cCoordinate(1, 1), $tavolozza['verde']);
            $puntoB=new cPunto(new cCoordinate(1, 20), $tavolozza['verde']);
            $puntoC=new cPunto(new cCoordinate(20, 20), $tavolozza['verde']);
            $puntoD=new cPunto(new cCoordinate(20, 1), $tavolozza['verde']);
         
                                 
            $poligono = new cPoligono($tavolozza['grigio']);
            $poligono->setVertice($puntoA);
            $poligono->setVertice($puntoB);
            $poligono->setVertice($puntoC);
            $poligono->setVertice($puntoD);
            $poligono->disegna($ad);
         
            $ad->tracciaSegmento($puntoA, $puntoC, $puntoA);
            $ad->tracciaSegmento($puntoB, $puntoD, $tavolozza['rosso']);
         
            $ad->tracciaPunto($puntoA, $tavolozza['blu']);
            $ad->tracciaPunto($puntoB, $tavolozza['blu']);
            $ad->tracciaPunto($puntoC, $tavolozza['blu']);
            $ad->tracciaPunto($puntoD, $tavolozza['blu']);                      
         
            $origine = new cCoordinate(28, 28);
         
            $rettangolo = new cPoligonoRettangolo($origine, 10, 10, $tavolozza['verde']);
            $rettangolo->ruota($origine, 45);
            $rettangolo->disegna($ad);
         
            $pol = new cPoligono($tavolozza['rosso']);
            $rettangolo = new cRettangolo($origine, 10, 10, $pol, $pol);
            $rettangolo->trasla(-3, -3);        
            $rettangolo->disegna($ad);
         
            $rettangolo = cFabbricaPoligoni::rettangolo($origine, 10, 10, $tavolozza['blu']);
            $rettangolo->trasla(-6, -6);
            $rettangolo->disegna($ad);
                     
            $rettangolo = cFabbricaPoligoni::rettangolo(new cCoordinate(50, 50), 10, 10, new cColore(rand(0, 255), rand(0, 255), rand(0, 255)));
            for($angolo=0;$angolo<360; $angolo+=45){                              
                $rettangolo->ruota(new cCoordinate(50, 50), $angolo);
                $rettangolo->disegna($ad);
                $rettangolo->setColore(new cColore(rand(0, 255), rand(0, 255), rand(0, 255)));
            }



            $rettangolo = new cRettangolo(new cCoordinate(40, 1), 20, 20, $tavolozza['blu'], new cPoligono($tavolozza['nero']));
            $rettangolo->disegna($ad);
            $rettangolo->scala(new cCoordinate(50, 11), 0.5, 0.5);
            $rettangolo->setColore($tavolozza['rosso']);
            $rettangolo->disegna($ad);
         
            echo $ad->output();
        ?>
    </body>
</html>

Il risultato nel browser sarà qualcosa di simile a questo:

L'output dello script index.php ulteriormente modificato