Abstract
In questo articolo parleremo di come sfruttare un linguaggio classico ed
importante come il C ANSI nell'ambito delle tecnologie orientate agli oggetti,
cercando di sfatare il mito che l'Object-Technology è un'esclusiva di altri
linguaggi!
Data di stesura: 20/10/2002
Data di pubblicazione:
23/10/2002
Ultima modifica: 04/04/2006
Nell'industria elettronica o in ambiti in cui le performance sono importanti il
linguaggio C ha un ruolo importante. Ugualmente preponderante è l'uso per
l'integrazione verso sistemi legacy: nel linguaggio Java il meccanismo J.N.I. (Java
Native Interface) permette di sopperire ad esigenze di performance ed integrazioni
a volte non superabili con i normali elementi messi a disposizione dallo stesso
linguaggio della SUN. Quando si adotta il C emerge, però, il problema della suo
mancato supporto alla tecnologia orientata agli oggetti che lo rende, a prima vista,
meno flessibile e, per i più distratti, anacronistico. Quello che molti sviluppatori
non considerano è che l'Object-Technology non è data da un insieme di tool object-
oriented ma, in primis, è un approccio mentale che, se opportunamente
supportato conduce ai risultati di flessibilità e riuso che ne hanno determinato il
successo. Vedremo che è possibile formulare codice C ANSI e ottenere elementi
orientati ad oggetti senza il supporto di un particolare runtime o ambiente di
sviluppo!
C come Java?
La programmazione ad oggetti è basata su pochi ma essenziali concetti tra i quali
ricordiamo l'INCAPSULAMENTO, alla base di una giusta scomposizione in
moduli del codice, l'EREDITARIETÀ ed il POLIMORFISMO, che permettono
riuso e flessibilità negli artefatti. In questo articolo è stato preso come riferimento
una versione leggera del modello ad oggetti di Java: arriveremo ai concetti
dell'object orientation in piccoli passi di natura implementativa.
Incapsulamento
Grazie a questo concetto è possibile porre un limite tra ciò
che è pubblicabile al mondo esterno e ciò che è proprio di un'astrazione. Nel C
non sono presenti dei modificatori di visibilità di tipo public o private. È possibile
simulare, comunque, questo particolare carattere degli oggetti sfruttando gli
elementi del linguaggio C che alterano la visibilità di quanto dichiarato in un file.
In particolare ciò che appartiene alla classe di memorizzazione EXTERNAL può
essere visto ovunque, anche tra file differenti, mentre ciò che è dichiarato STATIC
vede la propria visibilità limitata al file di appartenenza.
Supponiamo di avere due elementi, un header e un'implementazione: obj.h e obj.c:
#include <...
#define ...
int method1(void);
int metodo2(void);
#include <...
#include "obj.h"
static int
method1(void)
{
...
}
int
method2(void)
{
...
}
Il risultato di quanto esposto è che il primo metodo è visibile solo nell'ambito del
file obj.c, mentre il secondo metodo è utilizzabile anche all'esterno. Questa
possibilità vale anche per le variabili e ci offre una modalità per distinguere tra
ciò che è pubblico e ciò che intendiamo come di competenza di uno specifico
modulo.
Classe e oggetto
A questo punto estendiamo il nostro sistema per arrivare ai
concetti di classe e oggetto. In C è STRUCT l'elemento sintattico che più si
avvicina all'idea di oggetto ma, sfortunatamente non ha supporto per
l'incapsulamento. Per risolvere questa problematica useremo i puntatori onde
riferirsi ad una struttura anche se questa non è stata definita. Useremo la sintassi:
struct nome_struttura *ptr;
Ricordiamo inoltre che in C è possibile definire tipi utente grazie alla keyword
TYPEDEF. Possiamo pensare allora ad una dichiarazione nella forma:
typedef struct nome_struttura *nome_classe;
e successiva definizione:
#include "obj.h"
struct nome_struttura { ... }
Abbiamo, ora gli elementi per simulare un reference Java e proseguire nella nostra
idea del mondo ad oggetti. Consideriamo, infatti, il seguente codice:
Abbiamo usato un puntatore a funzione per creare un riferimento ad un pseudo-
metodo in quella che sarà la parte pubblica della nostra astrazione; evidenziamo
l'uso del modificatore STATIC per rendere l'implementazione del metodo privata.
Per completare un primo esempio concreto, basta fornire una funzione globale che
inizializzi i campi pubblici e privati, nonchè associ i puntatori a funzioni alle
definizioni delle stesse. Il risultato ultimo sarà una pseudo-classe da cui è possibile
ottenere delle istanze ed in cui è presente il concetto di ciò che è pubblico e ciò che
è privato.
#include <stdio.h>
#include <stdlib.h>
typedef struct classt_struct *_Class;
typedef struct
{
void *SUPER;
struct Class THIS;
_Class INTERNAL;
int (*get)(_Class ref);
void (*set)(_Class ref, int val);
} Class;
typedef Class *reference;
reference create(void);
#include "Object.h"
struct class_struct
{
int value;
};
static _object
newObject(void)
{
_Class tmp = (_Class)malloc(sizeof(struct class_struct));
tmp->val = 0;
return tmp;
}
static int
gv(_Class ref)
{
printf("\n metodo GET ... L'oggetto restituisce: %d \n",
ref->val);
return ref->val;
}
static void
sv(_Class ref, int v)
{
ref->val = v;
printf(" \n metodo SET ... L'oggetto vede settato il suo valore a:%d \n",
ref->val);
}
reference
create(void)
{
referene tmp = (reference)malloc(sizeof(Class));
tmp->SUPER = null;
tmp->THIS = (struct Class *)tmp;
tmp->INTERNAL = newObject();
tmp->get = gv;
tmp->set = sv;
return tmp;
}
Un altro cardine dell'object orientation è
l'ereditarietà: una classe B che estenda una classe A ne eredita alcuni
elementi (ad esclusione di quelli privati). È stato considerato il concetto di
ereditarietà singola, tipica di Java, in conseguenza del quale è possibile ricevere le
caratteristiche di un solo genitore a differenza dell'ereditarietà multipla dove è
possibile ereditare da più di un genitore!
Vediamo una possibile realizzazione.
A questo punto è interessante vedere una porzione di codice Java equivalente al
file Main2.c, e notare le forti similitudini. Viene presentata una classe Main che
ospita solo il metodo statico di esecuzione main, si supponga inoltre l'esistenza di
due classi, ClassA e ClassB, in rapporto di ereditarietà (ClassB estende ClassA):
public class Main
{
public static void main(String [] args)
{
ClassA obj1 = new ClassA();
ClassB obj2 = new ClassB();
obj1.set(10);
obj1.get();
obj2.stampa()
}
}
Da un punto di vista esecutivo, le tracce ottenibili su console diminuiscono
oltremodo le distanze dal nostro codice C e l'equivalente codice Java.
Conclusione
Questo semplice esempio pratico ci dimostra che l'object orientation non è un
qualcosa dettato dall'adozione o meno di un dato linguaggio, anche se un o.o.l. ha
connaturato il supporto per i concetti base della programmazione ad oggetti. È
evidente che in Java è molto più facile produrre oggetti e modellare i problemi in
questo modo, ma è anche vero che ragionando ad oggetti si può comunque
produrre codice orientato agli oggetti, anche quando il linguaggio non lo è:
questo rimane valido anche per il Cobol o altri linguaggi tipicamente orientati alle
procedure, oltre che per il C ANSI, come visto in questo articolo.
Informazioni sull'autore
Stefano Fago, classe 1973. Diplomato in ragioneria, ha conseguito il Diploma di Laurea in Informatica con un progetto legato alle interfacce grafiche soft-realtime in Java. Dopo esperienze in Alcatel ed Elea, ha svolto attività di consulenza come Software Developer e Trainer alla ObjectWay S.p.A. sede di Milano. Attualmente Software Designer presso la sezione Innovazione e Attività Progettuali di BPU Banca. Appassionato del linguaggio Java e di tutte le tecnolgie Object Oriented. Polistrumentista dilettante.