Premessa

Portale di appartenenza: Programmazione orientata agli oggetti.

Cosa troverai in questa nota:

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.

1 - Il concetto di oggetto

Uno degli approcci innovativi apportati al mondo della programmazione, a partire dagli anni ‘70, è stata l’introduzione del concetto di oggetto “software”, basato sull’accezione più comune del termine oggetto.

1.1 - Dall’oggetto del mondo reale a quello della programmazione

Guarda intorno a te in questo momento e troverai molti esempi di oggetti del mondo reale, come il tuo cane, la tua scrivania, il tuo televisore, la tua bicicletta.

Gli oggetti del mondo reale condividono due caratteristiche: hanno uno stato e un comportamento. I cani hanno uno stato (nome, colore, razza, fame) e un comportamento (abbaiare, riportare oggetti, scodinzolare). Anche le biciclette hanno uno stato (marcia attuale, cadenza di pedalata, velocità attuale) e un comportamento (cambiare marcia, cambiare cadenza, frenare). Identificare lo stato e il comportamento degli oggetti reali è un ottimo modo per iniziare a pensare in termini di programmazione orientata agli oggetti.

Prenditi un momento per osservare gli oggetti del mondo reale che si trovano nel tuo ambiente immediato. Per ogni oggetto che vedi, poniti due domande: “in quali stati può trovarsi questo oggetto?” e “quali comportamenti può eseguire questo oggetto?“. Noterai che gli oggetti del mondo reale variano in complessità: la tua lampada da scrivania potrebbe avere solo due stati possibili (accesa e spenta) e due comportamenti (accendere, spegnere), mentre la tua radio da scrivania potrebbe avere stati aggiuntivi (accesa, spenta, volume attuale, stazione attuale) e comportamenti (accendere, spegnere, aumentare volume, diminuire volume, cercare, scansionare, sintonizzare). Potresti anche notare che alcuni oggetti contengono a loro volta altri oggetti. Queste osservazioni del mondo reale si traducono direttamente nel mondo della programmazione orientata agli oggetti.

Gli oggetti nel mondo della programmazione sono concettualmente simili agli oggetti del mondo reale: anche essi consistono in uno stato e in un comportamento associato. Possiamo quindi provare a definire formalmente cos’è un oggetto in questa accezione.

Definizione: oggetto

Un oggetto è un’entità identificabile in un programma da:

  • Uno stato, ossia l’insieme delle caratteristiche dell’oggetto, rappresentato dai suoi attributi.
  • Un comportamento, ossia ciò che può fare l’oggetto, rappresentato dai suoi metodi.

Vediamo meglio i concetti di attributi e metodi.

Definizione: attributo

Un attributo (o campo nella sua realizzazione pratica nei linguaggi di programmazione) è una struttura dati associata a un oggetto.

Definizione: metodo

Un metodo è una procedura associata a un oggetto. Esso è composto da due parti:

  • Una firma, ossia l’abbinamento del nome del metodo con i suoi parametri formali.
  • Un corpo, cioè l’insieme di istruzioni che definiscono cosa fa il metodo.

1.2 - Le classi e le istanze di oggetti

Nel mondo reale, si trovano spesso molti oggetti individuali dello stesso tipo: potrebbero esserci migliaia di biciclette esistenti, tutte dello stesso modello e marca, ma solo una di queste è quella tua, nonostante ogni bicicletta sia stata costruita a partire dallo stesso insieme di progetti e pertanto contiene gli stessi componenti.

Ecco quindi che il termine classe aiuta in questo caso a “categorizzare” gli oggetti dello stesso tipo, ognuno dei quali viene considerato una sua istanza.

Definizione: classe

Una classe è una struttura astratta che definisce un insieme di attributi e metodi a partire da cui verranno generati gli oggetti di quel tipo.

Questa definizione di classe può sembrare a prima vista poco comprensibile, quindi vediamo subito un esempio.

Una classe è quindi un modello astratto che descrive lo stato (attributi) e il comportamento (metodi) di un oggetto, cioè 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.

Definizione: istanza

Un’istanza è un singolo oggetto concreto creato a partire da una classe: possiede un proprio stato (cioè valori specifici per gli attributi definiti dalla classe) e può eseguire i comportamenti (metodi) previsti dalla classe.

Per capire meglio le differenze tra oggetti, classi e istanze facciamo un esempio.

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.

1.3 - Definizione di programmazione orientata agli oggetti

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.

2 - 1. Il ruolo della gerarchia degli oggetti

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, cioè oggetti ciascuno 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.

2.1 - La 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.

2.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.

2.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.

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 di Gestore dati studenti che di Gestore appelli.

2.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.

2.2 - I tipi di gerarchie

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.

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.

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”.

3 - L’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.

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.

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.

3.2 - L’incapsulamento

Unitamente al concetto di astrazione c’è quello di incapsulamento, utile alla sicurezza dei programmi.

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. Ciò protegge l’integrità dello stato interno dell’oggetto, rendendo l’accesso ai dati contenuti in esso molto più sicuro.

4 - L’ereditarietà

Tipi diversi di oggetti spesso condividono alcune caratteristiche comuni tra loro: per esempio, le mountain bike, le bici da strada e i tandem, ad esempio, condividono tutti lo stesso comportamento tipico di una bicicletta. Tuttavia, ognuno di questi tipi di bicicletta possiede anche caratteristiche aggiuntive che lo rendono diverso dagli altri: i tandem hanno due selle e due manubri; le bici da strada hanno manubri ricurvi verso il basso; alcune mountain bike hanno una corona aggiuntiva, che consente un rapporto di trasmissione più basso.

La programmazione orientata agli oggetti permette alle classi di ereditare stato e comportamento comuni da altre classi, tramite il meccanismo dell’ereditarietà.

Definizione: ereditarietà

L’ereditarietà è un meccanismo per cui una classe (detta sottoclasse o classe derivata) può acquisire (estendere) automaticamente gli attributi e i metodi definiti in un’altra classe (detta superclasse o classe base), consentendo la riutilizzazione del codice e la modellazione di relazioni gerarchiche tra entità.

5 - Le interfacce

Come hai già appreso, gli oggetti definiscono la loro interazione con il mondo esterno attraverso i metodi che espongono: essi, cioè, costituiscono l’interfaccia dell’oggetto con l’ambiente esterno; ad esempio, i pulsanti sul frontale del tuo televisore rappresentano l’interfaccia tra te e il circuito elettrico che si trova dietro la scocca in plastica.

Nella programmazione orienta agli oggetti, le interfacce determinano il comportamento degli oggetti.

Definizione: interfaccia

Una interfaccia è un insieme definito di metodi che una classe si impegna a implementare e rendere accessibili all’esterno, specificando cosa un’istanza di quella classe può fare, ma non come lo fa.

Fonti