mercoledì 27 novembre 2013

La OOP in PHP spiegata in un esempio - Parte 2 - Le Interfacce

La classe cCoordinate

Ora che è stata realizzata la classe cColore procederemo con la classe cCoordinate. Questa classe è molto simile alla precedente e permette di memorizzare le coordinate sotto forma di coppia di valori x,y.


<?php

class cCoordinate {

    private $x;
    private $y;

    public function __construct($x, $y) {
        $this->setXYArray([$x, $y]);
    }

    public function setX($x) {
        $this->x = $x;
    }

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

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

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

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

    public function setXYArray($XY) {
        if (is_array($XY) and count($XY) == 2)
            $this->setXY($XY[0], $XY[1]);
        else
            throw new Exception("La funzione setXYArray si aspetta di ricevere come argomento un array contenente le due coordinate");
    }
    
    public function getXYArray(){
        return [$this->getX(),  $this->getY()];
    }

}


La classe cPunto

A questo punto possiamo definire un oggetto punto dello schermo quale elemento caratterizzato da delle coordinate e da un colore. Una possibile definizione della classe è la seguente:

<?php
class cPunto{
    private $oCoordinate;
    private $oColore;
 
    public function __construct(\iCoordinate $oCoordinate,\iColore $oColore) {}   

    public function setPunto(\iCoordinate $oCoordinate,\iColore $oColore) {}
    public function setCoordinate(\iCoordinate $oCoordinate){}  
    public function getCoordinate(){}
    public function setColore(\iColore $oColore){}
    public function getColore(){}
 
}
?>

I nomi delle variabili membro $oCoordinate e $oColore sono precedute da una o, per ricordare a noi stessi che si tratta di oggetti, e non di variabili contenenti tipi primitivi. Definendo il corpo dei metodi otteniamo:

<?php

class cPunto{

    private $oCoordinate;
    private $oColore;

    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;
    }

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

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

?>

come possiamo osservare, al suo interno la classe cPunto fa uso di oggetti di classe cColore e cCoordinate. Ciò apre la strada ad una possibile ereditarietà per delega. A tal fine possiamo formalizzare le interfacce pubbliche di cColore e cCoordinate in due "interfacce", nel senso stretto del linguaggio di programmazione. Quindi fare in modo che cColore implementi l'interfaccia iColore (definisca al suo interno tutti i metodi dichiarati nell'interfaccia), cCoordinate implementi l'interfaccia iCoordinate. Infine cPunto implementerà entrambe le interfacce. In questo modo potremo utilizzare un oggetto di classe cPunto anche come un oggetto cColore o cCoordinate. Definiamo quindi le tre interfacce in tre file separati ottenendo:

<?php
interface iColore {  
    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();    
}
?>

<?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();
}
?>


<?php
interface iPunto {    
    public function setPunto(\iCoordinate $oCoordinate,\iColore $oColore);
    public function setCoordinate(\iCoordinate $oCoordinate);
    public function getCoordinate();
    public function setColore(\iColore $oColore);
    public function getColore();
}
?>

Come si può osservare le interfacce, dichiarate per mezzo della parola chiave interface, non sono altro che dei contenitori di intestazioni o meglio firme dei metodi pubblici. Le classi che intendono implementare una interfaccia devono definire tutti i metodi previsti dall'interfaccia stessa. Chiunque utilizzi un oggetto, istanza di una classe che implementa una specifica interfaccia, sa con certezza quali metodi pubblici sono interfaccia della classe in uso. Quindi modifichiamo i file cColore e cCoordinate perchè le relative classi siano implementazioni delle relative interfacce ottenendo:

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

class cColore implements iColore{
...


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

class cCoordinate implements iCoordinate{
...

I puntini indicano che ciò che segue resta invariato. La parola chiave implements è seguita dall'elenco delle interfacce che la classe implementa, separate da virgola. A questo punto modifichiamo la classe cPunto affinché sia implementazione non solo della interfaccia iPunto, ma anche di iColore e iCoordinate:

<?php

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

class cPunto implements iPunto, iColore, iCoordinate {

    private $oCoordinate;
    private $oColore;

    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;
    }

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

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

    //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();
    }

    //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();
    }

}

?>

Si osservino, ad esempio, i metodi setColore(\iColore $oColore) e setCoordinate(\iCoordinate $oCoordinate). PHP non è mai stato un linguaggio fortemente tipizzato. Con PHP 5 però è possibile specificare per metodi e funzioni, oltre agli argomenti, anche il tipo ad eccezione di quasi tutti i tipi primitivi. Quindi è possibile indicare prima del nome del parametro una classe ,una interfaccia, array o callable. In tal modo è garantito che l'oggetto passato come argomento sia sottoclasse, o meglio implementi l'interfaccia richiesta piuttosto che essere oggetto di una specifica classe. Ciò permette di passare a queste funzioni anche un oggetto cPunto (che implementa le interfacce necessarie) facendo in modo che un nuovo punto abbia lo stesso colore di un punto creato in precedenza e passato come argomento a setColore(). Gli oggetti sono così più flessibili in ragione di una minore coesione, ossia si è spostato il legame Classe-Classe (PHP non ha l'ereditarietà multipla sebbene una pezza a sia stata posta con i Traits) ad un legame Classe-Interfaccia laddove una interfaccia può essere implementata da molteplici classi o per ereditarietà (la vedremo più avanti) o per delega come in questo caso.