Programmazione orientata agli oggetti in ANSI-C. Ereditarietà – Circle

Un cerchio non è altro che un grosso punto: oltre alle coordinate del centro c'è bisogno di un valore per il raggio. Disegnare un cerchio è un po' diverso rispetto a disegnare un punto, ma spostare un cerchio richiede semplicemente che vengano cambiate le coordinate del centro.

Ecco dunque un esempio per spiegare a fondo in che cosa consiste il riuso del codice. Facciamo una copia dell'implementazione dei punti e andiamo a cambiare quelle parti in cui un cerchio si differenzia da un punto. Quindi, struct Circle avrà un componente aggiuntivo:

int rad;

Questo componente viene inizializzato dal costruttore:

self —> rad = va_arg(* app, int);

e utilizzato in Circle_draw()

printf("circle at %d,%d rad %d\n",
self —> x, self —> y, self —> rad);

Abbiamo un problema ora in move(). Le azioni necessarie per lo spostamento sono identiche per un punto e un cerchio: dobbiamo però aggiungere gli argomenti per il cosiddetto displacement (riposizionamento) dei componenti delle coordinate. Se move() fosse linkato dinamicamente, potremmo disporre di due funzioni diverse che fanno la stessa cosa, ma c'è sicuramente un modo migliore per risolvere il problema. Consideriamo infatti la possibile rappresentazione di punti e cerchi così come da figura:

struct_point_struct_circle

La figura mostra che ogni cerchio inizia con un punto. Se derivassimo la struct Circle aggiungendo rad alla fine della struct Point, potremmo passare un cerchio a move() perchè la parte iniziale della sua rappresentazione è simile a quello che move() si aspetta di ricevere. E in effetti, i valori di x e y sarebbero i soli dati che move() andrebbe a cambiare. Ecco un buon motivo er fare sì che la parte iniziale di un cerchio sembri un punto

Lasciamo che la struttura derivata inizi con una copia della struttura base che stiamo estendento. Il concetto di information hiding richiede che non si possano raggiungere le informazioni della struttura base in modo diretto; di conseguenza utilizzeremo un quasi invisibile simbolo di underscore come nome e lo dichiareremo come const per evitare che inavvertitamente vengano fatti assegnamenti.

Quello di cui abbiamo parlato è l'ereditarietà semplice: una sottoclasse viene derivata da una superclasse (o base class) semplicemente appesantendo la struttura che rappresenta un oggetto della superclasse.
Poichè la rappresentazione di un oggetto della sottoclasse (un cerchio ad esempio) inizia proprio come la rappresentazione di un oggetto della superclasse (ossia il punto) il cerchio può sempre essere inteso come un punto – ed infatti l'indirizzo iniziale della rappresentazione di un cerchio è realmente una rappreentazione di un punto.

Ecco spiegato perchè a rigor di logica è possibile passare un cerchio a move(): la sottoclasse eredita i metodi della superclasse perchè questi metodi operano solo su una parte della rappresentazione della sottoclasse che è identica alla rappresentazione della superclasse per i quali i metodi sono stati originariamente scritti. Passare un cerchio come un punto significa convertire da una struct Circle ad una struct Point. Faremo riferimento a questa operazione come un up-cast da una sottoclasse a una superclasse – in ANSI C è un'operazione che si può fare solo esplicitamente, attraverso un operatore di conversione oppure attraverso un valore void * intermedio.

Tipicamente, non è corretto fare in modo di passare un punto ad una funzione realizzata espressamente per i cerchi, come ad esempio Circle_draw(): convertire da una struct Point * a una struct Circle * è ammissibile solo se il punto era originariamente un cerchio. Questa operazione è chiamata down-cast da una superclasse ad una sottoclasse – ciò richiede una conversione esplicita oppure un valore void * intermedio, e può essere fatta solo da puntatori ad oggetti che si trovano nella sottoclasse con cui devono iniziare.

Scarica subito una copia gratis

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend