Perché la programmazione ad oggetti?
Sembrerà banale, ma la prima cosa che deve fare un programmatore è quello di realizzare programmi funzionanti, in secondo luogo il programma deve essere facilmente gestibile, ed in seguito poter riutilizzare i programmi già fatti.
I linguaggi non orientati agli oggetti si basano sulla tecnica a funzioni (programma = struttura dati + funzioni), il che permette di realizzare programmi di difficile manutenzione e riuso. La programmazione ad oggetti divide il programma in tante piccoli parti, che dovrebbero facilitare queste operazioni.
La struttura dati e le librerie di funzioni del programma si trasforma in una serie di oggetti ognuno dei quali con una propria struttura dati ed una propria libreria di funzioni. Un programma ad “oggetti” è un’ulteriore astrazione del programma a funzioni che potrebbe essere considerato come un programma composto da un solo oggetto. Facendo un analogia con gli edifici possiamo dire che con la programmazione ad oggetti si costruisce l’edificio disponendo di moduli prefabbricati, a differenza dei linguaggi procedurali dove ogni singola parte della struttura va costruita per intero.
Nella programmazione ad oggetti i dati sono racchiusi insieme alle funzioni negli oggetti, questo rappresenta l’ulteriore astrazione di questi linguaggi, e per accedervi si deve far ricorso a speciali funzioni.
La struttura dati che descrive lo stato di un oggetto viene definito dalle proprietà dell’oggetto e realizzata con le variabili dell’oggetto, mentre le funzioni che descrivono il comportamento dell’oggetto diventano i metodi dello stesso.
La classe è un pezzo di codice che descrive le proprietà e i metodi di un oggetto. Per cui adesso un programma è formato da una serie di classi. In gergo si dice che metodi e variabili sono membri della classe.
Nella programmazione ad oggetti vi sono particolari costruttori che permettono di manipolare gli stessi oggetti. In particolare l’operatore new permette di istanziare lo stesso oggetto, in pratica questo corrisponde alla chiamata di un particolare metodo (costruttore), che ha lo stesso nome della classe e che provvede ad inizializzare la struttura di dati dell’oggetto.
Per definire un linguaggio ad oggetti bisogna descrivere ancora altre tre proprietà:
- Incapsulamento
- Ereditarietà
- Poliporfismo
Incapsulamento
Chi usa un oggetto deve conoscere solo i nomi delle proprietà e dei metodi cosiddetti pubblici (public), che costituiscono l’ interfaccia dell’oggetto; quindi non ha bisogno di conoscere i dettagli dell’implementazione dello stesso che rimangono incapsulati, cioè nascosti, nello stesso oggetto (private).
Questo, nei linguaggi ad oggetti, rappresenta l’incapsulamento . L’incapsulamento serve per isolare il codice di un oggetto evitando di fatto che il suo cambiamento possa influenzare il codice di un altro oggetto, e si realizza attraverso particolari attributi che proteggono l’accesso a classi, metodi e variabili che vogliamo rendere inaccessibili. Attraverso l’incapsulazione si proteggono le proprietà degli oggetti, che possono essere resi accessibili solo attraverso speciali metodi di accesso (detti getter o getValue) e di modifica (detti setter o setValue).
Ereditarietà
Per modificare una classe già “completa”, non è più necessario (ma sempre possibile) modificare la classe originaria, come succedeva nella programmazione a funzioni; i linguaggi ad oggetti infatti, permettono di definire una nuova classe, che estende (o eredita) un’altra classe aggiungendo nuovi metodi e proprietà aggiuntivi rispetto alla classe cosiddetta padre.
Si possono anche ridefinire i metodi, della classe gerarchicamente superiore usando il cosiddetto overriding (sovrascrittura).
Polimorfismo
Con il meccanismo dell’ereditarietà, i programmi diventano delle gerarchie di classi che condividono proprietà e metodi; quando si richiama un metodo di un oggetto proveniente da una classe non si ha la certezza che venga richiamato un metodo ben definito (e cioè quello della classe stessa), ma sarà compito dell’esecutore scoprire a run-time quale metodo deve essere richiamato, che potrebbe essere quello della classe stessa, o quello della classe padre o quello ancora superiore.
Grazie ai meccanismi appena descritti, nel momento in cui qualcuno andasse ad aggiornare il codice, ad esempio perché si vuole aggiungere un nuovo dato in una certa classe, nella programmazione ad oggetti non è necessaria nessuna modifica al codice originale; basterà aggiungere il dato non nella classe (potendolo pure fare), ma in una nuova classe cosiddetta “specializzata” (rispetto alla classe padre), e cioè con le sole proprietà ed i metodi aggiuntivi rispetto a quelli già presenti nella classe padre (da cui eredita).
Così facendo le applicazioni precedenti non saranno oggetto di modifiche e continueranno a funzionare esattamente come prima. Nella programmazione a funzioni questo non è sempre possibile in quanto l’aggiunta di dati nel codice, può avere ricadute su tutte le funzioni che ne facevano uso.
Nella programmazione OO, un programma è un insieme di oggetti che comunicano tra di loro mandandosi dei messaggi, questi messaggi che consistono nella chiamata di un metodo, chiedendo all’oggetto che riceve il messaggio di rispondere facendo qualcosa.
Sostanzialmente ogni oggetto ha una propria memoria che può contenere dati primitivi e altri oggetti, questo permette di creare degli oggetti più complessi, partendo da oggetti semplici impacchettandoli in un nuovo oggetto.
Alla base vi è l’idea di far scrivere ai programmatori degli oggetti (classi) migliori, che poi possono essere riusati da tutti per costruire nuove applicazioni.
Java nasce proprio per questo scopo:
- garantire maggiore semplicità rispetto al C++, per la scrittura e la gestione del codice;
- permettere la realizzazione di programmi non legati ad una architettura precisa.
Il primo degli obbiettivi fu affrontato liberando il programmatore dall’onere della gestione della memoria (e togliendo dalle sue mani la gestione dei puntatori) creando il primo linguaggio, destinato alla grande diffusione, basato su un sistema di gestione della memoria chiamato gargbagecollection ; con questo sistema, la memoria viene automaticamente assegnata e rilasciata a seconda delle esigenze del programma.
Il secondo punto fu invece affrontato applicando il concetto di Macchina Virtuale (JVM: Java Virtual Machine). La JVM permette sostanzialmente di fare in modo che i programmi, non siano compilati in codice macchina (nativo), ma in una sorta di codice “intermedio” (chiamato bytecode); questo, non è destinato ad essere eseguito direttamente dall’hardware (come per il codice macchina) ma deve essere, a sua volta, interpretato da un secondo programma, la macchina virtuale, che si chiama così, appunto perchè simula l’hardware reale ma, è “solo” un software.
Ciò comporta che, lo stesso codice può essere eseguito su più piattaforme semplicemente trasferendo il bytecode purché sia disponibile una JVM. Il bytecode rappresenta quindi una sorta di programma eseguibile per la JVM e non per la macchina reale.
Ciò vuol dire che il programmatore non deve preoccuparsi di creare un programma eseguibile per ogni macchina reale ( e cioè un programma per PC con windows, uno per MAC, uno per Linux, uno per Android ecc.), ma solo uno per la JVM. Questo concetto è chiamato WORA, Write Once, Run anywhere ovvero: scrivi una volta e funziona ovunque.
Java con le sue librerie specializzate, fornisce migliaia di oggetti pronti per il riuso, il tutto non solo per facilitare il lavoro dei programmatori, ma anche per fornire un ambiente di lavoro identico su tutte le piattaforme. Imparare a programmare in Java significa innanzitutto conoscere queste librerie di classi.
In generale un programma viene realizzato nel seguente modo:
- si crea la classe;
- si definiscono le variabili;
- si definisce il costruttore;
- si definisce il metodo;
- si istanzia la classe;
- se necessario si richiedono i valori di input;
- se necessario si visualizza l’output degli argomenti;
- se necessario si applicano i metodi agli argomenti, ed eventualmente vengono visualizzati come output.
Esempio
Esempio di un semplice programma in Java:
public class Territorio // viene creata la classe Territorio { // vengono definite levariabili double superficie; int abitanti; // viene definito il costruttore Territorio(double s, int a) { superficie = s; abitanti = a; } // viene definito il metodo double densita() { return abitanti / superficie; } double popM(){ return abitanti * 0.45; } // viene definito il metodo main public static void main(String args[]) { // si istanzia la classe Territorio Territorio t = new Territorio(257.3, 12250); // si visualizzano i valori degli argomenti su console System.out.println(t.superficie); System.out.println(t.abitanti); System.out.println(t.densita()); /* di seguito viene applicato il metodo densità * all’oggetto t e visualizzato su console */ System.out.println(t.popM()); } }