venerdì 29 novembre 2013

La OOP in PHP spiegata in un esempio - Parte 4 - Classi Astratte, Metodi Statici e Costrutto self::


A questo punto occorre definire la classe per l'area di disegno. Nel post precedente abbiamo accennato l'interfaccia per l'area di disegno con i metodi per tracciare i punti e i segmenti. Ma questi non completano l'interfaccia, infatti in fase di descrizione del problema è scritto "avremo bisogno di definire un'area di disegno all'interno della quale inserire le nostre 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". Occorre procedere all'aggiunta dei metodi di accesso alle informazioni di dimensione dell'area di disegno (altezza e larghezza), e un metodo per la produzione dell'output finale.


<?php
interface iAreaDiDisegno {  
    public function tracciaPunto(\iCoordinate $oCoordinate,\iColore $oColore);
    public function tracciaSegmento(\iCoordinate $oCoordinateA, \iCoordinate $oCoordinateB, \iColore $oColore);
    public function setAltezza($h);
    public function setLarghezza($l);
    public function getAltezza();
    public function getLarghezza();
    public function setDimensioni($h,$l);
    public function getDimensioni(&$h,&$l);
    public function output();
}
?>

Possiamo procede alla realizzazione della classe aAreaDiDisegno che implementi l'interfaccia iAreaDiDisegno. Così come in precedenza ho fatto iniziare per c i nomi delle classi e per i quelle delle interfacce, anche la a ha un suo significato. Infatti aAreaDiDisegno è una classe astratta (modificatore abstract) . Una classe astratta è una classe che deve essere estesa poichè non se ne può ottenere una istanza diretta. Al suo interno possono essere definite variabili membro e metodi. Uno o più metodi possono presentare il modificatore abstract, il che significa che la classe concreta, che estende la classe astratta, deve definire i metodi lasciati astratti dalla genitrice. Nella fattispecie, la maggior parte dei metodi sono definiti, mentre è lasciato astratto il metodo output(). Ciò perchè esistono molti modi per definire l'output, tramite le librerie GD o tramite HTML (con il tag pre ed un punto per ogni carattere o con delle tabelle in cui ogni cella rappresenta il punto e lo sfondo utilizzato per il colore). Riprendendo le interfacce, queste possono essere pensate come classi in cui tutti i metodi sono astratti. A vantaggio delle interfacce resta la possibilità di essere implementate in qualunque numero dalle classi, mentre una classe può estendere una e una sola classe. A vantaggio delle classi astratte è la possibilità di una implementazione, sia pure parziale, della classe delegando alla figlia la definizione di quanto lasciato astratto.

<?php

require_once './iAreaDiDisegno.php';

abstract class aAreaDiDisegno implements iAreaDiDisegno {

    private $h;
    private $l;
    protected $puntiDisegnati = [];

    public function __construct($h, $l) {}
    public function tracciaPunto($oCoordinate, $oColore) {}
    public function tracciaSegmento($oCoordinateA, $oCoordinateB, $oColore) {}
    public static function coefficenteAngolare($x1, $y1, $x2, $y2) {}
    public static function intercetta($x1, $y1, $x2, $y2) {}
    public static function scambia(&$a, &$b) {}
    public function setAltezza($h) {}
    public function setLarghezza($l) {}
    public function getAltezza() {}
    public function getLarghezza() {}
    public function setDimensioni($h, $l) {}
    public function getDimensioni(&$h, &$l) {}
    abstract public function output() {}

}

?>

Ai metodi previsti dall'interfaccia, è stato aggiunto un costruttore in modo da impostare subito le dimensioni dell'area di disegno in fase di creazione dell'oggetto. Tali dimensioni possono anche essere modificate in fase d'esecuzione dello script tramite gli appositi metodi. Sono presenti anche delle funzioni con modificatore static. Una funzione static è una funzione che esiste a prescindere dall'esistenza dell'oggetto e può essere invocata direttamente tramite il nome della classe (ad esempio cAreaDiDisegnoHTML::scambia($x,$y)). Il fatto che possa essere invocata a prescindere dall'esistenza di un oggetto implica che in nessun caso essa possa fare riferimento all'oggetto e suoi metodi e variabili tramite il costrutto $this->. Può invece invocare ed accedere a variabili e metodi di classe (altri elementi static) tramite il costrutto self::.

Si osservi che la variabile oggetto $puntiDisegnati ha modificatore protected. Ciò è stato fatto perchè i metodi d'accesso a tale variabile sono tracciaPunto() (il set) e output() (il get), laddove output ritorna la rappresentazione, come definita dalla classe figlia, dei punti disegnati. Essendo poi output() un metodo astratto, al fine di consentire  alla classe figlia libero accesso alla variabile, la stessa è stata definita protected.

Completando la classe con il codice necessario otteniamo:

<?php

require_once './iAreaDiDisegno.php';

abstract class aAreaDiDisegno implements iAreaDiDisegno {

    private $h;
    private $l;
    protected $puntiDisegnati = [];

    public function __construct($h, $l) {
        $this->setDimensioni($h, $l);
    }

    public function tracciaPunto(\iCoordinate $oCoordinate,\iColore $oColore) {       
        $this->puntiDisegnati[$oCoordinate->getX()][$oCoordinate->getY()] = $oColore;       
    }

    public function tracciaSegmento(\iCoordinate $oCoordinateA, \iCoordinate $oCoordinateB, \iColore $oColore) {
        $x1 = $oCoordinateA->getX();
        $y1 = $oCoordinateA->getY();
        $x2 = $oCoordinateB->getX();
        $y2 = $oCoordinateB->getY();
        if ($x1 == $x2) { //segmento parallelo all'asse x
            if ($y1 > $y2)
                self::scambia($y1, $y2);
            for ($y = $y1; $y <= $y2; $y++)
                $this->tracciaPunto(new cCoordinate($x1, $y), $oColore);
        } elseif ($y1 == $y2) { //segmento parallelo all'asse y
            if ($x1 > $x2)
                self::scambia($x1, $x2);
            for ($x = $x1; $x <= $x2; $x++)
                $this->tracciaPunto(new cCoordinate($x, $y1), $oColore);
        } else { //segmento diagonale
            $m = self::coefficenteAngolare($x1, $y1, $x2, $y2);
            $q = self::intercetta($x1, $y1, $x2, $y2);
            if ($x1 > $x2)
                self::scambia($x1, $x2);
            for ($x = $x1; $x <= $x2; $x++) {
                $y = round($m * $x + $q);
                $this->tracciaPunto(new cCoordinate($x, $y), $oColore);
            }
        }
    }

    public static function coefficenteAngolare($x1, $y1, $x2, $y2) {
        return ($y2 - $y1) / ($x2 - $x1);
    }

    public static function intercetta($x1, $y1, $x2, $y2) {
        return $y1 - (($y1 - $y2) / ($x1 - $x2)) * $x1;
    }

    public static function scambia(&$a, &$b) {
        $t = $a;
        $a = $b;
        $b = $t;
    }

    public function setAltezza($h) {
        $h = (int) $h;
        if ($h > 0)
            $this->h = $h;
        else
            throw new Exception("L'altezza deve essere un valore intero magigore di 0");
    }

    public function setLarghezza($l) {
        $l = (int) $l;
        if ($l > 0)
            $this->l = $l;
        else
            throw new Exception("La larghezza deve essere un intero maggiore di 0");
    }

    public function getAltezza() {
        return $this->h;
    }

    public function getLarghezza() {
        return $this->l;
    }

    public function setDimensioni($h, $l) {
        $this->setAltezza($h);
        $this->setLarghezza($l);
    }

    public function getDimensioni(&$h, &$l) {
        $h = $this->getAltezza();
        $l = $this->getLarghezza();
    }

    abstract public function output();

}

?>

Si noti come il metodo output() sia un metodo astratto puro, assolutamente privo di codice, oltre all'utilizzo del costrutto self:: nel metodo tracciaSegmento() per accedere ai metodi statici della classe stessa (dall'esterno della classe occorre utilizzare nomeDellaClasse::metodoStatico()) .