Ereditarietà

18. Ereditarietà

L'ereditarietà è uno dei concetti base su cui poggia uno dei paradigmi di programmazione più usati dalle applicazioni moderne, la programmazione orientata agli oggetti, che abbiamo già avuto modo di conoscere nel precedente capitolo sulle classi : Classi

Questo metodo di programmazione ci consente di sfruttare appieno le potenzialità di questo paradigma per i seguenti motivi :

ActionScript 3 ci consente di creare una sottoclasse derivata da un'altra mediante la direttiva extends, come mostrato nell'esempio seguente.

Unita.as - Classe Base

package
{
	public class Unita
	{
		protected var attacco:uint;
		protected var difesa:uint;
		protected var distanza:uint;
		protected var velocita:uint;

		public function Unita():void
		{ /* ... Costruttore ... */ }
	}
}

Fante.as - Classe Derivata

package
{
	import Unita;

	public class Fante extends Unita // "Fante" eredita da "Unita"

	{
		protected var puntiferita:uint;

		public function Fante():void
		{
			this.attacco = 10;
			this.difesa = 20;
			this.distanza = 1;
			this.velocita = 5;

			this.puntiferita = 100;
		}
	}
}

Come possiamo notare dall'esempio soprastante, la classe "Fante" eredita i quattro attributi protected dichiarati nella classe "Unita".
Nota : protected è un modificatore di accesso che restringe la visibilità dei membri alla sola classe base ed alle sue derivate.

Nell'esempio soprastante, abbiamo esteso il codice senza effettuare alcuna modifica alla classe esistente "Unita", ma semplicemente creando una classe specializzata per rappresentare il nuovo tipo di dato "Fante" con un attributo aggiuntivo.

Un altro strumento che l'ereditarietà ci mette a disposizione è la possibilità di ridefinire alcuni elementi delle classi base.

Molti linguaggi ci consentono di ridefinire sia gli attributi che i metodi di una classe, mentre ActionScript 3 ci permette di ridefinire solo i metodi, lasciando tutti gli attributi e le costanti delle classe base (var e const) come proprie di quest'ultima.

E' comunque possibile ridefinire gli attributi attraverso la definizione dei metodi speciali get e set che abbiamo visto nel capitolo precedente.


Ridefinizione dei Metodi

Per ridefinire un metodo in ActionScript 3, si usa la direttiva override seguita dalla dichiarazione del metodo, che dovrà rispecchiare esattamente la struttura del metodo base o il compilatore restituirà il messaggio "Override incompatibile".

Questo significa che se il metodo base prende un determinato numero di parametri di un tipo stabilito, il metodo che andrà a ridefinirlo dovrà prendere gli stessi parametri dello stesso tipo.

Un metodo sarà ridefinibile solo se dichiarato pubblico, protetto o interno.
Se dichiarato privato sarà comunque possibile definire un metodo con lo stesso nome nella classe derivata, in quanto il metodo della classe base non sarà visibile all'esterno (private), e procederemo quindi senza la direttiva override.

Di seguito un semplice esempio dove ridefiniremo il metodo "muovi()" :

Unita.as - Classe Base

package
{
	public class Unita
	{
		protected var attacco:int;
		protected var difesa:int;
		protected var distanza:int;
		protected var velocita:int;

		public function Unita():void
		{ /* ... Costruttore ... */ }

		public function muovi(passi:uint):void
		{
			/* Sposta l'unita di N passi */
			trace("Chiamata a 'Unita.muovi()'");
		}
	}
}

Fante.as - Classe Derivata

package
{
	import Unita;
	
	public class Fante extends Unita
	{
		public function Fante():void
		{
			this.attacco = 10;
			this.difesa = 20;
			this.distanza = 1;
			this.velocita = 5;
		}

		override public function muovi(passi:uint):void
		{
			trace("Chiamata a 'Fante.muovi()'");
		}
	}
}

HelloWorld.as - Classe documento

package
{
	import Fante;
	import flash.display.MovieClip;

	public class HelloWorld extends MovieClip
	{
		public function HelloWorld():void
		{
			var soldato:Fante = new Fante();
			soldato.muovi(10); // Output : Chiamata a 'Fante.muovi()'
		}
	}
}

Se non avessimo effettuato un override nella classe Fante, la chiamata al metodo "muovi()" avrebbe provocato il seguente output :
Chiamata a 'Unita.muovi()'


Metodo super

Potrebbe capitare durante l'implementazione di un override, di non voler riscrivere completamente il metodo base ma di volergli solo aggiungere qualche funzionalità.

In questo caso ci sarà di aiuto lo speciale metodo super, attraverso cui ci sarà possibile accedere direttamente alle proprietà (attributi e metodi) della classe base, da una sua derivata.

Nel prossimo esempio riscriverò solo la classe Fante, lasciando i file Unita.as e HelloWorld.as invariati :

Fante.as - Classe Derivata

package
{
	import Unita;
	
	public class Fante extends Unita
	{
		public function Fante():void
		{
			this.attacco = 10;
			this.difesa = 20;
			this.distanza = 1;
			this.velocita = 5;
		}

		override public function muovi(passi:uint):void
		{
			super.muovi(passi * this.velocita);
			trace("Chiamata a 'Fante.muovi()'");
		}
	}
}

Richiamando la classe documento HelloWorld si ottiene il seguente output :
Chiamata a 'Unita.muovi()'
Chiamata a 'Fante.muovi()'

Nota : vengono ereditati tutti i membri che non sono private e statici.

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.

18. Gli Oggetti e l'Ereditarietà

Il concetto dell'ereditarietà, è uno dei più importanti della programmazione orientata agli oggetti, a cui si appoggiano altri metodi avanzati di programmazione come ad esempio il Polimorfismo o le Classi Astratte che tratteremo nei capitoli seguenti.

L'ereditarietà ci consente di creare delle classi (classi derivate o sottoclassi) basate su classi già esistenti (classi base o superclassi).

Un grande vantaggio è quello di poter riutilizzare il codice di una classe di base senza doverlo modificare.

L'ereditarietà ci consente quindi di scrivere del codice molto più flessibile, in quanto permette una generalizzazione molto più forte di un concetto, rendendo più facile descrivere una situazione di vita reale.

Pensate alla classe base come ad un oggetto che descrive un concetto generale, e pensate invece alle sottoclassi come ad una specializzazione di tale concetto, esteso mediante proprietà e metodi aggiuntivi.

Imparare a programmare utilizzando questi concetti è un passo fondamentale per costruire applicazioni di un certo livello.

Vediamo ora un esempio dove definiremo una classe base e di seguito due derivate (sottoclassi) attraverso la parola chiave extends.
Segue la definizione della classe madre (base) :

Animale.php

<?php

	class Animale // Classe Base
	{
		public $zampe;
		public $ordine;
		public $nome;

		public function Animale($z, $o, $n) // Costruttore
		{
			$this->zampe = $z;
			$this->ordine = $o;
			$this->nome = $n;
		}

		protected function stampaDati() // Metodo protetto : può essere richiamato solo dalle classi derivate
		{ echo $this->nome . " : Zampe = " . $this->zampe . " / Ordine = " . $this->ordine; }
	}

?>

Cane.php

<?php

	require_once("Animale.php");

	class Cane extends Animale // "Sottoclasse" o "Classe Derivata"
	{
		public function Cane() // Costruttore classe derivata
		{
			parent::Animale(4, "Vertebrati", "Cane"); // Chiamata al costruttore della classe madre

			/* oppure più generalizzato

			parent::__construct(4, "Vertebrati", "Cane"); */
		}

		public function stampaDati($suono)
		{
			echo "Faccio $suono perchè sono ";
			parent::stampaDati(); // Chiamata al metodo protetto della classe madre
		}
	}

?>

Gallina.php

<?php

	require_once("Animale.php");

	class Gallina extends Animale // "Sottoclasse" o "Classe Derivata"
	{
		public function Gallina() // Costruttore classe derivata
		{
			parent::Animale(2, "Vertebrati", "Gallina"); // Chiamata al costruttore della classe madre
		}

		public function stampaDati($suono)
		{
			echo "Faccio $suono perchè sono ";
			parent::stampaDati(); // Chiamata al metodo protetto della classe madre
		}
	}

?>

Nel codice soprastante abbiamo dichiarato una classe base "Animale" e due classi derivate da questa : "Cane" e "Gallina".

Il metodo stampaDati di Animale è dichiarato con il modificatore di accesso protected, ossia può essere utilizzato solo all'interno della classe madre Animale e all'interno di tutte le classi da essa derivate, quindi anche Cane e Gallina.

Per richiamare il costruttore della classe madre dalle figlie è sufficiente utilizzare parent:: invece di this, in questo modo accediamo direttamente alla classe base, allo stesso modo con cui self:: viene usato al posto di this.

Ora vediamo un frammento di codice dove utilizziamo le classi appena dichiarate :

test.php

<?php

	require_once("Cane.php");
	require_once("Gallina.php");

	$cane = new Cane();
	$cane->stampaDati("bau");

	echo "\n\n<br /><br />\n\n";

	$gallina = new Gallina();
	$gallina->stampaDati("chicchirichì");

?>

Il risultato ottenuto.

In questo modo possiamo ampliare e modificare "Animale" senza effettivamente modificarne la classe e quindi il file Animale.php.

Sarà sufficiente fare le modifiche necessarie direttamente alla classi derivate e, qualora si presentassero nuove esigenze, sarà possibile creare una nuova sottoclasse di Animale senza intaccare il funzionamento delle due derivate Cane e Gallina, o addirittura potremmo creare una nuova sottoclasse derivata da una delle sottoclassi di Animale come nell'esempio che segue.

Alano.php

<?php

	require_once("Cane.php");

	class Alano extends Cane
	{
		private $suono;

		public function Alano()
		{
			parent::Cane();
			$this->suono = "wuoff";
		}

		public function stampaDati()
		{
			parent::stampaDati($this->suono);
			echo " / Razza : Alano";
		}
	}

?>

test.php

<?php

	require_once("Alano.php");

	$cane = new Alano();
	$cane->stampaDati();

?>

Il nuovo risultato ottenuto.

Nota : PHP non supporta l'ereditarietà multipla, offrendoci come alternativa le Interfacce.

Condividi contenuti