Programmazione orientata agli oggetti in ANSI-C. Visibilità e Funzioni d’accesso

Si passa ora a cercare di implementare Circle_draw(). Il principio dell'information hiding impone di usare tre files per ogni classe in virtù della filosofia "need to know", che tradotto significa "ciò che abbiamo bisogno di sapere". Circle.h contiene l'interfaccia per i tipi di data astratti, per la sottoclasse include il file interfaccia della superclasse per le dichiarazioni dei metodi ereditati.

#include "Point.h"
extern const void * Circle; 
/* new(Circle, x, y, rad) */

Il file interfaccia Circle.h viene incluso nel codice dell'applicazione per l'implementazione della classe, è protetto dalle inclusioni multiple.

La rappresentazione di un cerchio viene dichiarata nel file header Circle.r. Include il file di rappresentazione della superclasse così che si possa derivare la rappresentazione della sottoclasse estendendo la superclasse.

#inclue "Point.r"
struct Circle { const struct Point _; int rad;};

La sottoclasse necessita della rappresentazione della superclasse per implementare l'ereditarietà. La struct Circle contiene un const struct Point. Il punto è certamente non costante – move() cambierà le sue coordinate. Ma il qualificatore const vigila contro possibili operazioni che vadano ad effettuare l'overwriting dei componenti.

La rappresentazione Circle.r è inclusa solamente per l'implementazione della classe, viene protetto dalla inclusione multipla.
Infine, l'implementazione di un cerchio è definito nel file sorgente Circle.c che include l'interfaccia e i file di rappresentazione per la classe e per la gestione degli oggetti.

#include "Circle.h"
#include "Circle.r"
#include "new.h"
#include "new.r"
static void Circle_draw (const void * _self)
{ const struct Circle * self = _self;
printf("circle at %d,%d rad %d\n",
self —> _.x, self —> _.y, self —> rad);
}

In Circle_draw() abbiamo letto i componenti punto per i cerchio invadendo la parte della sottoclasse con il nome invisibile _-

Da un punto di vista dell'information hiding, questa non è una buona idea. Mentre leggere i valori delle coordinate non dovrebbe creare grossi problemi non possiamo mai essere sicuri che in altre situazioni una implementazione di una sottoclasse non vada a modificare la sua superclasse direttamente.

L'efficienza impone che una sottoclasse raggiunga i componenti della superclasse direttamente. L'informazion hiding e la manutenibilità richiede che una superclasse nasconda la sua rappresentazione il meglio possibile per le sue sottoclassi. Se optiamo per la seconda ipotesi, dovremmo fornire le funzioni di accesso per tutti quei componenti di una superclasse che una sottoclasse è autorizzata a manipolare, e le funzioni di modifica per quei componenti - se ve ne sono – che la sottoclasse può modificare.

Le funzioni di accesso e di modifica sono metodi linkati staticamente. Se li dichiariamo nel file di rappresentazione della superclasse, che è il solo incluso nell'implementazione delle sottoclassi possiamo usare le macro, perchè gli effetti secondari non costituiscono un problema se la macro usa ogni argomento una sola volta. Ad esempio, in Point.r definiamo le seguenti macro di accesso:

#define x(p) (((const struct Point *)(p)) —> x)
#define y(p) (((const struct Point *)(p)) —> y)
#define x(p) (((struct Point *)(p)) —> x)

Queste macro possono essere applicate a un puntatore ad ogni oggetto che inizia con struct Point, ossia agli oggetti creati da una qualsiasi sottoclasse dei nostri punti. La tecnica è quella di effettuare l'up-cast del puntatore. Const nel casting blocca l'assegnazione del risultato. Se const venisse omesso una macro chiamata x(p) produce un valore che può essere il target di un'assegnazione. Una funzione di modifica migliore potrebbe essere la definizione di macro

#define set_x(p,v) (((struct Point *)(p)) —> x = (v))

che produce un'assegnazione.

Al di fuori dell'implementazione di una sottoclasse, possiamo utilizzare solo i metodi linkati staticamente per le funzioni di modifica e accesso. La definizione di macro dimostra, comunque, che quando la rappresentazione di una classe è disponibile, l'information hiding può essere facilmente aggirato. Ecco qui di seguito un modo di definire struct Point in modo migliore. All'interno dell'implementazione della sueprclasse useremo la normale definizione

struct Point {
const void * class;
int x, y; /* coordinates */
};
Per l'implementazione della sottoclasse forniamo la versione
struct Point {
const char _ [ sizeof( struct {
const void * class;
int x, y; /* coordinates */
})];
};

Questa struttura ha la stessa dimensione della precedente ma non potremo leggere o scrivere i componenti perchè sono nascosti all'interno di una struttura anonima all'interno.

Scarica subito una copia gratis

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend