giovedì 19 dicembre 2013

La OOP in PHP spiegata in un esempio - Parte 14 - La Clonazione (shallow clone)


Ora che abbiamo coscienza del side effect delle nostre classi possiamo pensare di risolvere il problema generando ogni volta un nuovo oggetto.

Meglio ancora, visto che abbiamo già scritto tutto il codice, sarebbe bello avere la possibilità di creare un nuovo oggetto partendo da uno esistente. Questa possibilità l'abbiamo grazie all'istruzione clone.

La sua sintassi è semplice: clone $oggettoDaClonare. Il risultato è un nuovo oggetto della stessa classe $oggettoDaClonare in cui, dopo la creazione, è copiato lo stato dell'oggetto clonato. Per copia dello stato s'intende la copia dei valori contenuti in variabili oggetto private, pubbliche o protette, nelle rispettive variabili oggetto private, pubbliche o protette del nuovo oggetto.

Potremo così scrivere:


<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Forme Geometriche</title>
    </head>
    <body>
        <?php          
            require_once './cColore.php';
            require_once './cAreaDiDisegnoHTML.php';
            require_once './cCoordinateTrasforamabile.php';
            require_once './cFabbricaPoligoni.php';
            require_once './cRettangolo.php';
            require_once './cPoligonoRettangolo.php';
                     
       
            $ad = new cAreaDiDisegnoHTML(50, 50);                    
                   
            $rettangoli = [];
            $origine = new cCoordinateTrasforamabile(28,28);          
            $colore = new cColore(255,0,0);
         
            $rettangoli[] = new cPoligonoRettangolo(clone $origine, 10, 10, $colore);          
       
            $colore->setRGB(0, 255, 0);
            $pol = new cPoligono($colore);
            $origine->trasla(-3, -3);
            $rettangoli[] = new cRettangolo(clone $origine, 10, 10, $pol, $pol);          
       
            $origine->trasla(-3, -3);
            $colore->setRGB(0, 0, 255);
            $rettangoli[] = cFabbricaPoligoni::rettangolo($origine, 10, 10, $colore);
         
            foreach ($rettangoli as $rettangolo)
                $rettangolo->disegna ($ad);
       
            echo $ad->output();
        ?>
    </body>
</html>

come si può vedre è stato aggiunto il comando clone davanti ai parametri attuali dei costruttori in modo da trasferire a questi un copia dell'oggetto e non l'oggetto originale, trasferito unicamente all'ultimo rettangolo. Il risultato è:

Output dello script

Quasi quello che volevamo. Che succede ancora? Tutti i rettangoli sono blu, perchè il colore, gestito tramite il trait tColorabile, prevede l'assegnazione di un oggetto iColore direttamente alla variabile oggetto privata $oColore.

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

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

Siamo alle solite. Possiamo modificare il trait come segue:

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

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

in questo modo l'oggetto passato come parametro formale è clonato prima di essere assegnato disgiungendolo dal parametro attuale.

In generale occorre valutare bene l'opportunità di lasciare al client il compito di clonare l'oggetto o clonarlo in fase di ricezione nella classe prima di memorizzarlo in qualche variabile oggetto privata o meno.

E' mio parere che il client (chi utilizza un oggetto) dia per scontato il poter continuare ad utilizzare un oggetto A senza che si verifichino interferenze in altri oggetti, qualora l'oggetto A sia utilizzato come argomento per dei metodi di altri oggetti, proprio come accade per i tipi primitivi interi, stringa array ecc. Analogamente un client è cosciente che accedendo ad un oggetto X memorizzato in un oggetto Y tramite un metodo d'accesso Y->getX(), ed invocando eventuali metodi relativi a ciò che ha ottenuto dalla get, modifica  lo stato interno dell'oggetto X usato dall'oggetto Y di cui è parte dello stato.

Questo significa che occorre rivisitare il codice scritto fino ad ora e modificare quei metodi set che assegnano un oggetto ad una variabile oggetto, facendo uso della funzione clone come fatto poco sopra in tColorabile. In particolare:

    //In cPoligonoRettangolo
    public function setOrigine(\iCoordinate $oOrigine){
        $this->oOrigine=clone $oOrigine;      
    }

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

    //In cRettangolo
    public function setPoligono(\iPoligono $oPoligono) {
        $this->oPoligono = clone $oPoligono;
    }
    public function setOrigine(\iCoordinate $oOrigine) {
        $this->oOrigine = clone $oOrigine;
    }

    //In tColorabile
    public function setColore(\iColore $oColore) {
        $this->oColore = clone $oColore;
    }

Ciò fatto potremo rieseguire lo script così come correttamente pensato in origine.

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Forme Geometriche</title>
    </head>
    <body>
        <?php          
            require_once './cColore.php';
            require_once './cAreaDiDisegnoHTML.php';
            require_once './cCoordinateTrasforamabile.php';
            require_once './cFabbricaPoligoni.php';
            require_once './cRettangolo.php';
            require_once './cPoligonoRettangolo.php';
                     
       
            $ad = new cAreaDiDisegnoHTML(50, 50);                    
                   
            $rettangoli = [];
            $origine = new cCoordinateTrasforamabile(28,28);          
            $colore = new cColore(255,0,0);
         
            $rettangoli[] = new cPoligonoRettangolo($origine, 10, 10, $colore);          
       
            $colore->setRGB(0, 255, 0);
            $pol = new cPoligono($colore);
            $origine->trasla(-3, -3);
            $rettangoli[] = new cRettangolo($origine, 10, 10, $pol, $pol);          
       
            $origine->trasla(-3, -3);
            $colore->setRGB(0, 0, 255);
            $rettangoli[] = cFabbricaPoligoni::rettangolo($origine, 10, 10, $colore);
         
            foreach ($rettangoli as $rettangolo)
                $rettangolo->disegna ($ad);
       
            echo $ad->output();
        ?>
    </body>
</html>

Ottenendo finalmente il risultato atteso:
Output dello script precedente