Interfacce

20. Interfacce

I veterani di ActionScript 2 avranno già familiarità con le interfacce, un metodo molto utile per fornire strumenti standard a classi non necessariamente in relazione fra loro.

Le interfacce non definiscono esattamente un tipo di dato come le classi, infatti non è possibile creare un'istanza di un interfaccia, è possibile implementare un'interfaccia solo attraverso la dichiarazione di una classe.

Le interfacce vengono usate per dichiarare metodi che dovranno essere obbligatoriamente definiti da tutte le classi che implementeranno l'interfaccia in questione, non potranno quindi contenere attributi di nessun tipo nè definizioni di metodi, ma solo dichiarazioni di metodi.

La classe che poi implementerà l'interfaccia, dovrà riportare la medesima dichiarazione del metodo così come si presenta nell'interfaccia, e fornire obbligatoriamente una definizione del metodo (corpo del metodo).

Vediamo due interfacce di esempio ...

InterfacciaUno.as

package
{
	public interface InterfacciaUno
	{
		function MetodoInterfacciaUno(parametro:uint = 5):uint;
		function AltroMetodoInterfacciaUno(parametro:uint, altroparametro:Boolean = false):uint;
	}
}

InterfacciaDue.as

package
{
	public interface InterfacciaDue
	{
		function MetodoInterfacciaDue(parametro:String = null):uint;
		function AltroMetodoInterfacciaDue(parametro:uint = 10, altroparametro:Boolean = true):uint;
	}
}

Come avrete notato dal codice soprastante, non ho specificato alcun modificatore di accesso nella dichiarazione dei metodi d'interfaccia, poichè ActionScript 3 non lo consente.

A seguire la dichiarazione di una classe "Oggetto" che implementa entrambe le interfacce riportate sopra, attraverso la direttiva implements, che consente di implementare più interfacce separate da una virgola :

Oggetto.as

package
{
	import InterfacciaUno;
	import InterfacciaDue;

	public class Oggetto implements InterfacciaUno, InterfacciaDue
	{
		public function Oggetto():void
		{
			// Costruttore
		}

		public function MetodoInterfacciaUno(par:uint = 3):uint
		{
			return par + 2;
		}

		public function AltroMetodoInterfacciaUno(par:uint, altroparametro:Boolean = false):uint
		{
			if (altroparametro) return par + 2;
			else return par - 2;
		}

		public function MetodoInterfacciaDue(parametro:String = null):uint
		{
			return parametro.length + 1;
		}

		public function AltroMetodoInterfacciaDue(parametro:uint = 10, altroparametro:Boolean = true):uint
		{
			if (altroparametro) return parametro * 2;
			else return parametro / 2;
		}
	}
	
}

Il codice soprastante viene compilato senza errori e funziona correttamente.
ActionScript 3 infatti ci consente delle modifiche minime alla dichiarazione dei metodi :

  • Possiamo applicare un modificatore di accesso al metodo (public, private ...)
  • E' possibile cambiare i nomi dei parametri a patto che rimangano dello stesso tipo e nello stesso ordine
  • Possiamo modificare i valori di default dei parametri

Non sono ammesse altre modifiche alla dichiarazione dei metodi, ActionScript 3 è molto rigoroso nei controlli e richiederà pertanto che il metodo restituisca un valore del tipo specificato o che non venga restituito alcun valore (void).

Note :

  • Una classe può estendere solo un'altra classe (ereditarietà)
  • Una classe può implementare più di una interfaccia
  • Un'interfaccia può estendere più interfacce (ereditarietà multipla)
  • Un'interfaccia non può implementare un'altra interfaccia

37. Overload dell'interfaccia di Iterazione

Illustrerò in questo capitolo l'ultimo overload che vedrà come protagonista l'interfaccia nativa Iterator.

Implementando questa interfaccia nelle nostre classi, avremo modo di fornire gli strumenti a PHP per gestire tali oggetti nei cicli di iterazione.

L'overload di questa interfaccia abbinata ad ArrayAccess vi consentirà di costruire degli strumenti veloci e flessibili per interagire più rapidamente con le vostre classi.

Gli indici per effettuare le iterazioni sugli oggetti dovranno essere attributi interni della classe.

Vediamo come è costituita l'interfaccia Iterator :

interface Iterator

  • void rewind()
  • mixed current()
  • mixed key()
  • void next()
  • bool valid()
  • rewind() - Azzera l'indice del ciclo facendolo tornare all'inizio
  • current() - Ritorna il valore della posizione corrente
  • key() - Restituisce l'indice / chiave della posizione corrente
  • next() - Aumenta l'indice per puntare all'elemento successivo
  • valid() - Ritorna true o false per controllare se l'array è finito (viene interrogato prima di una chiamata a current() o key())

Esempio overload Iterator

Per creare un esempio più semplice possibile non ho volutamente utilizzato l'ereditarietà, ma ho creato due semplici classi base.

La prima classe è Unità e rappresenta un unità presente in un magazzino, con il nome, prezzo e codice.

La seconda e ultima classe (Magazzino) è un semplice contenitore di Unità, che memorizzerà utilizzando un array classico monodimensionale, dove l'elemento singolo non sarà un semplice valore ma l'oggetto più complesso Unità.

<?php

	class Unita
	{
		public $nome;
		public $prezzo;
		public $codice;

		public function Unita($n, $p, $c)
		{
			$this->nome = $n;
			$this->prezzo = $p;
			$this->codice = $c;
		}
	}

	class Magazzino implements Iterator
	{
		private $pezzi;
		private $current; // Indice interno per gestire l'iterazione

		public function Magazzino()
		{
			$this->pezzi = array();
		}

		/* Inizio Interfaccia Iterator */
		function rewind()
		{
			$this->current = 0;
		}

		function current()
		{
			return $this->pezzi[$this->current];
		}

		function key()
		{
			return $this->current;
		}

		function next()
		{
			return $this->current++;
		}

		function valid()
		{
			return isset($this->pezzi[$this->current]);
		}
		/* Fine Interfaccia Iterator */

		public function aggiungi(Unita $unita)
		{
			array_push($this->pezzi, $unita);
		}
	}

	$store = new Magazzino();

	$store->aggiungi(new Unita("Le avventure dello zio Pasquale", 15.5, "A096H"));
	$store->aggiungi(new Unita("I delitti di Grottaferrata", 12.0, "B076X"));
	$store->aggiungi(new Unita("Quando dico Basta!!", 23.5, "KJ87F2"));

	foreach ($store as $unita)
	{
		echo "Nome = $unita->nome<br />\n";
		echo "Prezzo = $unita->prezzo<br />\n";
		echo "Codice = $unita->codice<br /><br />\n\n";
	}

?>

Il codice produce il seguente risultato :

Nome = Le avventure dello zio Pasquale
Prezzo = 15.5
Codice = A096H

Nome = I delitti di Grottaferrata
Prezzo = 12
Codice = B076X

Nome = Quando dico Basta!!
Prezzo = 23.5
Codice = KJ87F2


23. Esempio pratico per l'uso di Interfacce


Introduzione

In questo capitolo scriverò delle classi e delle interfacce per la creazione e la gestione di alcuni account per un sito web.

Ovviamente il codice non sarà utilizzabile, in quanto solo parzialmente scritto. Lo scopo è quello di far capire meglio le potenzialità delle interfacce, mediante un esempio pratico facilmente intuibile e più vicino ad un'applicazione reale.


Definizione del concetto base

Questa pseudo-libreria, fornirà gli strumenti per creare e gestire 3 tipi di account :

  • Utente - Utente normale senza privilegi particolari
  • Premium - Utente premium con la possibilità di gestire una propria casella messaggi
  • Amministratore - Utente con privilegi amministrativi ed una casella messaggi personale

Per questo scopo scriveremo 1 classe astratta, 2 interfacce e 3 sottoclassi elencate di seguito :

  • Account - Classe Astratta
    Fornirà gli attributi di base per gli altri account e un metodo astratto per registrare gli account nell'archivio del sito (es. Database MySQL o file esterno)
  • Amministrazione - Interfaccia
    Fornirà dei metodi astratti di base per amministrare l'utenza, inserendo, modificando e cancellando utenti dall'archivio del sito
  • CasellaMessaggi - Interfaccia
    Fornirà dei metodi astratti per la gestione di una casella messaggi personale interna al sito
  • Utente - Sottoclasse derivata da Account
    Oggetto per la memorizzazione dei dati su un utente con metodo per la registrazione in archivio
  • Premium - Sottoclasse derivata da Account che implementa l'interfaccia CasellaMessaggi
    Oggetto per memorizzare un utente con privilegi premium e metodi per registrare l'utente in archivio e gestire una casella messaggi personale
  • Amministratore - Sottoclasse derivata da Account che implementa l'interfaccia Amministrazione e CasellaMessaggi
    Oggetto per la memorizzazione di un account di amministrazione, con metodi per la registrazione dell'account in archivio, la gestione di altri utenti (inserimento, modifica e cancellazione) e la gestione di una casella messaggi personale

Ecco un diagramma che mostra tutti gli oggetti e le loro relazioni.

Di seguito il codice testato con la corretta sintassi :

<?php

	// Classe Astratta di Base
	abstract class Account
	{
		protected $nome_utente;
		protected $pass_utente;
		protected $data_iscrizione = NULL;

		abstract protected function registraAccount();
	}

	// Inizio INTERFACCE
	interface Amministrazione
	{
		function aggiungiUtente(Utente $utente);
		function modificaUtente(Utente $utente);
		function cancellaUtente(Utente $utente);
	}

	interface CasellaMessaggi
	{
		const MAX_MESSAGGI = 100;

		function controllaCasella();
		function leggiMessaggio($id_messaggio);
		function cancellaMessaggio($id_messaggio);
	}
	// Fine INTERFACCE

	// Classi per la definizione dei vari tipi di Account

	class Utente extends Account
	{
		public function Utente($n, $p, $d)
		{
			$this->nome_utente = $n;
			$this->pass_utente = $p;
			$this->data_iscrizione = $d;
		}

		public function registraAccount()
		{ /* Codice per la registrazione dell'utente */ }
	}

	class Premium extends Account implements CasellaMessaggi
	{
		public function Premium($n, $p, $d)
		{
			$this->nome_utente = $n;
			$this->pass_utente = $p;
			$this->data_iscrizione = $d;
		}

		public function registraAccount()
		{ /* Codice per la registrazione dell'utente */ }

		public function controllaCasella()
		{ /* Codice per il controllo della Casella Messaggi */ }

		public function leggiMessaggio($id_messaggio)
		{ /* Codice per la lettura di un messaggio della casella */ }

		public function cancellaMessaggio($id_messaggio)
		{ /* Codice per la cancellazione di un messaggio dalla casella */ }
	}

	class Amministratore extends Account implements Amministrazione, CasellaMessaggi
	{
		public function Amministratore($n, $p)
		{
			$this->nome_utente = $n;
			$this->pass_utente = $p;
		}

		public function registraAccount()
		{ /* Codice per la registrazione dell'utente */ }

		public function aggiungiUtente(Utente $utente)
		{
			echo "Stai aggiungendo l'utente " . $utente->nome_utente . "<br />\n";
			// Codice per aggiungere un utente all'archivio ...
			echo "Utente " . $utente->nome_utente . " aggiunto con successo!<br />\n";
		}

		public function modificaUtente(Utente $utente)
		{
			echo "Stai modificando l'utente " . $utente->nome_utente . "<br />\n";
			// Codice per modificare un utente dell'archivio ...
			echo "Utente " . $utente->nome_utente . " modificato con successo!<br />\n";
		}

		public function cancellaUtente(Utente $utente)
		{
			echo "Stai cancellando l'utente " . $utente->nome_utente . "<br />\n";
			// Codice per cancellare un utente dall'archivio ...
			echo "Utente " . $utente->nome_utente . " cancellato con successo!<br />\n";
		}

		public function controllaCasella()
		{ /* Codice per il controllo della Casella Messaggi dell'Amministratore */ }

		public function leggiMessaggio($id_messaggio)
		{ /* Codice per la lettura di un messaggio della casella dell'Amministratore */ }

		public function cancellaMessaggio($id_messaggio)
		{ /* Codice per la cancellazione di un messaggio dalla casella dell'Amministratore */ }
	}

	// Inizio codice di esempio

	$utente = new Utente("mario", "miapassword", 1185456501);
	$admin = new Amministratore("francesco", "altrapass");

	$admin->aggiungiUtente($utente);

?>

Le ultime tre righe di codice, puramente a scopo illustrativo, genereranno il seguente output :

Stai aggiungendo l'utente mario
Utente mario aggiunto con successo!


Ricordatevi che l'ereditarietà multipla è concessa solo fra interfacce con extends, mentre fra classi è necessario ricorrere all'implementazione di interfacce con implements.

Nota : le interfacce non possono ereditare dalle classi.

22. Interfacce


Teoria sulle Interfacce

Come già accennato nel capitolo "Gli Oggetti e l'Ereditarietà", PHP non supporta l'ereditarietà multipla fra classi, ma solo fra interfacce o fra classi e interfacce, se quindi vi sarà necessario creare una sottoclasse che erediti le proprietà da più classi, allora dovrete creare delle Interfacce che gliele forniscano.

Lo scopo delle interfacce è quello di fornire un preciso set di metodi base per le classi, mediante la dichiarazione di metodi astratti, che andranno poi definiti nelle sottoclassi che li erediteranno implementando l'interfaccia.

Le interfacce possono avere solo metodi che saranno di default astratti e costanti.
Non sono pertanto ammessi :

  • Attributi di nessun tipo (Variabili membro)
    Fatal error: Interfaces may not include member variables
  • Definizioni di metodi, è consentita solo la dichiarazione poichè ripeto, i metodi delle interfacce devono essere astratti
    Fatal error: Interface function INTERFACCIA::FUNZIONE() cannot contain body
  • La parola chiave abstract non è necessaria, pertanto deve essere omessa
    Fatal error: Access type for interface method INTERFACCIA::FUNZIONE() must be omitted
  • Modificatori di accesso sui metodi (public, protected e private)
    Fatal error: Access type for interface method INTERFACCIA::FUNZIONE() must be omitted

Dichiarazione Interfaccia

Per dichiarare un'interfaccia si usa la parola chiave interface in questo modo :

<?php

	interface NomeInterfaccia
	{
		const COSTANTE = "valore";

		function metodoAstratto();
		function altroMetodo(Oggetto $obj);
	}

?>


Implementare ed estendere Interfacce

Per implementare un'interfaccia in una classe usate il comando implements, per estendere invece un'interfaccia in un'altra interfaccia dovete usare il comando extends come per le classi derivate :

<?php

	interface Interfaccia1
	{
		const COSTANTE = "valore";

		function metodo1();
		function metodo2();
	}
	
	interface Interfaccia2
	{
		function altroMetodo1();
		function altroMetodo2();
	}

	interface Interfaccia3 extends Interfaccia1, Interfaccia2
	{
		function metodoSpeciale1();
		function metodoSpeciale2();
	}

	class MiaClasse1 implements Interfaccia3
	{
		public function metodo1() { /* Corpo del Metodo */ }
		public function metodo2() { /* Corpo del Metodo */ }
		public function altroMetodo1() { /* Corpo del Metodo */ }
		public function altroMetodo2() { /* Corpo del Metodo */ }
		public function metodoSpeciale1() { /* Corpo del Metodo */ }
		public function metodoSpeciale2() { /* Corpo del Metodo */ }
	}

	class MiaClasse2 implements Interfaccia2
	{
		public function altroMetodo1() { /* Corpo del Metodo */ }
		public function altroMetodo2() { /* Corpo del Metodo */ }
	}

?>

Una classe o un'interfaccia può implementare / estendere più di un'interfaccia, solo se tali interfacce non contengono la dichiarazione degli stessi metodi o costanti. Per generare un conflitto è sufficiente anche solo una costante o un metodo in comune.


<?php

	interface Interfaccia1
	{
		const COSTANTE = "valore";

		function metodo1();
		function metodo2();
	}
	
	interface Interfaccia2
	{
		function metodo1(); // Genererà un errore nella dichiarazione di MiaClasse1
		function altroMetodo1();
		function altroMetodo2();
	}

	class MiaClasse1 implements Interfaccia1, Interfaccia2
	{
		public function metodo1() { /* Corpo del Metodo */ }
		public function metodo2() { /* Corpo del Metodo */ }
		public function altroMetodo1() { /* Corpo del Metodo */ }
		public function altroMetodo2() { /* Corpo del Metodo */ }
	}

?>

L'esempio soprastante genera il seguente errore :
Fatal error: Can't inherit abstract function Interfaccia2::metodo1() (previously declared abstract in Interfaccia1)

Nella prossima pagina un'esempio molto pratico per farvi familiarizzare maggiormente con questo potente strumento.

Condividi contenuti