Articoli Manifesto Tools Links Canali Libri Contatti ?
Linguaggi / OOP

Object Orientation: linguaggi esclusivi?

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
di Stefano Fago Discuti sul forum   Stampa

ANSI C: un linguaggio importante.

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:
typedef struct nome_struttura *reference;

typedef struct
  {
    ...
    reference internal;
    void (*nome_metodo)(...);
    ...
  } nome_classe;
struct nome_struttura
  {
    ...
  };


static void
nome_impl_metodo(...)
{
  ...
}
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;
}
#include "Object.h"

int
main(void)
{
  reference myObject = create();
  
  myObject->set(myObject->INTERNAL, 10);
  myObject->get(myObject->INTERNAL);
  
  return 0;
}

Ereditarietà (singola)

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.
#include <stdio.h>
#include <stdlib.h>
#include "class.h" 


typedef struct clsB *_ClassB;

typedef struct
  {
    Class * SUPER;
    Struct ClassB * THIS;
    _ClassB INTERNAL;
    void (*stampa)(_ClassB ref, Class * ref2);
  } ClassB;


typedef ClassB *referenceB;


referenceB createB(void);
#include "classB.h"

struct clsB
  {
    const char * desciption;
  };


static _ClassB
newClassB(void)
{
  ClassB tmp =(_ClassB)malloc(sizeof(struct clsB));
  tmp->description = "\n FIGLIO DI CLASS\n";
  return tmp;
}

static void
sp(_ClassB ref, class *s)
{
  print("%s", ref->description);
  s->get(s->INTERNAL);
}

referenceB
createB(void)
{
  referenceB tmp = (referenceB)malloc(sizeof(Classb));

  tmp->SUPER    = create();
  tmp->THIS     = (struct ClassB *)tmp;
  tmp->INTERNAL = newClassB();
  tmp->stampa   = sp;

  return tmp;
}
#include "classB.h"

int main(void)
{
  reference obj1  = create();
  referenceB obj2 = createB();

  obj1->set(obj1->INTERNAL, 10);
  obj1->get(obj1->INTERNAL);
  obj2->stampa(obj->INTERNAL, obj2->SUPER);

  return 0;
}
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.

È possibile consultare l'elenco degli articoli scritti da Stefano Fago.

Altri articoli sul tema Linguaggi / OOP.

Discuti sul forum   Stampa

Cosa ne pensi di questo articolo?

Discussioni

Questo articolo o l'argomento ti ha interessato? Parliamone.