lunedì 25 novembre 2013

La OOP in PHP spiegata in un esempio - Parte 1 - Classi e Modificatori d'Accesso (private, protected e public)


Chi inizia a studiare la programmazione a oggetti si sente dire: la programmazione a oggetti consente di modellare elementi del mondo reale come oggetti software. Seguono immediatamente esempi di biciclette, cani, gatti e canarini.

Chi vuole imparare, non desidera favoleggiare su improbabili utilizzi di un paradigma ma piuttosto vedere calato in esempi, quanto più reali possibile, il paradigma oggetto di studio. Se poi all'esempio si affianca una spiegazione sulla programmazione a oggetti, forse otteniamo due risultati in un colpo solo. Per prima cosa è quindi bene effettuare un ripasso sui paradigmi di programmazione. E' un testo breve ma fornisce l'idea di cosa si vuole ottenere con l'introduzione di un nuovo paradigma di programmazione. Può quindi fornire informazioni base su quale strada percorrere, per non incappare nella tentazione di utilizzare un nuovo paradigma come se fosse quello da cui deriva.

I paradigmi di programmazione infatti non sono indipendenti gli uni dagli altri, ma sono più che altro una naturale evoluzione o affinamento della programmazione. Riprendendo la definizione inserita nel post indicato prima, in un linguaggio a oggetti:

Si determinino quali classi si desiderano; si fornisca un insieme completo delle operazioni di ogni classe; si renda esplicito ciò che hanno in comune mediante l'ereditarietà.

Questa è la definizione che il papà di C++, Bjarne Stroustrup, dava di un linguaggio a oggetti nel suo celeberrimo libro.

Prima di procedere si approfondiscano i termini base della programmazione a oggetti quali:
Si tratta di post brevi che introducono concetti essenziali.

Partiamo quindi dalla soluzione di un problema che ci possa guidare nello sviluppo di una applicazione PHP che faccia uso della OOP.

Traccia

Sviluppare un sistema di disegno testuale di forme geometriche piane per browser in PHP.

Iniziamo ad analizzare il nostro problema in modo da individuare le potenziali classi/oggetti che ne fanno parte:
  • avremo bisogno di definire un'area di disegno all'interno della quale inserire le nostre figure (è un contenitore di figure) capace di operazioni base quali definire le proprie dimensioni, rappresentare un punto ed al più un segmento. Quindi produrre l'output html per la pagina web
  • le figure geometriche sono oggetti caratterizzate da vertici (una collezione ordinata di punti) e un colore. Uniti i vertici tramite segmenti si produce la rappresentazione grafica 
  • il punto è un oggetto caratterizzato da delle coordinate ed un colore
  • le coordinate sono un oggetto caratterizzato da due valori x e y
  • Un colore RGB è un oggetto caratterizzato da un valore compreso tra 0 e 255 per ognuna delle tre componenti fondamentali
  • il triangolo, il quadrato, pentagono sono figure geometriche (estendono questa famiglia) specializzate
Fermiamoci qui per non complicare troppo le cose.

Iniziamo da quelli che sembrano essere gli oggetti più semplici e cioè colore e coordinate. Ricordiamo sempre di rispettare l'incapsulamento dei dati, quindi non esponiamo mai i dati (lo stato dell'oggetto), se possibile anche alle classi ereditate, ma forniamo piuttosto un set completo di metodi (funzioni) tramite cui interagire con l'oggetto per modificarne lo stato.

La classe cColore

Ogni nome di classe che definisco è preceduta dalla lettera c, ad indicare appunto che si tratta di una classe.  Per le interfacce utilizzerò invece la i. In più ogni interfaccia o classe risiederà in un file avente lo stesso nome della classe o interfaccia. Abbozziamo ciò che sarà lo stampo dei nostri oggetti colore:

<?php
class cColore {
    private $r;
    private $g;
    private $b;
 
    public function __construct($r,$g,$b){}
    public function setRGB($r,$g,$b){}
    public function setRGBArray($RGB){}
    public function getRGBArray(){}
    public function setR($r){}
    public function setG($g){}
    public function setB($b){}
    public function getR(){}
    public function getG(){}
    public function getB(){}      
}
?>

La parola chiave class ci permette di definire una nuova classe il cui nome è cColore. Lo stato dell'oggetto istanza di cColore, ossia i suoi attributi o proprietà o variabili dell'oggetto, chiamate $r, $b e $g, sono accessibili solo alle funzioni dell'oggetto stesso. Ciò perchè precedute dal modificatore d'accesso private. Altri modificatori sono public (accessibile a chiunque) e protected (accessibile sono alle classi che estendono o ereditano dalla classe cColore). D'altra parte il principio d'incapsulamento impone, salvo casi realmente eccezionali, di nascondere il dato fornendo un'interfaccia completa di accesso. E così abbiamo le varie funzioni di accesso in lettura (get) e scrittura (set) per modificare lo stato dell'oggetto.

Completando la classe con il codice sorgente dei metodi che compongono l'interfaccia otteniamo:


<?php

class cColore {

    private $r;
    private $g;
    private $b;

    private function controlloValore($valore) {
        if (!(is_int($valore) and $valore >= 0 and $valore <= 255))
            throw new Exception("I valori RGB devono essere numeri naturali compresi tra 0 e 255");
    }

    public function __construct($r, $g, $b) {
        $this->setRGB($r, $g, $b);
    }

    public function setRGB($r, $g, $b) {
        $this->setR($r);
        $this->setG($g);
        $this->setB($b);
    }

    public function setRGBArray($RGB) {
        if (is_array($RGB) and count($RGB) == 3) 
            $this->setRGB($RGB[0],$RGB[1],$RGB[2]);           
        else
            throw new Exception("La funzione setRGBArray richiede che il suo parametro sia un array con tre valori interi");
    }

    public function getRGBArray() {
        return [$this->getR(), $this->getG(), $this->getB()];
    }

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

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

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

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

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

    public function getB() {
        return $this->b;
    }
    
    public function __toString() {
        return "$this->r,$this->g,$this->b";
    }

}

?>

Il costrutto $this-> permette di accedere a membri, metodi e variabili, dell'oggetto stesso. Si osservi come gli unici metodi che accedono alle variabili membro sono setR($r), setG($g), e setB($b) per la scrittura e getR(), getG() e getB() per la lettura. Tutti gli altri metodi si affidano all'interfaccia stessa dell'oggetto. Ai metodi è stato aggiunto un metodo con modificatore d'accesso private. Ciò indica che il metodo è utilizzabile solo dai metodi dell'oggetto stesso. Tale metodo, chiamato controlloValore(), ha il compito di verificare che il suo parametro sia un intero compreso tra 0 e 255, e sollevare una eccezione nel caso in cui non sia rispettato tale vincolo. 

Le eccezioni sono il sistema con cui gestire eventuali errori. Come si può vedere dal codice precedente una eccezione può essere sollevata tramite il comando throw seguito da un oggetto, a cui il client che invoca il metodo che genera l'eccezione può accedere per reperire informazioni e gestire l'eccezione stessa (lo vedremo più avanti). La gestione avviene tramite il costrutto try-catch. Una eccezione non gestita, quindi in assenza di un blocco try-catch che la intercetti, causa l'interruzione del programma.

Infine si noti la funzione __toString(). E' una funzione speciale per PHP che se definita, è invocata ogni qual volta l'oggetto è utilizzato in contesti in cui occorre una stringa. Ad esempio in operazioni di concatenazione con l'operatore . o nelle chiamate echo o print. Allo stesso modo __construct() è una funzione speciale per PHP invocata ogni qualvolta si genera un nuovo oggetto o istanza della classe per mezzo del comando new.