martedì 3 dicembre 2013

La OOP in PHP spiegata in un esempio - Parte 6 - I Trait



Visto che siamo arrivati ad ottenere un risultato funzionante, prima di procedere oltre è il caso di migliorare il nostro codice.Si osservi che esistono due metodi (getColore e setColore) che si ripetono identici in oggetti che hanno la proprietà di possedere un colore. Ciò suggerisce la creazione di una interfaccia implementata dagli oggetti colorabili:


<?php
interface iColorabile {
    public function setColore(\iColore $oColore);
    public function getColore();
}
?>

Tale interfaccia può essere aggiunta alla lista delle interfacce implementate da cPunto e cPoligono le cui dichiarazioni nei rispettivi file cambiano in:

<?php

require_once './iPunto.php';
require_once './iColore.php';
require_once './iCoordinate.php';
require_once './iColorabile.php';

class cPunto implements iPunto, iColore, iCoordinate, iColorabile {
...

<?php

require_once './iFigura.php';
require_once './iColore.php';
require_once './iColorabile.php';

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

Ancora più importante possiamo notare i tatti simili (codice ripetuto sempre uguale) in diverse classi. Ad esempio si osservi come nella ereditarietà per delega ci sia una monotona ripetizione di funzioni in cPunto, così come in cPoligono:


...
   //Interfaccia iColore
    public function setRGB($r, $g, $b) {
        $this->getColore()->setRGB($r, $g, $b);
    }

    public function setRGBArray($RGB) {
        $this->getColore()->setRGBArray($RGB);
    }

    public function getRGBArray() {
        return $this->getColore()->getRGBArray();
    }

    public function setR($r) {
        $this->getColore()->setR($r);
    }

    public function setG($g) {
        $this->getColore()->setG($g);
    }

    public function setB($b) {
        $this->getColore()->setB($b);
    }

    public function getR() {
        return $this->getColore()->getR();
    }

    public function getG() {
        return $this->getColore()->getG();
    }

    public function getB() {
        return $this->getColore()->getB();
    }
...


Possiamo quindi definire un trait tColore (è ovvio perchè il nome inizi con t) che racchiuda tali metodi, e indicare alle classi di usare il relativo trait. Iniziamo con il definire i traits tColore e tCoorindate, entrambi casi di ereditarietà per delega, in due file separati:

<?php
trait tColore {
    //Implementazione dell'interfaccia iColore
    public function setRGB($r, $g, $b) {
        $this->getColore()->setRGB($r, $g, $b);
    }

    public function setRGBArray($RGB) {
        $this->getColore()->setRGBArray($RGB);
    }

    public function getRGBArray() {
        return $this->getColore()->getRGBArray();
    }

    public function setR($r) {
        $this->getColore()->setR($r);
    }

    public function setG($g) {
        $this->getColore()->setG($g);
    }

    public function setB($b) {
        $this->getColore()->setB($b);
    }

    public function getR() {
        return $this->getColore()->getR();
    }

    public function getG() {
        return $this->getColore()->getG();
    }

    public function getB() {
        return $this->getColore()->getB();
    }  
}
?>


<?php
trait tCoordinate {
    //implementazine dell'interfaccia iCoordinate
    public function setX($x) {
        $this->getCoordinate()->setX($x);
    }

    public function setY($y) {
        $this->getCoordinate()->setY($y);
    }

    public function getX() {
        return $this->getCoordinate()->getX();
    }

    public function getY() {
        return $this->getCoordinate()->getY();
    }

    public function setXY($x, $y) {
        $this->getCoordinate()->setXY($x, $y);
    }

    public function setXYArray($XY) {
        $this->getCoordinate()->setXYArray($XY);
    }

    public function getXYArray() {
        return $this->getCoordinate()->getXYArray();
    }  
}
?>

Come si può vedere, un trait è definito tramite la parola chiave trait seguita dal nome. Al suo interno trovano posto variabili (proprietà) e metodi dell'oggetto o metodi statici (di classe). E' come una dichiarazione parziale di una classe. Se si desidera approfondire l'argomento sui trait ho scritto un apposito post. Si tenga presente che tale funzionalità è disponibile dalla versione 5.4 di PHP.

Altro tratto comune è negli oggetti iColorabili, in cui è presente una variabile $oColore impostata/letta tramite i metodi setColore e getColore. Anche in questo caso il tratto comune può essere racchiuso in un trait:

<?php
trait tColorabile{
    private $oColore;
 
    public function setColore(\iColore $oColore) {
        $this->oColore = $oColore;
    }

    public function getColore() {
        return $this->oColore;
    }              
}
?>


Definiti i trait indichiamo alle due classi di utilizzarli provvedendo a rimuovere quanto definito nei trait stessi ed utilizzando la clausola use seguita dall'elenco dei trait utilizzati dalla classe:

<?php

require_once './iPunto.php';
require_once './iColore.php';
require_once './iCoordinate.php';
require_once './iColorabile.php';
require_once './tCoordinate.php';
require_once './tColore.php';
require_once './tColorabile.php';

class cPunto implements iPunto, iColore, iCoordinate, iColorabile {

    use tCoordinate,
        tColore,
        tColorabile;

    private $oCoordinate;

    public function __construct(\iCoordinate $oCoordinate, \iColore $oColore) {
        $this->setPunto($oCoordinate, $oColore);
    }

    //Implementazione dell'interaccia iPunto
    public function setPunto(\iCoordinate $oCoordinate, \iColore $oColore) {
        $this->setColore($oColore);
        $this->setCoordinate($oCoordinate);
    }

    public function setCoordinate(\iCoordinate $oCoordinate) {
        $this->oCoordinate = $oCoordinate;
    }

    public function getCoordinate() {
        return $this->oCoordinate;
    }

}

?>

<?php

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

class cPoligono implements iFigura, Iterator, iColore, iColorabile {

    use tColore,
        tColorabile;

    private $vertici = [];

    public function __construct(\iColore $oColore) {
        $this->setColore($oColore);
    }

    public function setVertice(\iCoordinate $oCoordinate) {
        $this->vertici[] = $oCoordinate;
    }

    public function delVertici(){
        $this->vertici = [];
    }

    public function getVertice($numero) {
        if ($numero < count($this->vertici) + 1)
            return $this->vertici[$numero];
        else
            throw new Exception("Si cerca di accedere ad un vertice non disponibile");
    }

    //Interfaccia iFigura
    public function disegna(\iAreaDiDisegno $oAreaDiDisegno) {
        $ultimo = $primo = null;
        foreach ($this as $punto) {
            if (is_null($ultimo)) {
                $primo = $ultimo = $punto;
                continue;
            }
            $oAreaDiDisegno->tracciaSegmento($ultimo, $punto, $this->getColore());
            $ultimo = $punto;
        }
        $oAreaDiDisegno->tracciaSegmento($primo, $ultimo, $this->getColore());
    }

    //Interfaccia Iterator
    public function current() {
        return current($this->vertici);
    }

    public function key() {
        return key($this->vertici);
    }

    public function next() {
        return next($this->vertici);
    }

    public function rewind() {
        return reset($this->vertici);
    }

    public function valid() {
        return current($this->vertici) !== false;
    }

}

?>

Come si può notare il codice complessivo delle due classi è stato significativamente ridotto, e la collaborazione fra interfacce e trait riesce a porre in essere ciò che potremmo definire ereditarietà multipla.