mercoledì 18 dicembre 2013

La OOP in PHP spiegata in un esempio - Parte 13 - Side effect


Negli ultimi posta abbiamo un po' divagato scostandoci da quelli che sono gli argomenti che fondano il nucleo della OOP. E' arrivato il momento di prendere in esame un fattore fino ad ora ignorato. In molte classi, chiediamo degli oggetti come argomenti e memorizziamo tali oggetti  all'interno della classe. Tale pratica però può essere controproducente ossia dar luogo a dei side effect, perchè il client  (colui che utilizza un oggetto) potrebbe utilizzare lo stesso oggetto come argomento per altre classi o invocarne i metodi modificandone lo stato. In tale circostanza anche l'oggetto memorizzato dalla classe cambia.

Procediamo con ordine ed iniziamo a vedere come PHP tratta variabili e parametri di funzioni (metodi). Gli oggetti primitivi quali interi, booleani, stringhe e per fino array, sono nomi simbolici di una locazione di memoria in cui è custodito il valore assegnato alla variabile. 

Se definisco $intero, avrò accesso alla locazione che contiene il valore tramite il nome $intero sia in lettura ($pippo=$intero;) che in scrittura tramite l'istruzione di assegnazione ($intero=10;). Nelle funzioni PHP, in cui gli argomenti sono sempre passati per valore (tramite copia) e non per riferimento (è richiesto l'esplicito utilizzo del modificato &), il valore del parametro attuale è assegnato al (copiato nel) parametro formale. Di fatto il parametro formale (quello che utilizziamo dentro la funzione) memorizza in una nuova locazione di memoria ciò che è memorizzato nel parametro attuale (quello passato come argomento alla funzione).

function test($parametroFormale){...}

$parametroAttuale=10;
test($parametroAttuale);

Nell'esempio $parametroFormale e $parametroAttuale sono nomi simbolici per distinte locazioni di memoria. Al momento della chiamata alla funzione test, queste due distinte locazioni di memoria sono popolate con il medesimo valore numerico 10, ossia il valore 10 è letto da $parametroAttuale ed assegnato a $parametroFormale.

Nel caso degli oggetti il nome della variabile è sempre un nome simbolico ad una locazione di memoria. Tale locazione però non contiene l'oggetto ma un riferimento che indirizza all'oggetto. Un po' come avviene per le risorse. Una risorsa in PHP è di norma un valore, tramite cui  accedere alla risorsa vera e propria posta all'esterno di PHP. Nel caso degli oggetti invece la risorsa a cui si accede è dentro l'area dati dello stesso script PHP in esecuzione.

$oggetto1 = new Calsse();
$oggetto2 = $oggetto1;

Le due istruzioni producono il seguente effetto:
  1. E' allocata la memoria per il nuovo oggetto e tale indirizzo di memoria è custodito nella locazione di memoria accessibile tramite nome simbolico $oggetto1.
  2. Nella locazione di memoria individuata da $oggetto2, è copiato il valore contenuto nella locazione di memoria accessibile tramite il nome simbolico $oggetto1. Detta locazione contiene l'indirizzo a partire dal quale è memorizzato l'oggetto.
Come è giusto che sia, il contenuto di una variabile è stato copiato nell'altra. Ciò non di meno, tale contenuto non è altro che l'indirizzo/riferimetno dell'oggetto e non l'oggetto. Ne consegue che i metodi invocati tramite le due variabili $oggetto1 e $oggetto2  per mezzo del costrutto -> agiscono sul medesimo oggetto. Possiamo quindi definire il costrutto -> non come mezzo di accesso all'interfaccia della classe (è una conseguenza), ma più in generale come costrutto atto a risolvere il riferimento contenuto nelle variabili a cui è applicato.
Ciò che è contenuto in una variabile di tipo oggetto è un indirizzo
alla locazione di memoria che custodisce al suo interno l'oggetto.
Quando si utilizza il costrutto $oggetto1-> lo stesso risolve il riferimento LocazioneXYZ

Questo spiega anche un'altra cosa, ossia il comportamento di PHP quando si invoca una funzione o metodo passandole come argomento un variabile contenente un oggetto. Anche in tale circostanza è il contenuto ad essere copiato dal parametro attuale a quello formale, con il risultato che è stato copiato il valore LocazioneXYZ ossia un indirizzo di memoria. Ma essendo l'indirizzo di memoria sempre lo stesso, di fatto le due variabili accedono tramite il costrutto -> allo stesso oggetto. In più $oggetto1 è per confronto (== o ===) uguale a $oggetto2 non perchè gli oggetti a cui puntano sono uguali, ma perchè il valore "indirizzo di memoria" da cui reperire l'oggetto è identico.

Proviamo a scrivere un programma di test che metta in luce il side effect cercando di disegnare tre rettangoli traslati di pochi punti l'uno rispetto all'altro:

<!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);       
            $origine->trasla(-3, -3);
            $rettangoli[] = new cRettangolo($origine, 10, 10, new cPoligono($colore),  $colore);            
          
            $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>

L'output prodotto dallo script sarà
L'output prodotto dallo script precedente
Cosa è successo? Dove sono i tre rettangoli? Vediamo cosa fa lo script. Appare evidente che questo script (client di varie classi) genera un punto, quindi 3 rettangoli. Ad ogni rettangolo indica come punto d'origine quello generato inizialmente, traslandolo di volta in volta di 3 punti in alto a sinistra. Quello che il client vorrebbe ottenere è tre rettangoli di colori diversi, ognuno dei quali traslato di tre punti in alto a sinistra rispetto al precedente. Ciò che ottiene è invece solo il rettangolo blu (l'ultimo).

Quello che è successo è che:
  1. Il primo rettangolo, generato tramite il metodo costruttore cPoligonoRettangolo, imposta il punto d'origine tramite il metodo setOrigine($origine), che a sua volta assegna il parametro formale alla variabile oggetto privata $oOrigine
  2. Il secondo rettangolo, generato tramite il metodo costruttore cRettangolo ha il medesimo funzionamento del precedente
  3. Il terzo rettangolo, generato dal metodo statico, fa uso della classe cPoligono e provvede a passare come primo vertice direttamente il parametro attuale
Tutto ciò si traduce nel fatto che $origine dello script, $oOrigine variabile oggetto privata della classe cPoligonoRettangolo, $oOrigine variabile oggetto privata della classe cRettangolo ed il primo vertice appartenente all'array $vertici variabile oggetto privato della classe cPoligono hanno al loro interno un riferimento alla locazione di memoria contenente l'unico oggetto cCoordinateTrasformabile generato dallo script. Ne consegue che ogni traslazione sta di fatto variando il punto d'origine dentro ogni oggetto che implementa un rettangolo.

Nel momento in cui il client cerca di produrre tutti e tre i rettangoli nell'area di disegno, i tre hanno lo stesso punto d'origine e sono quindi sovrapposti uno dopo l'altro. L'ultimo rettangolo, che sovrasta tutti gli altri, è l'unico visibile. Nel prossimo post si vedrà come ovviare a tale problema.