Programmazione orientata agli oggetti in ANSI-C. La classe Point rivista e corretta -1

Vogliamo ingegnerizzare un preprocessore ooc che ci aiuti nel mantenere coerenti le nostre classi con la codifica standard che ci siamo imposti come convenzione. Il miglior modo per progettare un preprocessore è quello di partire prendendo un esempio reale di una classe esistente e vedere come si può semplificare l'implementazione utilizzando ipotesi opportune circa a quanto il preprocessore debba fare. In altre parole, consentiteci di comportarci come il preprocessore stesso per un po'.

La classe Point del capitolo 4 e della sezione 6.10 è un ottimo esempio: non è la classe radice del nostro sistema, richiede una nuova metaclasse e ha pochi metodi. D'ora in poi useremo il corsivo e ci riferiremo a Point per sottolineare che ci serve solamente per simulare ciò che il preprocessore avrà in pasto.

Partiamo quindi con una descrizione di classe più o meno autodescrittiva, descrizione che possiamo capire facilmente e che a sua volta possa essere abbastanza semplice per essere compresa da un preprocessore basato su awk:

%PointClass: Class Point: Object { // header
int x; // object components
int y;
% // statically linked
void move (_self, int dx, int dy);
%- // dynamically linked
void draw (const _self);
%}

Il grassetto nella descrizione di classe di cui sopra indica gli elementi che ooc riconosce; il font standard di linea stampante viene usato invece per gli elementi che il processore legge qui e riproduce da qualche altra parte. I commenti iniziano con // e terminano fino alla fine della linea, le linee possono essere estese con un backslash.

Descriviamo quindi una nuova classe Point che è una sottoclasse di Object. Gli oggetti hanno nuovi componenti x e y e possono essere entrambi di tipo int. C'è un metodo staticamente linkato move() che può cambiare l'oggetto usando gli altri parametri. Introduciamo inoltre un nuovo metodo linkato dinamicamente draw(), di conseguenza dobbiamo iniziare una nuova metaclasse PointClass.
estendendo la super metaclasse Class. L'argomento di draw() è const, ossia non può essere cambiato.

Se non avessimo nuovi metodi linkati dinamicamente, la descrizione sarebbe addirittura più semplice. Consideriamo la classe Circle come esempio:
Alcune Parti del pattern sono usate ripetutamente, ad esempio per tutti i metodi con un certo linkage oppure per tutti i parametri di un metodo. Altre parti del pattern dipendono da condizioni come ad esempio da come è stata definita una nuova metaclasse. Ciò viene indicato in corsivo e con indentazione.

La descrizione di classe contiene anche sufficienti informazioni per produrre il file di rappresentazione. Ecco un pattern per generare Point.r:

#ifndef Point_r
#define Point_r
#include "Object.r"
struct Point { const struct Object _;
for all components
int x;
int y;
};
if there is a new metaclass
struct PointClass { const struct Class _;
for all methods in %-
void (* draw) (const void * self);
};
for all methods in %-
void super_draw (const void * class, const void * self);
#endif

Il file originale può essere consultato nella sezione 6.10. Contiene le definizioni per su macro di accesso x() e y(). Pertanto, ooc può inserirli nel file di rappresentazion e adottiamo la fonvenzione che un file di descrizione di classe possa contenere linee extra in aggiunta a quelle della descrizione di classe. Queste linee sono copiate in un file interfaccia o se sono precedute da un alinea con %prot . prot si riferisce a informazioni protette, queste linee sono disponibili ad una classe e alle sue sottoclassi ma non ad un'applicazione che utilizza tale classe.

La descrizione di classe contiene sufficienti informazioni per far sì che ooc generi una parte significativa dei file di implementazione. Faremo riferimento a varie parti di Point.c per fornirvi un esempio di quello che stiamo spiegando:

#include "Point.h" // include
#include "Point.r"

Per prima cosa, i file di implementazione includono i file di interfaccia e di rappresentazione.

implementation file includes the interface and representation files.
// method header
void move (void * _self, int dx, int dy) {
for all parameters // importing objects
if parameter is a Point
struct Point * self = _self;
for all parameters // checking objects
if parameter is an object
assert(_self);
... // method body

Per i metodi linkati staticmanete, possiamo verificare che siano permessi per la classe prima che si possa generare l'header del metodo. Con un ciclo sui parametri possiamo inizializzare le variabili locali da tutti quei parametri che si riferiscono ad oggetti nella classe alla quale il metodo appartiene, e possiamo proteggere il metodo dall'uso di puntatori nulli.

// method header
static void Point_draw (const void * _self) {
for all parameters // importing objects
if parameter is a Point
const struct Point * self = _self;
... // method body

Per i metodi linkati dinamicamente genereremo gli headers e importeremo gli oggetti. Il pattern può differire leggermente in accordo al fatto che il selettore abbia già verificato che gli oggetti siano proprio quelli che pretendono di essere.

Ci sono comunque alcuni altri problemi, comunque. Come sottoclasse di Object la nostra classe Point può sovrascrivere un metodo linkato dinamicamente come ctor() che appare per primo in Object. Se ooc deve generare tutti gli headers dei metodi, dobbiamo leggere tutte le descrizioni delle superclassi fino ad arrivare alla radice della gerarchia. Dal nome della superclasse Object nella descrizione di classe per Point dovremo essere in grado di trovare il file contenente la descrizione di classe per la superclasse. La soluzione ovvia è memorizzare la descrizione per Object in un file con un nome del tipo Object.d.

static void * Point_ctor (void * _self, va_list * app) {
...
Scarica subito una copia gratis

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend