Programmazione orientata agli oggetti

La programmazione orientata gli oggetti (nel seguito OOP, Object Oriented Programming) è un paradigma di programmazione, che prevede di raggruppare in un'unica entità (la classe) sia le strutture dati che le procedure che operano su di esse, creando per l'appunto un "oggetto" software dotato di proprietà (dati) e metodi (procedure) che operano sui dati dell'oggetto stesso.

La modularizzazione di un programma viene realizzata progettando e realizzando il codice sotto forma di classi che interagiscono tra di loro. Un programma ideale, realizzato applicando i criteri dell'OOP, sarebbe completamente costituito da oggetti software (istanze di classi) che interagiscono gli uni con gli altri.

La programmazione orientata agli oggetti è particolarmente adatta a realizzare interfacce grafiche.

Indice

Storia

La classe può essere considerata l'erede del tipo di dato astratto, una tendenza che si è sviluppata all'interno del paradigma della programmazione procedurale, secondo la quale un modulo dovrebbe implementare un tipo di dato definito dall'utente, con cui si possa interagire solo attraverso una interfaccia ben definita, che nasconda agli altri moduli i dettagli dell'implementazione, in modo che sia possibile modificarli contenendo gli effetti della modifica sul resto del programma. La classe può essere vista come il costrutto che permette di realizzare questa astrazione con un supporto strutturato da parte del linguaggio.

Classi

Le classi sono uno strumento per costruire strutture dati che contengano non solo dei dati ma anche il codice per gestirli.

Come tutti i costrutti che permettono di definire di strutture dati, una classe definisce un nuovo tipo di dato.

I membri di una classe sono dati (esattamente come i membri di un record), chiamati attributi, e metodi, ovvero procedure, che operano su un oggetto.

Dal punto di vista matematico, una classe definisce un insieme in modo intensivo, ovvero definendone le caratteristiche invece che elencandone gli elementi. Se l'accesso agli attributi è ristretto ai soli membri della classe, le caratteristiche dell'insieme possono includere vincoli sui possibili valori che la tupla degli attributi può o non può assumere, e anche sulle possibili transizioni tra questi stati. Un oggetto può quindi essere vista come una macchina a stati finiti.

Una classe può dichiarare riservati una parte delle sue proprietà e/o dei suoi metodi, e riservarne l'uso a se stesso e/o a particolari tipi di oggetti a lui correlati.

Oggetti

L'oggetto è una instanza di una classe (a volte di più di una). Un oggetto occupa memoria, la sua classe definisce come sono organizzati i dati in questa memoria.

L'oggetto possiede tutti gli attributi definiti nella classe, ed essi hanno un valore, che può mutare durante l'esecuzione del programma come quello di qualsiasi variabile. Se il paradigma della OOP è applicato in modo rigido, gli attributi di un oggetto vengono manipolati solo da metodi invocati su quello stesso oggetto.

Sintatticamente, i metodi di una classe vengono invocati "su" un particolare oggetto, e ricevono come parametro implicito l'oggetto su cui sono stati invocati. Questo parametro spesso può essere referenziato esplicitamente con la parola chiave this, che è un puntatore (in C++) o un riferimento (in java) all'oggetto su cui il metodo è stato invocato.

Gli oggetti effettivamente creati sono membri dell'insieme definito dalla loro classe.

Alcuni linguaggi forniscono un supporto per l'inizializzazione automatica di un oggetto, con uno speciale metodo detto costruttore. Allo stesso modo, la fine della vita di un oggetto può essere gestita con un metodo detto distruttore.

Ereditarietà

L'OOP prevede un meccanismo molto importante, l'ereditarietà, che permette di derivare nuovi tipi di dati a partire da classi già definite. L'ereditarietà permette di aggiungere membri ad una classe, e di modificare il comportamento dei metodi, in modo da adattarli alla nuova struttura della classe.

Da una stessa classe è possibile costruire diverse classi derivate. Da una classe derivata è possibile derivarne un'altra con lo stesso meccanismo.

Sintatticamente, una classe può essere definita come derivata da un'altra classe esistente. La classe derivata, o sottoclasse, eredita tutti i metodi e gli attributi della classe "genitrice", e può aggiungere membri alla classe, sia attributi che metodi, e/o ridefinire il codice di alcuni metodi.

Gli oggetti della classe derivata possiedono sia le caratteristiche della propria classe che quelle della classe genitrice, e quindi appartengono ad entrambi i tipi di dato. Il tipo della classe derivata è detto sottotipo. Una variabile di tipo classe può contenere oggetti sia del tipo per cui è dichiarata che di tipi da esso derivati. Naturalmente questo è realizzabile solo se la variabile è un puntatore o un riferimento ad un oggetto.

Ad esempio, se nel mio programma esiste già una classe "mezzoditrasporto" che ha come proprietà i dati di posizione, velocità, destinazione e carico utile, ed occorre una nuova classe "aereo", è possibile crearla direttamente dall'oggetto "mezzoditrasporto" dichiarando una classe di tipo "aereo" che eredita da "mezzoditrasporto" e aggiungendovi anche il dato "Quota di crociera", con il vantaggio che la nuova classe sarà sia un "aereo" che un "mezzoditrasporto", permettendo di gestire in modo omogeneo tutti i mezzi con una semplice lista di "mezziditrasporto".

L'ereditarietà può essere usata come meccanismo per gestire l'evoluzione ed il riuso del software.

Polimorfismo

La possibilità che una classe derivata ridefinisca i metodi e le proprietà dei suoi antenati rende possibile che gli oggetti appartententi ad una classe che ha delle sottoclassi rispondano diversamente alle stesse istruzioni. I metodi che vengono ridefiniti in una sottoclasse sono detti "polimorfi", in quanto lo stesso metodo si comporta diversamente a seconda del tipo di oggetto su cui è invocato.

Le buone regole di programmazione ad oggetti prevedono che quando una classe derivata ridefinisce un metodo, il nuovo metodo abbia la stessa semantica di quello ridefinito, dal punto di vista degli utenti della classe.

Binding dinamico

Il polimorfismo è particolarmente utile quando la versione del metodo da eseguire viene scelta sulla base del tipo di oggetto effettivamente contenuto in una variabile a runtime (invece che al momento della compilazione). Questa funzionalità è detta binding dinamico (o late-binding), e richiede un grosso sforzo di supporto da parte della libreria runtime del linguaggio.

Se ho una variabile di tipo A, e il tipo A ha due sottotipi (sottoclassi) B e C, che ridefiniscono entrambe il metodo m(), l'oggetto contenuto nella variabile potrà essere di tipo A, B o C, e quando sulla variabile viene invocato il metodo m() viene eseguita la versione appropriata per il tipo di oggetto contenuto nella variabile in quel momento.

Per ritornare all'esempio di poco fa, supponiamo che un "aereo" debba affrontare procedure per l'arrivo e la partenza molto più complesse di un normale camion, come in effetti è: allora le procedure arrivo() e partenza() devono essere cambiate rispetto a quelle della classe base "mezzoditrasporto". Quindi provvediamo a ridefinirle nella classe "aereo" in modo che facciano quello che è necessario (polimorfismo): a questo punto, dalla nostra lista di mezzi possiamo prendere qualsiasi mezzo e chiamare arrivo() o partenza() senza doverci più preoccupare di che cos'è l'oggetto che stiamo maneggiando: che sia un mezzo normale o un aereo, si comporterà rispondendo alla stessa chiamata sempre nel modo giusto.

Il binding dinamico è supportato dai più diffusi linguaggi di programmazione ad oggetti come il Java o il C++. C'è però da sottolineare che in Java il binding dinamico è implicitamente usato come comportamento di default nelle classi polimorfe, mentre il C++ per default non usa il binding dinamico e se lo si vuole utilizzare bisogna inserire la keyword virtual nella segnatura del metodo interessato.

Diversi approcci all'OOP

Naturalmente non sono tutte rose e fiori; la OOP soffre di grossi problemi di efficienza se applicata a tutto indiscriminatamente, come avviene nel linguaggio Smalltalk che utilizza gli oggetti anche per i tipi primitivi.

Un approccio più pratico e realista è quello adottato da linguaggi come Java e C++ che limitano la creazione di oggetti alle sole entità che il programmatore decide di dichiarare come tali più eventualmente una serie di oggetti predefiniti, per lo più riguardanti il sistema. In questo modo tali linguaggi restano efficienti, ma l'uso degli oggetti creati richiede più attenzione e più disciplina.

Inoltre il fatto di ridefinire i metodi ereditati dalle classi base può portare a introdurre errori nel programma se per caso questi sono usati all'interno della classe base stessa (il noto problema della classe base fragile).

Voci correlate

See also: Programmazione orientata agli oggetti, C plus plus, Classe (informatica), Compilatore, Funzione (informatica), GUI, Insieme, Java, Linguaggio C Plus Plus, Linguaggio di programmazione