Ininformatica, laprogrammazione orientata agli oggetti (in ingleseobject-oriented programming, inacronimoOOP), a volte chiamata semplicementeprogrammazione a oggetti, è unparadigma di programmazione che permette di definireoggettisoftware 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 delleinterfacce grafiche.
Tra gli altri vantaggi della programmazione orientata agli oggetti:
fornisce un supporto naturale alla modellazionesoftware degli oggetti del mondo reale o del modello astratto da riprodurre;
permette una più facile gestione e manutenzione di progetti di grandi dimensioni;
Il concetto di classe può essere considerato un'estensione di quello ditipo di dato astratto, sviluppatosi inizialmente all'interno del paradigma dellaprogrammazione procedurale, che prevede la definizione da parte del programmatore ditipi di dato con cui si può interagire solo attraverso unainterfaccia 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 ilSimula (1967), seguito neglianni settanta daSmalltalk e da varie estensioni delLisp. Neglianni ottanta sono state create estensioni orientate a oggetti dellinguaggio C (C++,Objective-C e altri), e di altri linguaggi (Object Pascal). Neglianni 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 edEiffel.Più spesso si incontra una realizzazione non esclusiva del paradigma di programmazione orientata agli oggetti, come inC++,Java,Delphi,Python,C#,Visual Basic .NET,Perl,PHP (a partire dalla versione 4).
La programmazione a oggetti prevede di raggruppare in alcune parti circoscritte delcodice sorgente, chiamateclassi, la dichiarazione dellestrutture dati e delleprocedure che operano su di esse. Le classi, quindi, costituiscono dei modelli astratti, che atempo di esecuzione vengono invocate per istanziare o creareoggetti software relativi alla classe invocata. Questi ultimi sono dotati di attributi (dati) emetodi (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 linguaggio[1]:
incapsulamento: consiste nella separazione della cosiddettainterfaccia 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 stessainterfaccia 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.
Le classi definiscono deitipi di dato e permettono la creazione degli oggetti secondo le caratteristiche definite nella classe stessa. Grazie alle relazioni diereditarietà, è possibile creare nuove classi a partire da quelle esistenti, estendendole con caratteristiche aggiuntive.
La classe è composta da:
attributi (analoghi ai membri di unrecord), ossiavariabili e/ocostanti che definiscono le caratteristiche o proprietà degli oggetti istanziabili invocando la classe; i valori inizializzati degli attributi sono ottenuti attraverso il cosiddettocostruttore;
metodi, ossiaprocedure che operano sugli attributi.
Un paragone (impreciso) con lamatematica è il seguente: si può pensare che una classe definisca uninsieme 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, ilnumero 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 dellamemoria 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 alprogetto 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.
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, inC++, inJava, e inC# si usa laparola chiavethis ($this in PHP), mentre inSmalltalk, inObjective-C,Python e inRuby si usa la parola chiaveself.
Dal punto di vista del calcolatore, ogni oggetto è identificato da una certazona di memoria, nella quale sono memorizzati gli attributi, e il valore di questi ultimi determina lo stato interno dell'oggetto. Istanziare un oggetto vuol direallocare 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, detticostruttori. Analogamente, la fine della vita di un oggetto può essere gestita con un metodo dettodistruttore.
Il codice eseguibile del programma accede a tale zona di memoria sempre e solo secondo le modalità definite dalla classe. Secondo il principio noto comeinformation 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 latupla degli attributi può o non può assumere, e anche sulle possibili transizioni tra questi stati. Un oggetto quindi può essere visto come unamacchina a stati finiti.
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 unablack-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 esempiomoltiplica(2,3) restituisce 6. L'algoritmo di moltiplicazione può essere uno dei tanti algoritmi esistenti, però questo per chi chiama il metodomoltiplica() non fa nessuna differenza. L'importante è che il risultato non sia errato.
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 chiamatooverriding.
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 definiresottotipi, sfruttando le relazioniis-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 classeMezzoDiTrasporto che ha come proprietà i dati di posizione, velocità, destinazione e carico utile, e occorre una nuova classeAereo, è possibile crearla direttamente dall'oggettoMezzoDiTrasporto dichiarando una classe di tipoAereo che eredita daMezzoDiTrasporto e aggiungendovi anche l'attributo che identifica laquota di crociera, con il vantaggio che la nuova classe acquisirà tutti i membri definiti inMezzoDiTrasporto per il fatto stesso di esserne sottoclasse.
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 ilprincipio di sostituzione di Liskov, un tipoS è un sottotipo diT quando è possibile sostituire tutte le istanze diT con delle istanze diS mantenendo intatto il funzionamento del programma.
Con gli opportuni accorgimenti è possibile creare una relazione di classe-sottoclasse che rispetti anche i vincoli della relazione tipo-sottotipo. Da un punto di vista sintattico, ciò richiede che tutti i metodi della superclasse siano presenti nella sottoclasse, e che le rispettive firme siano compatibili. Di conseguenza, una sottoclasse che voglia definire un sottotipo può ridefinire i metodi della superclasse, ma non può eliminarli. La firma dei metodi può essere solo parzialmente modificata, rispettando dei vincoli sulla variazione dei parametri rispetto alla catena di eredità.
Tuttavia, il rispetto delle restrizioni sintattiche non è sufficiente, da solo, ad assicurare il rispetto della condizione di Liskov: laridefinizione dei metodi o lariassegnazione degli attributi[non chiaro], infatti, potrebbe compromettere la compatibilità in tempo di esecuzione.
In molti linguaggi, nella definizione di una sottoclasse, si può decidere di eliminare o cambiare le proprietà di accesso a un metodo ereditato. In tal caso, l'operazione disubclassing non corrisponde a quella disubtyping. Alcuni linguaggi a oggetti, in particolareSather, dividono esplicitamente a livello sintatticosubclassing esubtyping.
In linguaggi contipizzazione statica edesplicita, la sottotipizzazione viene supportata attraverso il polimorfismo per inclusione delle sottoclassi: una stessa variabile può fare riferimento a un oggetto del tipo per cui è stata dichiarata, o di tipi da esso derivati. Il tipo dell'oggetto individuato dalla variabile, quindi, viene definito aruntime (binding dinamico), e può essere modificato durante l'esecuzione del programma.
Nella programmazione a oggetti, con il nome di polimorfismo per inclusione si indica il fatto che lo stesso codice eseguibile può essere utilizzato con istanze di classi diverse, aventi una superclasse comune.
Ilpolimorfismo è particolarmente utile quando la versione del metodo da eseguire viene scelta sulla base del tipo di oggetto effettivamente contenuto in una variabile aruntime (invece che al momento dellacompilazione). Questa funzionalità è dettabinding dinamico (olate-binding), ed è supportato dai più diffusi linguaggi di programmazione a oggetti.
Se una variabile di tipoA ha due sottotipi (sottoclassi)B eC, che ridefiniscono entrambe il metodom(), l'oggetto contenuto nella variabile potrà essere di tipoA,B oC, e quando sulla variabile viene invocato il metodom() viene eseguita la versione appropriata per il tipo di oggetto contenuto nella variabile in quel momento.
Per ritornare all'esempio precedente, si suppone che unAereo debba affrontare procedure per l'arrivo e la partenza molto più complesse di quelle di un normale camion, come in effetti è: allora le procedurearrivo() epartenza() devono essere cambiate rispetto a quelle della classe baseMezzoDiTrasporto. Si provvede quindi a ridefinirle nella classeAereo in modo da ottenere il comportamento necessario (polimorfismo): a questo punto, dalla lista di mezzi è possibile prendere qualsiasi mezzo e chiamarearrivo() opartenza() senza più doversi preoccupare di che cos'è l'oggetto che si sta maneggiando: che sia un mezzo normale o un aereo, si comporterà rispondendo alla stessa chiamata sempre nel modo giusto.
Il supporto albinding dinamico non è necessariamente automatico in tutti i linguaggi di programmazione a oggetti. Ad esempio in Java, Smalltalk, Python, Ruby, ilbinding dinamico è implicitamente usato come comportamento di default nelle classi polimorfe, mentre in C++ viene abilitato inserendo la parola riservatavirtual nellafirma del metodo interessato.
Il supportoruntime di una chiamata di metodo polimorfa richiede che a una variabile polimorfa venga associato unmetadato implicito che contiene il tipo del dato contenuto nella variabile in un dato momento, oppure la tabella delle funzioni polimorfe.
Si noti chefaiQualcosaDiImportante è un metodo definito daClasse1 ma potrebbe essere stato ridefinito inClasse2 (perché magari deve essere stampato in maniera diversa). Il compilatore non può sapere a tempo di compilazione sec è un oggetto associato alla classeClasse1 oClasse2. Tramite il binding dinamico la scelta su quale metodo effettivamentelinkare sarà effettuata durante l'esecuzione (runtime), quando il metodo viene effettivamente chiamato.
Questa voce o sezione sull'argomento informatica è ritenutada controllare.
Motivo:Sezione dilettantesca, priva di fonti. La scelta dei "problemi" dei linguaggi OOP sembra assolutamente arbitraria e discutibile. Da sostituire con una sezione sulle critiche al paradigma basata su fonti secondarie.
Alcuni meccanismi inclusi nella gestione degli oggetti causano un overhead in termini di tempo e memoria, che in determinate situazioni può portare a problemi di efficienza.
Alcuni linguaggi comeJava eC++ preferiscono un approccio ibrido rispetto all'OOP "puro", ad esempio del linguaggioSmalltalk, prevedendo che alcuni tipi di dati primitivi non siano considerati come oggetti. I vantaggi di quest'approccio sono particolarmente evidenti in presenza di computazioni numeriche; di contro, ogni volta che è necessario un oggetto in luogo di un tipo primitivo è necessario ricorrere a un appositowrapper, e questo può portare a cali prestazionali.
Tale wrapper può anche essere dato automaticamente dal linguaggio stesso, come nel caso del Java o delC#, tramite una conversione automatica chiamataautoboxing (inscatolamento automatico). Essa permette di mettere valori di tipi primitivi — per esempio interi o caratteri — all'interno di oggetti, quindi di richiamare parti di codice che vorrebbero oggetti senza scrivere esplicitamente l'istruzione di creazione dell'oggetto che racchiude il valore. Tale inscatolamento risolve il problema di scrivere unwrapper a mano, ma ovviamente il calo di prestazioni rimane.
Altre critiche all'OOP riguardano la maggiore complessità strutturale rispetto ai linguaggi procedurali, a fronte delle limitazioni introdotte per seguire il paradigma a oggetti. Esistono inoltre alcune carenze concettuali (in particolare riguardo alla sottotipizzazione), che aggiunte alle diverse implementazioni nei linguaggi possono causare problemi al programmatore. Ad esempio il funzionamento in contesti polimorfi non è garantito quando i metodi vengano ridefiniti in una catena di eredità. Inoltre cambiare le definizioni nelle classi base può portare a introdurre errori in cascata nelle sottoclassi (problema della classe base fragile).
^Questi 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.