Programmazione orientata agli oggetti in ANSI-C. Una nuova metaclasse – PointClass

Object e Class sono le radici della gerarchia delle classi. Ogni classe è una sottoclasse di Object e eredita i suoi metodi, ogni metaclasse è una sottoclasse di Calss e coopera con i suoi costruttori. Any in section 6.4 ha mostrato come una semplice sottoclasse può essere realizzata rimpiazzando i metodi dinamicamente linkati della sua superclasse e, possibilmente, definendo nuovi metodi staticamente linkati.

In questa sezione ci occuperemo invece di costruire classi con più funzionalità. Come esempio collegheremo Point e Circle alla nostra gerarchia di classi. Queste classi avranno un nuovo metodo dinamicamente linkato, draw(). Avremmo pertanto bisogno di una nuova metaclasse per appoggiarvi il link. Ecco l'interfaccia del file Point.h

#include "Object.h"
extern const void * Point; /* new(Point, x, y); */
void draw (const void * self);
void move (void * point, int dx, int dy);
extern const void * PointClass; /* adds draw */

La sottoclasse include sempre la superclasse e definisce un puntatore alla descrizione di classe e alla descrizione della metaclasse se ne esiste una nuova. Una volta introdotte le metaclassi, potremo dichiarare il selettore per un metodo linkato dinamicamente, il tutto nello stesso file interfaccia del puntatore della metaclasse.

Il file di rappresentazione Point.r contiene la struttura struct Point con le sue macro di accesso proprio come già visto in precedenza e contiene i selettori della superclasse assieme alla struttura della metaclasse:

#include "Object.r"
struct Point { const struct Object _; /* Point : Object */
int x, y; /* coordinates */
};
#define x(p) (((const struct Point *)(p)) —> x)
#define y(p) (((const struct Point *)(p)) —> y)
void super_draw (const void * class, const void * self);
struct PointClass {
const struct Class _; /* PointClass : Class */
void (* draw) (const void * self);
};

Il file di implementazione Point.c contiene move(), Point_draw(), draw() e super_draw(). Questi metodi sono scritti come prima: abbiamo analizzato la tecnica per la gestione dei selettori della superclasse nella sezione precedente. Il costruttore deve chiamare il costruttore della superclasse:

static void * Point_ctor (void * _self, va_list * app)
{ struct Point * self = super_ctor(Point, _self, app);
self —> x = va_arg(* app, int);
self —> y = va_arg(* app, int);
return self;
}

Un nuovo concetto proposto nel file di cui sopra è il costruttore della metaclasse. Chiama il costruttore della superclasse per la corretta gestione dell'ereditarietà e poi usa lo stesso meccanismo di Class_ctor() per sovrascrivere il nuovo metodo linkato dinamicamente draw().

static void * PointClass_ctor 
(void * _self, va_list * app)
{ struct PointClass * self
= super_ctor(PointClass, _self, app);
typedef void (* voidf) ();
voidf selector;
va_list ap = * app;
while ((selector = va_arg(ap, voidf)))
{ voidf method = va_arg(ap, voidf);
if (selector == (voidf) draw)
* (voidf *) & self —> draw = method;
}
return self;
}

Da notare che condividiamo le coppie selettore/metodo nella lista degli argomenti con il costruttore della superclasse,

Con questo costruttore possiamo inizializzare dinamicamente le nuove descrizioni di classe: PointClass è realizzata a partire da Class e Point è realizzata a partire da PointClass.

void initPoint (void)
{
if (! PointClass)
PointClass = new(Class, "PointClass",
Class, sizeof(struct PointClass),
ctor, PointClass_ctor,
0);
if (! Point)
Point = new(PointClass, "Point",
Object, sizeof(struct Point),
ctor, Point_ctor,
draw, Point_draw,
0);
}

Scrivere l'inizializzazione è abbastanza semplice: specificheremo i nomi delle classi, le relazioni di ereditarietà e la dimensione delle strutture degli oggetti e infine aggiungeremo le coppie selettore/metodo per tutti i metodi linkati dinamicamente definiti nel file. Uno zero termina ogni lista di argomenti.

Nel capitolo 9 illustreremo una tecnica per permettere di effettuare l'inizializzazione automatica. Per ora, initPoint() viene aggiunta all'interfaccia in Point.h e la funzione deve eessere chiamata prima che si possano istanziare punti o sottoclassi. La funzione può essere chiamata più di una volta – produrrà esattamente una descrizione di classe PointClass e Point.
Finchè chiameremo InitPoint() da main() possiamo riutilizare il programmino di test illustrato nella sezione 4.1 e ottenere lo stesso output.

$ points p
"." at 1,2
"." at 11,22

Circle è la sottoclasse di Point introdotta nel capitolo 4. Quando aggiungiamo Circle alla gerarchia delle classi possiamo eliminare il bruttissimo codice inserito nel costruttore così come discusso nella sezione 4.7

static void * Circle_ctor 
(void * _self, va_list * app)
{ struct Circle * self = super_ctor(Circle, _self, app);
self —> rad = va_arg(* app, int);
return self;
}

Avremmo ovviamente bisogno di una funzione di inizializzazione InitCircle() da chiamarsi in main() prima di istanziare i cerchi:

void initCircle (void)
{
if (! Circle)
{ initPoint();
Circle = new(PointClass, "Circle",
Point, sizeof(struct Circle),
ctor, Circle_ctor,
draw, Circle_draw,
0);
}
}

poiché Circle dipenda da Point, chiameremo InitPoint prima di inizializzare Circle. Tutte queste funzioni realizza il proprio compito una volta sola, e possiamo chiamarle in qualsiasi ordine purchè si tenga conto delle relazioni di interdipendenza insite nelle funzioni stesse.

Scarica subito una copia gratis

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend