Strutture e campi funzione
La programmazione orientata agli oggetti (OOP) impone una nuova visione di
concetti quali "Tipo di dato" e "Istanze di tipo". Sostanzialmente
mentre altri paradigmi di programmazione vedono le istanze di un tipo di dato
come una entita` passiva, nella programmazione a oggetti invece tali istanze
diventano a tutti gli effetti entita` (oggetti) attive.
L'idea e` che non bisogna piu` manipolare direttamente i valori di una struttura
(intesa come generico contenitore di valori), meglio lasciare che sia la struttura
stessa a manipolarsi e a compiere le operazioni per noi. Tutto cio` che bisogna
fare e` inviare all'oggetto un messaggio che specifichi l'operazione da compiere
e attendere poi che l'oggetto stesso ci comunichi il risultato. Il meccanismo
dei messaggi viene sostanzialmente implementato tramite quello della chiamata
di funzione e l'insieme dei messaggi cui un oggetto risponde viene definito
associando al tipo dell'oggetto un insieme di funzioni.
In C++ cio` puo` essere realizzato tramite le strutture:
struct Complex {
float Re;
float Im;
// Ora nelle strutture possiamo avere
// dei campi di tipo funzione;
void Print();
float Abs();
void Set(float PR, float PI);
};
Cio` che sostanzialmente cambia, rispetto a quanto visto, e` che una struttura
puo` possedere campi di tipo funzione (detti funzioni membro oppure
metodi) che costituiscono insieme ai campi ordinari (membri
dato o attributi) l'insieme dei messaggi (interfaccia)
a cui quel tipo e` in grado di rispondere. L'esempio non mostra come implementare
le funzioni membro, per adesso ci basta sapere che esse vengono definite da
qualche parte fuori dalla dichiarazione di struttura in modo pressocche` identico
alle ordinarie funzioni.
Una funzione dichiarata come campo di una struttura puo` essere invocata ovviamente
solo se associata ad una istanza della struttura stessa, dato che quello che
si fa e` inviare un messaggio ad un oggetto. Cio` nella pratica si fa tramite
la stessa sintassi utilizzata per selezionare un qualsiasi altro campo (solo
che ora ci sono anche campi funzione):
Complex A;
Complex* C;
A.Set(0.2, 10.3);
A.Print();
C = new Complex;
C -> Set(1.5, 3.0);
float FloatVar = C -> Abs();
Nell'esempio viene mostrato come inviare un messaggio: la quarta riga invia
il messaggio Print() all'oggetto A , l'ultima invece
invia il messaggio Abs() all'oggetto puntato da C
e assegna il valore ottenuto alla variabile FloatVar . Anche la
terza riga invia un messaggio ad A , in questo caso il messaggio
richiede dei parametri che vengono forniti nello stesso modo in cui vengono
forniti alle funzioni.
Il vantaggio principale di questo modo di procedere e` il non doversi piu` preoccupare
di come e` fatto quel tipo, se si vuole eseguire una operazione su una sua istanza
(ad esempio visualizzarne il valore) basta inviare il messaggio corretto, sara`
l'oggetto in questione ad eseguirla per noi. Ovviamente perche` tutto funzioni
e` necessario evitare di accedere direttamente agli attributi di un oggetto,
altrimenti crolla uno dei capisaldi della OOP, e sfortunatamente per noi il
meccanismo delle strutture consente l'accesso diretto a tutto cio` che fa parte
della dichiarazione di struttura, annullando di fatto ogni vantaggio:
// Con riferimento agli esempi riportati sopra:
A.Set(6.1, 4.3); // Setta il
valore di A
A.Re = 10; // Ok!
A.Im = .5; // ancora
Ok!
A.Print();
|