La pro­gram­ma­zio­ne orientata agli oggetti (OOP) ha trovato ap­pli­ca­zio­ne ovunque: questo genere di tec­no­lo­gie è infatti impiegato sia per scrivere sistemi operativi così come software com­mer­cia­li e open source. Tuttavia, solo quando un progetto raggiunge un certo livello di com­ples­si­tà i vantaggi dell’OOP diventano evidenti. In questo senso, lo stile di pro­gram­ma­zio­ne orientato agli oggetti è ancora uno dei paradigmi di pro­gram­ma­zio­ne pre­do­mi­nan­ti.

Che cos’è la pro­gram­ma­zio­ne orientata agli oggetti e a cosa serve?

Il termine “pro­gram­ma­zio­ne orientata agli oggetti” è stato coniato verso la fine degli anni ‘60 da Alan Kay, figura leg­gen­da­ria nell’ambito della pro­gram­ma­zio­ne. Kay ha col­la­bo­ra­to allo sviluppo di Smalltalk, il pio­nie­ri­sti­co lin­guag­gio di pro­gram­ma­zio­ne orientato agli oggetti ispirato a Simula, il primo lin­guag­gio in assoluto ad avere ca­rat­te­ri­sti­che OOP. Il concetto di base di Smalltalk esercita ancora oggi un’influenza sulle fun­zio­na­li­tà OOP dei moderni linguaggi di pro­gram­ma­zio­ne. Tra i linguaggi in­fluen­za­ti da Smalltalk figurano Ruby, Python, Go e Swift.

La pro­gram­ma­zio­ne orientata agli oggetti rap­pre­sen­ta uno dei paradigmi di pro­gram­ma­zio­ne pre­do­mi­nan­ti, al pari della popolare pro­gram­ma­zio­ne fun­zio­na­le (FP). In generale, gli approcci alla pro­gram­ma­zio­ne sono clas­si­fi­ca­bi­li in due correnti prin­ci­pa­li: “im­pe­ra­ti­va” e “di­chia­ra­ti­va”. OOP è una variante dello stile di pro­gram­ma­zio­ne im­pe­ra­ti­vo e nello specifico è un ulteriore sviluppo della pro­gram­ma­zio­ne pro­ce­du­ra­le:

  1. Pro­gram­ma­zio­ne im­pe­ra­ti­va: de­scri­zio­ne delle singole fasi di ri­so­lu­zio­ne di un problema. Esempio: algoritmo
  • Pro­gram­ma­zio­ne strut­tu­ra­ta
    • Pro­gram­ma­zio­ne pro­ce­du­ra­le
      • Pro­gram­ma­zio­ne orientata agli oggetti
  1. Pro­gram­ma­zio­ne di­chia­ra­ti­va: ge­ne­ra­zio­ne di risultati secondo de­ter­mi­na­te regole. Esempio: query SQL
  • Pro­gram­ma­zio­ne fun­zio­na­le
  • Lin­guag­gio di dominio specifico
N.B.

I termini “procedura” e “funzione” sono spesso usati come sinonimi. Questo perché si tratta di blocchi di codice ese­gui­bi­li che possono accettare argomenti. In realtà, le funzioni re­sti­tui­sco­no un valore, mentre le procedure non lo fanno. Non tutti i linguaggi offrono un supporto esplicito per le procedure.

Teo­ri­ca­men­te, qualsiasi problema di pro­gram­ma­zio­ne può essere risolto con uno qualsiasi dei paradigmi, dato che tutti i paradigmi sono “Turing completi”. Di con­se­guen­za, l’elemento limitante non è la macchina, ma l’essere umano. Chi programma in­di­vi­dual­men­te o un team di pro­gram­ma­zio­ne è in grado di gestire una quantità limitata di com­ples­si­tà. Perciò per la gestione di com­ples­si­tà è normale ricorrere a delle astra­zio­ni. In base all’area di ap­pli­ca­zio­ne e al problema, si presta meglio l’uno o l’altro stile di pro­gram­ma­zio­ne.

Gran parte dei linguaggi moderni sono co­sid­det­ti linguaggi multi-paradigma, cioè con­sen­to­no di pro­gram­ma­re in diversi stili di pro­gram­ma­zio­ne. Viceversa, esistono linguaggi che sup­por­ta­no un solo stile di pro­gram­ma­zio­ne. Ciò vale so­prat­tut­to per i linguaggi stret­ta­men­te fun­zio­na­li come Haskell:

Paradigma Ca­rat­te­ri­sti­che Par­ti­co­lar­men­te adatto a Linguaggi
Im­pe­ra­ti­vo OOP Oggetti, classi, metodi, eredità, po­li­mor­fi­smo Mo­del­la­zio­ne, pro­get­ta­zio­ne di sistemi Smalltalk, Java, Ruby, Python, Swift
Im­pe­ra­ti­vo Pro­ce­du­ra­le Flusso di controllo, ite­ra­zio­ne, procedure/funzioni Ela­bo­ra­zio­ne se­quen­zia­le dei dati C, Pascal, Basic
Di­chia­ra­ti­vo Fun­zio­na­le Im­mu­ta­bi­li­tà, funzioni pure, lambda calcolo, ri­cor­sio­ne, sistemi di tipo Ela­bo­ra­zio­ne parallela dei dati, ap­pli­ca­zio­ni ma­te­ma­ti­che e scien­ti­fi­che, parser e com­pi­la­to­ri Lisp, Haskell, Clojure
Di­chia­ra­ti­vo Lin­guag­gio di dominio specifico (DSL) Lin­guag­gio espres­si­vo e ampio Ap­pli­ca­zio­ni di dominio spe­ci­fi­che SQL, CSS
N.B.

Anche il CSS è un lin­guag­gio Turing completo. In sostanza, significa che i calcoli scritti in altri linguaggi possono essere risolti anche mediante CSS.

La pro­gram­ma­zio­ne orientata agli oggetti rientra nella pro­gram­ma­zio­ne im­pe­ra­ti­va ma nasce dalla pro­gram­ma­zio­ne pro­ce­du­ra­le. Quest’ultima opera prin­ci­pal­men­te con dati inerti che vengono elaborati da un codice ese­gui­bi­le:

  1. Dati: valori, strutture di dati, variabili
  2. Codice: espres­sio­ni, strutture di controllo, funzioni

La dif­fe­ren­za tra la pro­gram­ma­zio­ne orientata agli oggetti e quella pro­ce­du­ra­le è proprio questa: OOP raggruppa dati e funzioni in oggetti. So­stan­zial­men­te, un oggetto è una struttura dati vivente, in quanto gli oggetti non sono inerti, ma sono dotati di un com­por­ta­men­to. Si tratta quindi di oggetti pa­ra­go­na­bi­li a macchine o a organismi uni­cel­lu­la­ri. I dati vengono sem­pli­ce­men­te gestiti, invece con gli oggetti si in­te­ra­gi­sce o, meglio, gli oggetti in­te­ra­gi­sco­no tra di loro.

Ci serviamo di un esempio per com­pren­de­re meglio questa dif­fe­ren­za. Una variabile Integer in Java o C++ può contenere un solo valore. Non si tratta di una struttura di dati, ma di un “tipo primitivo” o “primitive”:

int number = 42;
Java

Le ope­ra­zio­ni sui primitive si ef­fet­tua­no mediante operatori o funzioni definite ester­na­men­te. Di seguito, l’esempio della funzione successor, la quale re­sti­tui­sce il numero suc­ces­si­vo a un numero intero:

int successor(int number) {
    return number + 1;
}
// returns `43`
successor(42)
Java

In linguaggi come Python e Ruby, di contro, “eve­ry­thing is an object” (tutto è un oggetto). Un semplice numero include il valore effettivo e un insieme di metodi che de­fi­ni­sco­no le ope­ra­zio­ni sul valore. Ri­por­tia­mo l’esempio della funzione succ in­cor­po­ra­ta in Ruby:

# returns `43`
42.succ
Ruby

In primo luogo, è pratico perché le fun­zio­na­li­tà di un tipo di dati sono rag­grup­pa­te. Non è possibile ri­chia­ma­re un metodo se non cor­ri­spon­de al tipo. Il metodo, però, può fare ancora di più. In Ruby, il ciclo For è rea­liz­za­to come metodo di un numero. Prendiamo ad esempio i numeri da 51 a 42:

51.downto(42) { |n| print n, ".. " }
Ruby

Ma da dove pro­ven­go­no i metodi? La maggior parte dei linguaggi definisce gli oggetti at­tra­ver­so le classi. Gli oggetti sono istan­zia­ti da classi e quindi vengono chiamati anche istanze. Per classe si intende un modello per la creazione di oggetti simili che hanno gli stessi metodi. Quindi, nei linguaggi OOP puri, le classi agiscono come tipi. Tale aspetto emerge chia­ra­men­te nella pro­gram­ma­zio­ne orientata agli oggetti in Python; infatti, la funzione type re­sti­tui­sce una classe come tipo di un valore:

type(42) # <class 'int'>
type('Walter White') # <class 'str'>
Python

Come funziona la pro­gram­ma­zio­ne orientata agli oggetti?

Se si chiede a una persona con un po’ di espe­rien­za nella pro­gram­ma­zio­ne che cosa sia OOP, pro­ba­bil­men­te la sua risposta sarà un vago: “qualcosa sulle classi”. A dire il vero, però, le classi non sono il punto centrale della questione. Il concetto di base della pro­gram­ma­zio­ne orientata agli oggetti di Alan Kay è più semplice e si può rias­su­me­re così:

  1. Gli oggetti in­cap­su­la­no il proprio stato interno.
  2. Gli oggetti ricevono messaggi tramite i propri metodi.
  3. L’as­se­gna­zio­ne dei metodi avviene di­na­mi­ca­men­te in fase di ese­cu­zio­ne.

Ap­pro­fon­dia­mo di seguito questi tre punti critici.

Gli oggetti in­cap­su­la­no il proprio stato interno

Al fine di com­pren­de­re cosa si intenda per in­cap­su­la­men­to, adottiamo l’esempio di un’au­to­mo­bi­le. Un’auto ha un de­ter­mi­na­to stato, stabilito ad esempio dalla per­cen­tua­le di carica della batteria, dal livello di car­bu­ran­te nel serbatoio, dal fun­zio­na­men­to o meno del motore. Rap­pre­sen­tan­do un’auto di questo tipo come un oggetto, le proprietà interne do­vreb­be­ro poter essere mo­di­fi­ca­te solo tramite in­ter­fac­ce definite.

Os­ser­via­mo alcuni esempi. Il nostro oggetto car rap­pre­sen­ta un’au­to­mo­bi­le. Al suo interno, lo stato è me­mo­riz­za­to in variabili. Questo oggetto controlla i valori delle variabili; ad esempio, possiamo as­si­cu­rar­ci che l’energia venga uti­liz­za­ta per avviare il motore. Mettiamo in moto l’auto inviando un messaggio di start:

car.start()
Python

In questo momento, è l’oggetto a decidere cosa accadrà: qualora il motore sia già in funzione, il messaggio viene ignorato, oppure viene emesso un nuovo messaggio. In assenza di carica suf­fi­cien­te della batteria o di serbatoio vuoto, il motore rimane spento. Quando tutte le con­di­zio­ni sono sod­di­sfat­te, il motore fi­nal­men­te si avvia e lo stato interno viene adattato. Per esempio, una variabile booleana motor_running viene impostata su “True” e la carica della batteria di­mi­nui­sce in base alla carica ne­ces­sa­ria per l’av­via­men­to. Il­lu­stria­mo sche­ma­ti­ca­men­te come potrebbe apparire il codice all’interno dell’oggetto:

# starting car
motor_running = True
battery_charge -= start_charge
Python

L’im­por­tan­te è che lo stato interno non possa essere mo­di­fi­ca­to di­ret­ta­men­te dall’esterno. Di­ver­sa­men­te, si potrebbe impostare motor_running su “True” anche se la batteria è scarica. Tuttavia, ciò non ri­spec­chie­reb­be la si­tua­zio­ne reale.

Inviare messaggi/ri­chia­ma­re metodi

Gli oggetti, come appena osservato, rea­gi­sco­no ai messaggi e come reazione possono cambiare il proprio stato interno. Questi messaggi vengono chiamati metodi; dal punto di vista tecnico, si tratta di funzioni legate a un oggetto. Il messaggio comprende il nome del metodo ed even­tual­men­te altri argomenti. Un oggetto che riceve è chiamato ri­ce­vi­to­re (“receiver” in inglese). Possiamo rias­su­me­re lo schema generale della ricezione dei messaggi da parte degli oggetti nel modo seguente:

# call a method
receiver.method(args)
Python

Facciamo un altro esempio: sup­po­nia­mo di pro­gram­ma­re uno smart­pho­ne. I diversi oggetti rap­pre­sen­ta­no le fun­zio­na­li­tà, ad esempio le funzioni del telefono come la torcia, una chiamata, un messaggio di testo, ecc. In genere, i singoli sot­to­com­po­nen­ti sono a loro volta modellati come oggetti. Di con­se­guen­za, la rubrica è un oggetto, proprio come ogni contatto che contiene e anche il numero di telefono di un contatto. Questo permette di modellare fa­cil­men­te i processi a partire dalla realtà:

# find a person in our address book
person = contacts.find('Walter White')
# let's call that person's work number
call = phone.call(person.phoneNumber('Work'))
...
# after some time, hang up the phone
call.hangUp()
Python

As­se­gna­zio­ne dinamica dei metodi

La terza ca­rat­te­ri­sti­ca es­sen­zia­le della de­fi­ni­zio­ne originale di OOP di Alan Kay è l’as­se­gna­zio­ne dinamica dei metodi nel tempo di ese­cu­zio­ne. In altre parole, la decisione su quale codice eseguire quando viene richiesto un metodo si verifica soltanto al momento dell’ese­cu­zio­ne del programma. Il com­por­ta­men­to di un oggetto può quindi essere mo­di­fi­ca­to in questo frangente.

L’as­se­gna­zio­ne dinamica dei metodi comporta notevoli con­se­guen­ze per l’im­ple­men­ta­zio­ne tecnica delle fun­zio­na­li­tà OOP nei linguaggi di pro­gram­ma­zio­ne. Nella pratica, raramente occorre averci a che fare. A ogni modo, ana­liz­zia­mo un esempio. Mo­del­lia­mo la torcia dello smart­pho­ne come un oggetto fla­shlight. Esso reagisce ai messaggi on, off e intensity:

// turn on flashlight
flashlight.on()
// set flashlight intensity to 50%
flashlight.intensity(50)
// turn off flashlight
flashlight.off()
Ja­va­Script

Poniamo che la torcia si rompa e decidiamo di emettere un avviso a ogni accesso. Uno degli approcci consiste nel so­sti­tui­re tutti i metodi con un nuovo metodo. In Ja­va­Script, ad esempio, è ab­ba­stan­za semplice. Definiamo la nuova funzione out_of_order e so­vra­scri­via­mo i metodi esistenti con essa:

function out_of_order() {
    console.log('Flashlight out of order. Please service phone.')
    return false;
}
flashlight.on = out_of_order;
flashlight.off = out_of_order;
flashlight.intensity = out_of_order;
Ja­va­Script

In seguito, ogni volta che si tenta di in­te­ra­gi­re con la torcia, sarà sempre attivato il comando out_of_order:

// calls `out_of_order()`
flashlight.on()
// calls `out_of_order()`
flashlight.intensity(50)
// calls `out_of_order()`
flashlight.off()
Ja­va­Script

Da dove pro­ven­go­no gli oggetti? Istan­zia­zio­ne e ini­zia­liz­za­zio­ne

Abbiamo visto finora come gli oggetti ricevono i messaggi e rea­gi­sco­no a essi. Da dove pro­ven­go­no però gli oggetti? Passiamo ora ad ana­liz­za­re il concetto centrale di istan­zia­zio­ne. L’istan­zia­zio­ne è il processo tramite il quale un oggetto prende vita. Nei vari linguaggi OOP esistono diversi mec­ca­ni­smi di istan­zia­zio­ne. So­li­ta­men­te si ricorre a uno o più dei seguenti mec­ca­ni­smi:

  1. De­fi­ni­zio­ne per oggetto letterale
  2. Istan­zia­zio­ne con funzione co­strut­to­re
  3. Istan­zia­zio­ne da una classe

Sotto questo aspetto, Ja­va­Script spicca perché oggetti quali numeri o stringhe possono essere definiti di­ret­ta­men­te come letterali. Per fare un esempio semplice: istan­zia­mo un oggetto person vuoto e poi gli as­se­gnia­mo la proprietà name e un metodo greet. Da questo momento in poi, il nostro oggetto sarà in grado di salutare un’altra persona e di pro­nun­cia­re il proprio nome:

// instantiate empty object
let person = {};
// assign object property
person.name = "Jack";
// assign method
person.greet = function(other) {
    return `"Hi ${other}, I'm ${this.name}"`
};
// let's test
person.greet("Jim")
Ja­va­Script

Abbiamo istan­zia­to un oggetto unico. In ogni caso, è frequente che si voglia ripetere l’istan­zia­zio­ne per creare una serie di oggetti simili. Anche questa evenienza può essere fa­cil­men­te af­fron­ta­ta in Ja­va­Script. Generiamo una co­sid­det­ta funzione co­strut­to­re che assembla un oggetto quando viene in­ter­pel­la­ta. La nostra funzione co­strut­to­re, chiamata Person, assume un nome e un’età e crea un nuovo oggetto quando in­ter­pel­la­ta:

function Person(name, age) {
    this.name = name;
    this.age = age;
    
    this.introduce_self = function() {
        return `"I'm ${this.name}, ${this.age} years old."`
    }
}
// instantiate person
person = new Person('Walter White', 42)
// let person introduce themselves
person.introduce_self()
Ja­va­Script

Notate l’uso della parola chiave this, una ca­rat­te­ri­sti­ca presente anche in altri linguaggi come Java, PHP e C++, e che spesso è causa di con­fu­sio­ne per chi è alle prime armi con OOP. In poche parole, si tratta di un se­gna­po­sto per un oggetto istan­zia­to. Al momento dell’ese­cu­zio­ne di un metodo,thisfa ri­fe­ri­men­to al ri­ce­vi­to­re, indicando un’istanza specifica dell’oggetto. In altri linguaggi, come Python e Ruby, al posto dithissi usa la parola chiaveself, con la medesima funzione.

Inoltre, in Ja­va­Script è ne­ces­sa­ria la parola chiave newper creare cor­ret­ta­men­te l’istanza dell’oggetto. Questo si verifica so­prat­tut­to in Java e C++, che di­stin­guo­no tra “stack” e “heap” per la me­mo­riz­za­zio­ne dei valori in memoria. Il terminenew viene uti­liz­za­to in entrambi i linguaggi per di­stri­bui­re memoria su heap. Ja­va­Script, come Python, memorizza tutti i valori in heap, per cui new diventa superfluo. Python dimostra che è possibile farne a meno.

La terza e più diffusa modalità di creazione di istanze di oggetti prevede l’uso di classi. La classe gioca un ruolo simile a quello di un co­strut­to­re in Ja­va­Script: sono entrambi un modello che permette di istan­zia­re oggetti simili in caso di necessità. Al tempo stesso, in linguaggi come Python e Ruby, le classi so­sti­tui­sco­no i tipi uti­liz­za­ti in altri linguaggi. Più avanti ri­por­te­re­mo un esempio di classe.

Quali sono i vantaggi e gli svantaggi di OOP?

A partire dall’inizio del XXI secolo, la pro­gram­ma­zio­ne orientata agli oggetti ha subito un crescente numero di critiche. Linguaggi moderni e fun­zio­na­li con im­mu­ta­bi­li­tà e sistemi di tipi forti sono con­si­de­ra­ti più stabili, af­fi­da­bi­li e per­for­man­ti. No­no­stan­te ciò, OOP trova largo impiego e presenta notevoli vantaggi. L’im­por­tan­te è scegliere lo strumento giusto per ogni problema, invece di affidarsi a un’unica me­to­do­lo­gia.

Vantaggio: in­cap­su­la­men­to

OOP offre un vantaggio evidente: il rag­grup­pa­men­to delle fun­zio­na­li­tà. Anziché rag­grup­pa­re diverse variabili e funzioni in un insieme di­sor­di­na­to, le si può combinare in unità coerenti. La dif­fe­ren­za è di­mo­stra­ta da un esempio: prendiamo a modello un autobus e uti­liz­zia­mo due variabili e una funzione. Le persone possono salire a bordo dell’autobus fino a quando non è pieno:

# list to hold the passengers
bus_passengers = []
# maximum number of passengers
bus_capacity = 12
# add another passenger
def take_bus(passenger)
    if len(bus_passengers) < bus_capacity:
        bus_passengers.append(passenger)
    else:
        raise Exception("Bus is full")
Python

Anche se questo codice funziona, è pro­ble­ma­ti­co. La funzione take_bus accede alle variabili bus_pas­sen­gers e bus_capacity senza tra­smet­ter­le come argomenti. Ciò comporta problemi con il codice esteso, dato che le variabili devono essere fornite glo­bal­men­te o passate a ogni richiesta. Per di più, così è possibile “barare”. Infatti, possiamo con­ti­nua­re ad ag­giun­ge­re pas­seg­ge­ri all’autobus anche se di fatto è pieno:

# bus is full
assert len(bus_passengers) == bus_capacity
# will raise exception, won't add passenger
take_bus(passenger)
# we cheat, adding an additional passenger directly
bus_passengers.append(passenger)
# now bus is over capacity
assert len(bus_passengers) > bus_capacity
Python

Nulla ci impedisce, inoltre, di aumentare la capacità dell’autobus. Ciò viola però le ipotesi sulla realtà fisica, perché la capacità fisica di un autobus esistente è limitata e non può essere mo­di­fi­ca­ta a pia­ci­men­to:

# can't do this in reality
bus_capacity += 1
Python

In­cap­su­la­re lo stato interno degli oggetti protegge da modifiche insensate o in­de­si­de­ra­te. Ri­por­tia­mo la stessa fun­zio­na­li­tà nel codice orientato agli oggetti. Sta­bi­lia­mo una classe di autobus e istan­zia­mo un autobus a capacità limitata. Ag­giun­ge­re persone è possibile solo at­tra­ver­so il metodo corretto:

class Bus():
    def __init__(self, capacity):
        self._passengers = []
        self._capacity = capacity
    
    def enter(self, passenger):
        if len(self._passengers) < self._capacity:
            self._passengers.append(passenger)
            print(f"{passenger} has entered the bus")
        else:
            raise Exception("Bus is full")
# instantiate bus with given capacity
bus = Bus(2)
bus.enter("Jack")
bus.enter("Jim")
# will fail, bus is full
bus.enter("John")
Python

Vantaggio: modellare sistemi

La pro­gram­ma­zio­ne orientata agli oggetti si presta in modo par­ti­co­la­re alla mo­del­la­zio­ne dei sistemi. OOP è intuitivo dal punto di vista umano, in quanto anche noi pensiamo in termini di oggetti che possono essere clas­si­fi­ca­ti. Per oggetti si intendono sia cose fisiche che concetti astratti.

Anche l’ere­di­ta­rie­tà tramite gerarchie di classi presente in molti linguaggi OOP cor­ri­spon­de a modelli di pensiero umani. Esa­mi­nia­mo l’ultimo punto con l’aiuto di un esempio. Gli animali sono concetti astratti. Gli animali esistenti sono sempre ma­ni­fe­sta­zio­ni concrete di una specie. In base alla specie, gli animali hanno ca­rat­te­ri­sti­che diverse. Un cane non è in grado di ar­ram­pi­car­si o di volare, quindi si limita a movimenti nello spazio bi­di­men­sio­na­le:

# abstract base class
class Animal():
    def move_to(self, coords):
        pass
# derived class
class Dog(Animal):
    def move_to(self, coords):
        match coords:
            # dogs can't fly nor climb
            case (x, y):
                self._walk_to(coords)
# derived class
class Bird(Animal):
    def move_to(self, coords):
        match coords:
            # birds can walk
            case (x, y):
                self._walk_to(coords)
            # birds can fly
            case (x, z, y):
                self._fly_to(coords)
Python

Svantaggi della pro­gram­ma­zio­ne orientata agli oggetti

Lo svan­tag­gio prin­ci­pa­le di OOP è il lessico, ini­zial­men­te difficile da com­pren­de­re. Dovete imparare concetti com­ple­ta­men­te nuovi, il cui si­gni­fi­ca­to e scopo non sono sempre chiari a partire da semplici esempi. Com­met­te­re errori è facile; la mo­del­la­zio­ne delle gerarchie di eredità, in par­ti­co­la­re, richiede molta abilità ed espe­rien­za.

Una delle critiche più frequenti a OOP è l’in­cap­su­la­men­to dello stato interno, che in realtà sarebbe concepito come un vantaggio. Ciò comporta dif­fi­col­tà nella pa­ral­le­liz­za­zio­ne del codice OOP. Se un oggetto viene trasmesso a diverse funzioni parallele, lo stato interno potrebbe cambiare tra le chiamate di funzione. Oltre a ciò, a volte occorre accedere a in­for­ma­zio­ni in­cap­su­la­te altrove all’interno di un programma.

In genere, la natura dinamica della pro­gram­ma­zio­ne orientata agli oggetti comporta una perdita di pre­sta­zio­ni. Questo perché il numero di ot­ti­miz­za­zio­ni statiche possibili è inferiore. Anche i sistemi di tipi dei linguaggi OOP puri, ten­den­zial­men­te meno pro­nun­cia­ti, im­pe­di­sco­no alcuni controlli statici. Eventuali errori diventano visibili solo in fase di ese­cu­zio­ne. Nuovi sviluppi, come il lin­guag­gio Ja­va­Script Ty­pe­Script, sono però in grado di con­tra­sta­re questa si­tua­zio­ne.

Quali linguaggi di pro­gram­ma­zio­ne sup­por­ta­no o sono adatti a OOP?

Pressoché tutti i linguaggi multi-paradigma sono adatti alla pro­gram­ma­zio­ne orientata agli oggetti. Tra questi figurano i ben noti linguaggi di pro­gram­ma­zio­ne web PHP, Ruby, Python e Ja­va­Script. Per contro, i principi di OOP sono lar­ga­men­te in­com­pa­ti­bi­li con l’algebra re­la­zio­na­le alla base di SQL. Onde evitare il fenomeno di “impedance mismatch” (di­sal­li­nea­men­to di impedenza), si ricorre a speciali livelli di tra­du­zio­ne noti come “Object Re­la­tio­nal Mappers” (ORM).

Persino i linguaggi puramente fun­zio­na­li come Haskell non for­ni­sco­no un supporto nativo per OOP. L’im­ple­men­ta­zio­ne di OOP in C richiede uno sforzo notevole. Cu­rio­sa­men­te, Rust è un lin­guag­gio moderno che funziona senza classi. struct ed enum sono invece uti­liz­za­te come strutture di dati il cui com­por­ta­men­to è definito dalla parola chiave impl. I com­por­ta­men­ti possono essere rag­grup­pa­ti con i co­sid­det­ti traits; in questo modo vengono rap­pre­sen­ta­ti anche l’ere­di­ta­rie­tà e il po­li­mor­fi­smo. La struttura del lin­guag­gio riflette la best practice OOP “Com­po­si­tion over In­he­ri­tan­ce”.

Vai al menu prin­ci­pa­le