Premessa
Portale di appartenenza: Programmazione orientata agli oggetti.
Cosa troverai in questa nota:
- Un’introduzione al paradigma della programmazione orientata agli oggetti e di concetti ad esso connessi come classi, oggetti e istanze.
- I concetti dietro l’ideazione della programmazione orientata agli oggetti come la scomposizione orientata agli oggetti di un problema, i tipi di gerarchia (“part of” e “is a”) tra entità, l’astrazione e l’incapsulamento.
Prerequisiti: per comprendere pienamente il contenuto di questa nota, oltre le conoscenze minime che do per scontato che tu sappia già, ti consiglio di:
- Sapere già come funziona la programmazione in generale, tramite linguaggi quali il C.
Buona lettura! ☝️🤓
Con la diffusione dell’informatica in ogni settore e la conseguente necessità di sviluppare nuove applicazioni, unita all’evoluzione dell’hardware, le dimensioni dei programmi (in termini di linee di codice) sono aumentate drasticamente, determinando criticità nello sviluppo, nel debug e nella manutenzione del software. Per questo motivo, sono stati ideati nuovi paradigmi di programmazione per aiutare i programmatori a sviluppare con maggiore facilità il software.
Uno degli approcci innovativi apportati al mondo della programmazione, a partire dagli anni ‘70, è stata l’introduzione del concetto di classe per facilitare la gestione del codice.
Definizione: classe, attributi e metodi
Una classe è una porzione circoscritta di codice sorgente che contiene la dichiarazione dei suoi membri, che possono essere:
- Strutture dati, dette attributi (o campi nella loro realizzazione pratica nei linguaggi di programmazione).
- Procedure, detti metodi, che operano sugli attributi.
Questa definizione di classe può sembrare a prima vista poco comprensibile, quindi vediamo subito un esempio.
Esempio: la classe
Automobile
All’interno di un programma, possiamo pensare di dichiarare una classe
Automobile
con la quale vogliamo rappresentare, appunto, un’automobile.Le strutture dati che conterrà questa classe, ossia i suoi attributi, potrebbero essere quelle variabili che mi permettono di identificare e descrivere le caratteristiche di un’automobile, per esempio:
- Gli attributi
targa
emodello
, di tipo stringa, che rappresentano rispettivamente targa e modello dell’auto.- L’attributo
anno
, di tipo intero, che rappresenta l’anno di immatricolazione dell’auto.- L’attributo
velocita
, di tipo reale, che rappresenta la velocità a cui sta andando l’auto in un determinato momento.Invece, esempi di procedure che mi permettono di operare su questi attributi (cioè i metodi della classe) potrebbero essere:
- Il metodo
accelera
che mi permette di incrementare il valore dell’attributovelocita
(entro un certo limite).- Il metodo
decelera
che mi permette di decrementare il valore dell’attributovelocita
(fino a raggiungere il valore0
).- Il metodo
descrizione
che mi restituisce tutte le informazioni sull’auto, ossia i valori degli attributitarga
,modello
eanno
.
Una classe è quindi un modello astratto che descrive le caratteristiche (attributi) e il comportamento (metodi) di un concetto che si vuole rappresentare, come una persona, un luogo o una cosa.
La concretizzazione di una classe avviene per mezzo delle sue istanze, dette oggetti.
Definizione: oggetto e istanza
Per capire meglio le differenze tra classi, oggetti e istanze facciamo un esempio.
Esempio: istanze della classe
Automobile
Riprendendo l’esempio della classe
Automobile
, a partire da questa classe possiamo definire alcune sue istanze, cioè alcuni “esempi concreti” del “modello astratto” rappresentato dalla classe:
- L’istanza
AutoMia
della classeAutomobile
, i cui attributi hanno i seguenti valori:
targa = "DE217AJ"
.modello = "Peugeot 208"
.anno = 2010
.velocita = 100.0
.- L’istanza
PandinoDelNonno
della classeAutomobile
, i cui attributi hanno i seguenti valori:
targa = "CM752BB"
.modello = "Fiat Panda"
.anno = 1989
.velocita = 0.0
.Le istanze
AutoMia
ePandinoDelNonno
rappresentano a tutti gli effetti degli oggetti.
Creare un’istanza di una classe significa quindi creare un oggetto con una propria memoria allocata e con valori concreti assegnati agli attributi della classe da cui deriva.
Osservazione: differenza tra i termini oggetto e istanza
Molti linguaggi e documentazioni usano i termini oggetto e istanza in modo intercambiabile, ma c’è una sfumatura importante:
- Il termine oggetto enfatizza l’entità concreta in uso.
- Il termine istanza enfatizza il rapporto tra quell’entità e la classe da cui è derivata.
A questo punto, possiamo quindi dare una definizione esaustiva di quella che è la programmazione orientata agli oggetti.
Definizione: programmazione orientata agli oggetti
La programmazione orientata agli oggetti (OOP, Object-Oriented Programming) è un paradigma di programmazione che permette di definire oggetti in grado di interagire gli uni con gli altri.
1. Le origini concettuali dell’OOP
Ok, ma quindi perché è stata ideata la programmazione orientata agli oggetti? Questi oggetti a cosa mi possono servire nel concreto? Ma sono fisicamente degli oggetti?
Tutte queste domande hanno una risposta molto più semplice di quel che possa sembrare. Partiamo da un semplice concetto: qualsiasi sistema più o meno complesso, di qualsiasi tipo esso sia, spesso ha una natura gerarchica per facilitarne la sua gestione. Pensiamo per esempio a un’azienda, che spesso ha a capo un amministratore delegato che la governa nel suo insieme, affiancato da dirigenti di vari reparti (come produzione, marketing, risorse umane), i quali a loro volta supervisionano gruppi più piccoli di dipendenti o sottosistemi operativi dell’azienda.
Questa struttura gerarchica consente di suddividere la complessità in livelli di responsabilità ben definiti, rendendo più chiara la distribuzione dei compiti, la comunicazione e il controllo. Allo stesso modo, nei sistemi software (soprattutto in quelli orientati agli oggetti) si adotta una logica simile: si definiscono entità astratte (le classi), che possono essere organizzate in una gerarchia (tramite l’ereditarietà), e da cui si generano istanze concrete (gli oggetti), ciascuna con un proprio comportamento e stato.
Questa analogia non è casuale: la programmazione orientata agli oggetti nasce proprio dall’idea di modellare i sistemi informatici seguendo schemi del mondo reale, sfruttando concetti come responsabilità, relazioni, compartimentazione, e riutilizzo.
1.1 - Il ruolo della scomposizione nella programmazione
Nella progettazione di un sistema software complesso, è fondamentale suddividerlo in componenti più piccole e gestibili, ognuna delle quali può essere analizzata, sviluppata e migliorata in modo indipendente. Questa strategia risponde a un limite intrinseco della cognizione umana: per comprendere efficacemente un sistema, è preferibile concentrarsi su un sottoinsieme di elementi alla volta, piuttosto che affrontarne l’intero insieme simultaneamente. Tale approccio favorisce una progettazione modulare, migliorando la manutenibilità del codice, la verificabilità del comportamento del sistema e la riusabilità dei suoi componenti.
1.1.1 - Scomposizione algoritmica dei problemi
La maggior parte di noi è stata formata con il paradigma della progettazione strutturata dall’alto verso il basso e quindi si approccia alla scomposizione dei problemi come a una semplice questione di scomposizione algoritmica, in cui ogni modulo del sistema denota una fase importante di un processo generale.
Definizione: scomposizione algoritmica di un problema
La scomposizione algoritmica di un problema è un approccio per cui il problema viene scomposto in una serie di passi elementari e sequenziali, ognuno dei quali rappresenta un’operazione specifica da eseguire per raggiungere la soluzione.
Di seguito è riportato un esempio di uno dei prodotti della progettazione strutturata, un diagramma di struttura che mostra le relazioni tra i vari elementi funzionali della soluzione.
Esempio di scomposizione algoritmica di una procedura di prenotazione a un esame
Ecco un esempio di una procedura di prenotazione a un esame scomposta usando la scomposizione algoritmica.
graph TD Isc(Iscrizione) Acc(Accesso) Sel(Selezione esame) Pre(Prenotazione) Pas(Inserimento password) Ver(Verifica della password) Aut(Autorizzazione accesso) Isc --> Acc Isc --> Sel Isc --> Pre Acc --> Pas Acc --> Ver Acc --> Aut
Il diagramma illustra parte della progettazione di un programma che supporta l’iscrizione degli studenti agli esami. La procedura comprende la fase di accesso, la selezione dell’esame a cui iscriversi e la fase di prenotazione in cui lo studente si iscrive all’esame.
Queste fasi sono di per sé complesse e scomposte in algoritmi ancora più semplici che mostriamo solo parzialmente nella figura.
1.1.2 - Scomposizione orientata agli oggetti dei problemi
Esiste una scomposizione alternativa per lo stesso problema. Anziché scomporre il problema in fasi sempre più elementari, possiamo identificare oggetti che rappresentano servizi che offre la piattaforma dell’università:
Definizione: scomposizione orientata agli oggetti di un problema
La scomposizione orientata agli oggetti di un problema è un approccio per cui il problema viene scomposto in oggetti che rappresentano entità del mondo reale o concetti astratti. Ogni oggetto incapsula dati e comportamenti, e l’interazione tra oggetti porta alla soluzione del problema.
Esempio di scomposizione orientata agli oggetti di una procedura di prenotazione a un esame
Usando sempre l’esempio della scomposizione di una procedura di prenotazione a un esame, proviamo ora a scomporlo tramite la scomposizione orientata agli oggetti.
graph LR Int(Interfaccia studenti) GeStu(Gestore dati studenti) GeApp(Gestore appelli) Int --"Verifica la password"--> GeStu GeStu --"Autorizza l'accesso"--> Int Int --"Prenota l'appello"--> GeApp GeApp --"Conferma la prenotazione"--> Int
In questo caso, gli oggetti da noi definiti sono:
Interfaccia studenti
: l’interfaccia utente che lo studente utilizza per iscriversi all’esame.Gestore dati studenti
: l’oggetto che memorizza e gestisce i dati dello studente (per la verifica della password).Gestore appelli
: l’oggetto che memorizza i dati sugli esami (per recuperare le informazioni sugli esami disponibili, con le relative date).Sebbene entrambi le scomposizioni risolvano lo stesso problema, lo fanno in modi piuttosto diversi. In questa seconda scomposizione, vediamo il mondo come un insieme di agenti autonomi che collaborano per eseguire un comportamento di livello superiore. Per esempio, l’azione di verifica della password non esiste come algoritmo indipendente, ma è un’operazione associata all’oggetto
Gestore dati studenti
. In questo modo, ogni oggetto della nostra soluzione incarna il suo comportamento unico e ognuno di essi modella qualche oggetto del mondo reale.
Osservazione: architettura client-server nella scomposizione orientata agli oggetti
Si noti che la comunicazione tra gli oggetti nella scomposizione orientata agli oggetti si basa su un’architettura client-server:
- L’oggetto che offre uno o più servizi è il server.
- Gli oggetti che invocano il server per ricevere i risultati dell’esecuzione del servizio sono i client.
Nell’esempio sopra,
Interfaccia studenti
è client sia diGestore dati studenti
che diGestore appelli
.
1.1.3 - Confronto tra i due tipi di scomposizione
Qual è il modo giusto di scomporre un sistema complesso? Per algoritmi o per oggetti? La risposta è che sono importanti entrambe le viste: la vista algoritmica mette in evidenza l’ordinamento degli eventi, mentre quella [orientata agli oggetti]((Programmazione%20orientata%20agli%20oggetti.md#^definizione-scomposizione-orientata-agli-oggetti-di-un-problema) enfatizza gli agenti che causano l’azione o i soggetti su cui agiscono le operazioni. Tuttavia, resta il fatto che non possiamo costruire un sistema complesso in entrambi i modi contemporaneamente, perché si tratta di viste completamente ortogonali. Dobbiamo iniziare a scomporre un sistema per algoritmi o per oggetti e poi usare la struttura risultante come quadro per esprimere l’altra prospettiva.
1.2 - Il ruolo della gerarchia nella programmazione
Un altro modo per dare più significato ai singoli pezzi di informazione in un sistema software complesso è quello di riconoscere in modo chiaro le gerarchie presenti al suo interno. Infatti, la maggior parte dei sistemi reali non segue una sola gerarchia, ma ne contiene diverse, a seconda di come li si osserva. Per esempio, possiamo suddividere una struttura nelle componenti di cui è composta: questo tipo di suddivisione prende il nome di gerarchia strutturale, oppure gerarchia “part of”, perché ogni componente è una parte di qualcosa di più grande.
Definizione: gerarchia "part of"
La gerarchia “part of” (o gerarchia strutturale) è un tipo di relazione strutturale in cui un’entità complessa è scomposta in componenti più semplici, ciascuno dei quali è parte costitutiva del tutto. In altre parole, descrive un sistema come insieme di parti, ognuna delle quali contribuisce alla struttura o al funzionamento dell’intero.
Esempio di gerarchia "part of"
Un esempio di gerarchia “part of” può essere rappresentato dalla struttura di un aereoplano, per cui ogni componente che lo costituisce è una parte della struttura totale.
graph TD Aereo(Aereoplano) --> Fusoliera(Fusoliera) Aereo --> Ali(Ali) Aereo --> Motori(Motori) Aereo --> Carrello(Carrello di atterraggio) Aereo --> ImpiantoElettrico(Impianto Elettrico) Ali --> Flap(Flap) Ali --> Alettoni(Alettoni) Motori --> Turbina(Turbina) Motori --> Compressore(Compressore) Carrello --> Ruote(Ruote) Carrello --> Ammortizzatori(Ammortizzatori)
Tuttavia, possiamo suddividere il sistema in modo del tutto ortogonale, per esempio secondo una gerarchia di tipo generalizzazione-specializzazione: questa seconda gerarchia rappresenta una cosiddetta gerarchia di ereditarietà o gerarchia “is a”.
Definizione: gerarchia "is a"
La gerarchia “is a” (o gerarchia di ereditarietà) è un tipo di relazione strutturale in cui un’entità complessa viene decomposta in entità più specifiche che rappresentano casi particolari di quella più generale.
Esempio di gerarchia "is a"
Un esempio di gerarchia “is a” può essere rappresentato da un motore a reazione di un aeroplano che può essere ricondotti ad alcuni suoi casi particolari.
graph TD A(Motore a reazione) --> B(Turbogetto) A --> C(Turboventola) A --> D(Razzo) A --> E(Idrogetto) B --> F(Rolls-Royce Avon) B --> G(General Electric J31)
L’esperienza dei creatori del paradigma orientato agli oggetti indica che è essenziale vedere un sistema da entrambe le prospettive, studiando sia la sua gerarchia “part of” che la sua gerarchia “is a”.
1.3 - Il ruolo dell’astrazione
I sistemi complessi non solo sono gerarchici, ma i livelli di questa gerarchia rappresentano diversi livelli di astrazione del sistema, ossia quanto in profondità vogliamo analizzarlo, che sia dal punto di vista delle sue componenti come nel caso della gerarchia “part of” o delle sue specializzazioni come nel caso della gerarchia “is a”.
Definizione: astrazione
L’astrazione è un principio che consiste nel nascondere i dettagli non essenziali di un sistema per metterne in evidenza gli aspetti rilevanti rispetto a un determinato contesto o livello di analisi.
Esempio di astrazione
Un esempio di astrazione è quello di un autista alla guida di un’automobile: per quanto riguarda il volante dell’auto, il guidatore sa che serve a sterzare, ma non ha bisogno di conoscere i dettagli del servosterzo, degli ingranaggi o del sistema idraulico. Il volante, in questo caso, è un’interfaccia astratta per il controllo della direzione.
In termini semplici, astrarre significa concentrarsi su cosa fa un componente, piuttosto che su come lo fa.
Il livello di astrazione che si vuole usare si determina in base alle proprie esigenze da soddisfare. Per esempio, se stiamo cercando di individuare un problema di latenza temporale nella memoria primaria, potremmo guardare correttamente all’architettura a livello di porta logica (gate) del computer, ma questo livello di astrazione sarebbe inappropriato se stessimo cercando di trovare la fonte di un problema in un’applicazione di foglio elettronico.
1.3.1 - Il modello contrattuale della programmazione
Con l’astrazione si rischia di cadere facilmente in alcune trappole, per cui è sempre bene tenere a mente che astrarre non significa semplificare ingenuamente. Un’astrazione mal progettata può nascondere troppo, o peggio ancora, mascherare complessità mal gestite. L’arte dell’astrazione consiste proprio nel trovare il giusto equilibrio tra ciò che deve essere esposto e ciò che deve essere celato.
Proprio per questo, in questo caso è bene adottare la filosofia del modello contrattuale della programmazione, introdotta da Bertrand Meyer (programmatore francese creatore del linguaggio di programmazione orientato agli oggetti Eiffel) nel suo libro Object-Oriented Software Construction .
Definizione: modello contrattuale della programmazione
Il modello contrattuale (in inglese design by contract) è un paradigma di progettazione del software che si basa sull’idea di trattare ogni modulo di un programma (funzione, metodo o classe) come se fosse una parte di un contratto formale, stabilito tra due parti: il chiamante, detto client, e il chiamato, detto server.
Come in un contratto legale, entrambe le parti hanno obblighi e diritti, e il corretto funzionamento del programma dipende dal rispetto reciproco di tali condizioni.
1.3.2 - L’incapsulamento
Unitamente al concetto di astrazione c’è quello di incapsulamento.
Definizione: incapsulamento
L’incapsulamento è un principio dei linguaggi di programmazione orientati agli oggetti secondo cui la struttura interna e il funzionamento interno di un oggetto non devono essere visibili dall’esterno, ma ci si può interagire con esso solo attraverso un’interfaccia.
Esempio di incapsulamento in un'automobile
Riprendendo l’esempio dell’astrazione di un’automobile, possiamo notare come essa offra un’interfaccia semplice e controllata (volante, pedali, cambio, ecc.), mantenendo nascosti i dettagli interni complessi. L’incapsulamento è la ragione per cui, per esempio, noi possiamo guidare indifferentemente un’automobile diesel e una a benzina: non importa come il motore è implementato, ci importa solo che l’interfaccia esterna dell’oggetto sia sempre la stessa.
Fonti
- 🏫 Lezioni e slide della Prof. Bono Viviana del corso di Principi di Programmazione Orientata agli Oggetti (canale B), Corso di Laurea in Informatica presso l’Università di Torino, A.A. 2024-25:
- 📚 Paul J. Deitel, Harvey M. Deitel - Programmare in Java (11ᵃ Edizione) - Pearson, 2020 - ISBN:
8891916218
.