| prof. Nunzio Brugaletta |
C++:
programmazione e oggetti |
Una delle proprietà più potenti delle classi è la possibilità di definire una nuova classe (classe discendente, subclass) a partire da una classe esistente (classe antenata, superclass). La classe discendente ha tutte le proprietà della classe genitrice e, in più, può avere nuove proprietà o metodi specifici per la nuova classe o, addirittura, può ridefinire i metodi della classe genitrice. Questo meccanismo è quello a cui si fa riferimento quando si parla di ereditarietà.
Questo esposto brevemente è un meccanismo che permette, a partire da una classe generica, di derivare, mediante ereditarietà, classi via via sempre più specializzate: la classe figlia è una specializzazione della classe padre, i comportamenti generali della classe derivata sono quelli della classe genitrice a cui si aggiungono i comportamenti tipici degli oggetti della classe derivata stessa. Per portare un esempio intuitivo si potrebbe dire che la zanzara è un insetto (con tutte le caratteristiche tipiche degli insetti) che punge e succhia il sangue (caratteristiche tipiche della zanzara).
La classe libro, utilizzata precedentemente, definisce i comportamenti generici di un libro, contenuto in una libreria, che può essere prestato. Se però si pensa ad una biblioteca con soci iscritti che usufruiscono dei servizi, l'operazione di prestito, definita nella classe libro, è generica. Non basta, infatti, dire che il libro è in prestito, occorrerebbe, per esempio, avere anche informazioni sul socio che lo ha preso in prestito e l'operazione, per esempio di prestito, riguarda anche la registrazione dei dati del socio che ha preso in prestito il libro.
// Definizione di classe derivata
#ifndef C_LIBSOCIO
#define C_LIBSOCIO
#include "c_libro"
namespace biblioteca{
struct datisoc{ /*1*/
string nome;
string cognome;
string via;
};
class libSocio : public libro { /*2*/
public:
libSocio(); /*3*/
bool prestato(datisoc); /*4*/
bool restituito(); /*4*/
friend ostream& operator<<(ostream&, const libSocio&); /*5*/
protected:
datisoc socbib; /*6*/
private:
void setSocio(datisoc s1) {socbib = s1;}; /*7*/
};
// overloading operatore inserimento
ostream& operator<<(ostream& output, const libSocio& l1)
{
output << l1.libbib.titolo << " | " << l1.libbib.autore /*8*/
<< " | " << l1.libbib.editore << " | " /*8*/
<< l1.libbib.prezzo << endl; /*8*/
if(!(l1.presente)){
output << "Prestato a:" << endl
<< l1.socbib.nome << " | " << l1.socbib.cognome /*9*/
<< " | " << l1.socbib.via << endl; /*9*/
}
return output;
};
// Inizializzazione dati socio
libSocio::libSocio()
{
socbib.nome=" ", socbib.cognome=" ", socbib.via=" ";
}
// Prestito del libro al socio
bool libSocio::prestato(datisoc s1) /*10*/
{
bool prestitoOK=false;
if(libro::prestato()){ /*11*/
setSocio(s1); /*12*/
prestitoOK=true;
}
return prestitoOK;
}
// Restituzione libro dal socio
bool libSocio::restituito() /*10*/
{
bool ritornatoOK=false;
datisoc s1;
if(libro::restituito()){ /*11*/
s1.nome=" ", s1.cognome=" ", s1.via=" ";
setSocio(s1); /*12*/
ritornatoOK=true;
}
return ritornatoOK;
}
}
#endif
Nella 1, similmente al caso della classe libro, si definisce una struttura che contiene i dati di interesse del socio.
Nella 2 si evidenzia l'inizio di una gerarchia di classi: la nuova classe libSocio è un discendente di libro e ne eredita tutti i metodi. La sintassi del C++ prevede l'uso dell'operatore : fra il nome attribuito alla classe discendente e quello della classe genitore. Il qualificatore public associato a libro, assicura che i metodi pubblici di libro saranno pubblici anche per libSocio. I qualificatori ammessi sono: public, protected, private. Se si fosse specificato private i metodi ereditati sarebbero stati pubblici per libro ma non per libSocio: per esempio setLibro() sarebbe disponibile per un oggetto della classe libro ma non per un oggetto della libSocio. Il qualificatore public garantisce, nell'esempio proposto, la disponibilità del metodo getInPrestito anche per gli oggetti della classe libSocio.
Nella parte pubblica vengono definiti i metodi specifici della classe (3), in aggiunta a quelli ereditati da libro. Una proprietà notevole dell'ereditarietà è quella evidenziata nelle righe 4. La classe discendente ha la possibilità di ridefinire i metodi ereditati dalla classe base (overloading dei metodi) per poterli adattare alla propria specificità. Per gli oggetti della classe è ridefinito l'operatore di inserimento (5).
Nella parte protected della classe vengono definiti i dati membri della nuova classe (6), accessibili dai metodi della classe e dalle classi discendenti. Le funzioni membro di libSocio hanno accesso diretto anche ai dati membri di libro (libbib, presente) essendo questi dichiarati nella sezione protected della classe base. L'accesso ai dati della classe genitrice (8), assieme agli altri dati della classe (9) è utilizzato nella ridefinizione dell'operatore di inserimento. Il metodo setSocio è inserito nella parte privata (7) perché si tratta di un metodo utilizzato per la registrazione di un prestito: è richiamato da un altro metodo della classe, è un affare interno della classe.
Fra i metodi è presente (3) il costruttore che si occupa dell'inizializzazione dei dati del socio. Nell'esempio proposto il costruttore si rende necessario per inizializzare i dati del socio a cui è stato effettuato il prestito e che, quando si inserisce un libro nella biblioteca, ancora non esiste.
Le 10 ridefiniscono due metodi già definiti nella classe libro, in modo da adattarsi alla nuova classe. Tutte le volte che si richiamerà uno dei metodi applicati ad un oggetto della classe libSocio, verrà fatto riferimento a tali funzioni: le nuove definizioni mascherano le definizione di prestito() e restituito() ereditate da libro. Le istruzioni delle righe 11 ordinano di eseguire il metodo relativo per come era stato definito in libro: è questo quello a cui si fa riferimento quando si dice che una classe discendente è una specializzazione della classe base. In aggiunta alle istruzioni che si eseguivano prima (righe 11) i nuovi metodi si occupano di conservare (12) gli opportuni valori nelle variabili private del socio. Agli aggiornamenti dei dati del libro, ci pensa il metodo richiamato nelle 11.
Le regole adottate per la derivazione di libSocio da libro possono essere estese al caso generale:
La classe C2 eredita dalla classe C1 quando la classe figlia C2 è una specializzazione della classe padre C1. libro è un libro generico della biblioteca, libSocio è un libro al quale è collegato un socio.
La classe C2 eredita dalla classe C1 se C2 IS-A C1 (C2 è un C1). libSocio è un libro con ulteriori attributi, metodi aggiunti e ridefiniti.
| http://ennebi.solira.org |
ennebi@solira.org |