In informatica, la programmazione orientata agli oggetti (, in acronimo OOP), a volte chiamata semplicemente programmazione a oggetti, è un paradigma di programmazione che permette di definire oggetti software in grado di interagire gli uni con gli altri attraverso lo scambio di messaggi.
Per "scambio di messaggi" s'intende la capacità degli oggetti di chiamare i metodi pubblici di altri oggetti, per esempio passandogli dati da elaborare e ricevendo il risultato della loro elaborazione. La OOP è particolarmente adatta nei contesti in cui si possono definire delle relazioni di interdipendenza tra i concetti da modellare (contenimento, uso, specializzazione). Un ambito che più di altri riesce a sfruttare i vantaggi della programmazione a oggetti è quello delle interfacce grafiche.
Tra gli altri vantaggi della programmazione orientata agli oggetti: fornisce un supporto naturale alla modellazione software degli oggetti del mondo reale o del modello astratto da riprodurre; permette una più facile gestione e manutenzione di progetti di grandi dimensioni; l'organizzazione del codice sotto forma di classi favorisce la modularità e il riuso di codice.
Storia Il concetto di classe può essere considerato un'estensione di quello di tipo di dato astratto, sviluppatosi inizialmente all'interno del paradigma della programmazione procedurale, che prevede la definizione da parte del programmatore di tipi di dato con cui si può interagire solo attraverso una interfaccia ben definita, nascondendo all'utilizzatore i dettagli dell'implementazione. I costrutti sintattici che permettono di definire una classe, nei linguaggi a oggetti, possono essere visti come un supporto strutturato per realizzare i dati astratti.
Il primo linguaggio di programmazione orientato agli oggetti fu il Simula (1967), seguito negli anni settanta da Smalltalk e da varie estensioni del Lisp. Negli anni ottanta sono state create estensioni orientate a oggetti del linguaggio C (C++, Objective-C e altri), e di altri linguaggi (Object Pascal). Negli anni novanta quello a oggetti è diventato il paradigma dominante, per cui gran parte dei linguaggi di programmazione erano o nativamente orientati agli oggetti o avevano una estensione in tal senso. Linguaggi che supportano solo il paradigma di programmazione orientata agli oggetti sono Smalltalk ed Eiffel. Più spesso si incontra una realizzazione non esclusiva del paradigma di programmazione orientata agli oggetti, come in C++, Java, Delphi, Python, C#, Visual Basic .NET, Perl, PHP (a partire dalla versione 4).
Descrizione La programmazione a oggetti prevede di raggruppare in alcune parti circoscritte del codice sorgente, chiamate classi, la dichiarazione delle strutture dati e delle procedure che operano su di esse. Le classi, quindi, costituiscono dei modelli astratti, che a tempo di esecuzione vengono invocate per istanziare o creare oggetti software relativi alla classe invocata. Questi ultimi sono dotati di attributi (dati) e metodi (procedure) secondo quanto definito/dichiarato dalle rispettive classi.
La parte del programma che fa uso di un oggetto si chiama client.
Un linguaggio di programmazione è definito a oggetti quando permette di implementare quattro meccanismi usando la sintassi nativa del linguaggioQuesti meccanismi possono essere simulati anche nei linguaggi che non sono considerati ad oggetti, a patto di adottare altri costrutti e di rispettare determinate convenzioni durante la scrittura del testo del programma.: incapsulamento: consiste nella separazione della cosiddetta interfaccia di una classe dalla corrispondente implementazione, in modo che i client di un oggetto di quella classe possano utilizzare la prima, ma non la seconda. ereditarietà: permette essenzialmente di definire delle classi a partire da altre già definite. polimorfismo: permette di scrivere un client che può servirsi di oggetti di classi diverse, ma dotati di una stessa interfaccia comune; nel tempo di esecuzione tale client potrà attivare comportamenti diversi senza conoscere a priori il tipo specifico dell'oggetto che gli viene dato in ingresso. Astrazione: Si tratta del processo di identificazione degli aspetti essenziali di una forma o concetto, astratti dalle specifiche realizzazioni. In OOP, significa definire le interfacce separate dalle implementazioni, consentendo al programmatore di lavorare con concetti ad alto livello senza preoccuparsi dei dettagli.
Classi
Le classi definiscono dei tipi di dato e permettono la creazione degli oggetti secondo le caratteristiche definite nella classe stessa. Grazie alle relazioni di ereditarietà, è possibile creare nuove classi a partire da quelle esistenti, estendendole con caratteristiche aggiuntive.
La classe è composta da: attributi (analoghi ai membri di un record), ossia variabili e/o costanti che definiscono le caratteristiche o proprietà degli oggetti istanziabili invocando la classe; i valori inizializzati degli attributi sono ottenuti attraverso il cosiddetto costruttore; metodi, ossia procedure che operano sugli attributi.
Un paragone (impreciso) con la matematica è il seguente: si può pensare che una classe definisca un insieme in modo intensivo, ovvero indicandone le caratteristiche invece che elencandone gli elementi, e che gli oggetti siano gli elementi di quell'insieme. Tuttavia, in matematica, il numero degli elementi è una caratteristica intrinseca dell'insieme stesso, e risulta definito nel momento in cui si definisce l'insieme, mentre in programmazione è possibile istanziare una classe un numero di volte arbitrario (teoricamente da zero a infinito, in pratica da zero fino a esaurimento della memoria di lavoro del calcolatore) e che dipende dall'esecuzione del programma. Per questo motivo, è preferibile considerare la classe come un modello astratto istanziabile.
In altri termini, una classe è paragonabile al progetto di un'infrastruttura che può poi essere messa in opera/esercizio ovvero realizzata o meno con l'istanziazione dei suoi oggetti tutti con le medesime caratteristiche, ovvero gli attributi (con valori diversi), su cui opereranno i metodi o funzioni.
Oggetti
Un oggetto è una istanza di una classe. Esso è dotato di tutti gli attributi e i metodi definiti dalla classe, e agisce come un fornitore di "messaggi" (i metodi) che il codice eseguibile del programma (procedure o altri oggetti) può attivare su richiesta. Inviare un messaggio a un oggetto significa, in gergo, invocare un metodo su quell'oggetto. Il metodo riceve come parametro (spesso implicito) l'oggetto su cui è stato invocato, che può essere referenziato tramite una parola-chiave o una sintassi apposita, anche se è passato come parametro implicito; per esempio, in C++, in Java, e in C# si usa la parola chiave this ( in PHP), mentre in Smalltalk, in Objective-C, Python e in Ruby si usa la parola chiave self.
Dal punto di vista del calcolatore, ogni oggetto è identificato da una certa zona di memoria, nella quale sono memorizzati gli attributi, e il valore di questi ultimi determina lo stato interno dell'oggetto. Istanziare un oggetto vuol dire allocare memoria ed eventualmente inizializzarla secondo le specifiche definite dalla classe. Molti linguaggi forniscono un supporto per l'inizializzazione automatica di un oggetto, con uno o più metodi speciali, detti costruttori. Analogamente, la fine della vita di un oggetto può essere gestita con un metodo detto distruttore.
Il codice eseguibile del programma accede a tale zona di memoria sempre e solo secondo le modalità definite dalla classe. Secondo il principio noto come information hiding, l'accesso ai campi di un oggetto deve essere permesso solo tramite metodi invocati su quello stesso oggetto. Il vantaggio principale è che il controllo completo sullo stato interno viene assegnato a una zona ristretta del codice eseguibile del programma (la classe), perché il codice esterno non è autorizzato a modificarlo. In tal caso, risulta possibile imporre dei vincoli sui possibili valori che la tupla degli attributi può o non può assumere, e anche sulle possibili transizioni tra questi stati. Un oggetto quindi può essere visto come una macchina a stati finiti.
Incapsulamento
L'incapsulamento è la proprietà per cui i dati che definiscono lo stato interno di un oggetto e i metodi che ne definiscono la logica sono accessibili ai metodi dell'oggetto stesso, mentre non sono visibili ai client. Per alterare lo stato interno dell'oggetto, è necessario invocarne i metodi pubblici, ed è questo lo scopo principale dell'incapsulamento. Infatti, se gestito opportunamente, esso permette di vedere l'oggetto come una black-box, cioè una "scatola nera" con la quale l'interazione avviene solo e solamente tramite i metodi definiti dall'interfaccia. Il punto è dare delle funzionalità agli utenti nascondendo i dettagli legati alla loro implementazione. Un esempio potrebbe essere un oggetto "matematica" che, tra le tante operazioni che fornisce, ne ha una che moltiplica due numeri. Per esempio moltiplica(2,3) restituisce 6. L'algoritmo di moltiplicazione può essere uno dei tanti algoritmi esistenti, però questo per chi chiama il metodo moltiplica() non fa nessuna differenza. L'importante è che il risultato non sia errato.
Ereditarietà
Il meccanismo dell'ereditarietà è utilizzato in fase di strutturazione/definizione/pianificazione del software o in successive estensioni e permette di derivare nuove classi a partire da quelle già definite realizzando una gerarchia di classi. Una classe derivata attraverso l'ereditarietà (sottoclasse o classe figlia) mantiene i metodi e gli attributi delle classi da cui deriva (classi base, superclassi o classi madre); inoltre, può definire i propri metodi o attributi, e ridefinire il codice di alcuni dei metodi ereditati tramite un meccanismo chiamato overriding.
Quando una classe eredita da una sola superclasse si parla di eredità singola; viceversa, si parla di eredità multipla. Alcuni linguaggi (tra gli altri, Java, Smalltalk) forniscono supporto esclusivo all'ereditarietà singola. Altri (C++, Python) prevedono anche l'ereditarietà multipla.
Alcuni linguaggi tipizzati (Java, C#) prevedono l'ereditarietà delle interfacce, oltre che delle classi.
L'ereditarietà può essere usata come meccanismo per ottenere l'estensibilità e il riuso del codice, e risulta particolarmente vantaggiosa quando viene usata per definire sottotipi, sfruttando le relazioni is-a esistenti nella realtà di cui la struttura delle classi è una modellizzazione. Oltre all'evidente riuso del codice della superclasse, l'ereditarietà permette la definizione di codice generico attraverso il meccanismo del polimorfismo.
Esempio Se nel programma esiste già una classe MezzoDiTrasporto che ha come proprietà i dati di posizione, velocità, destinazione e carico utile, e occorre una nuova classe Aereo, è possibile crearla direttamente dall'oggetto MezzoDiTrasporto dichiarando una classe di tipo Aereo che eredita da MezzoDiTrasporto e aggiungendovi anche l'attributo che identifica la quota di crociera, con il vantaggio che la nuova classe acquisirà tutti i membri definiti in MezzoDiTrasporto per il fatto stesso di esserne sottoclasse.
Sottotipizzazione
Sebbene concettualmente esistano delle differenze ben marcate, il meccanismo di eredità fra classi (subclassing), attraverso il meccanismo del polimorfismo per inclusione, permette nei linguaggi a oggetti di modellare l'eredità fra tipi (subtyping).
Secondo il principio di sostituzione di Liskov, un tipo S è un sottotipo di T quando è possibile sostituire tutte le istanze di T con delle istanze di S mantenendo intatto il funzionamento del programma.