Premessa
Portale di appartenenza: Programmazione orientata agli oggetti.
Cosa troverai in questa nota:
- Il paradigma della programmazione orientata agli oggetti e i concetti ad esso connessi come oggetti, classi e istanze.
- Il ruolo della gerarchia nella programmazione orientata agli oggetti e i suoi concetti chiave 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, l’ereditarietà e le interfacce.
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
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
Questa definizione di classe può sembrare a prima vista poco comprensibile, quindi vediamo subito un esempio.
Esempio: la classe
AutomobileAll’interno di un programma, possiamo pensare di dichiarare una classe
Automobilecon la quale vogliamo rappresentare, appunto, un’automobile.Gli attributi di questa classe, ossia quelle strutture dati che mi permettono di identificare e descrivere le caratteristiche di un’automobile, potrebbero essere:
- Gli attributi
targaemodello, 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, i metodi di questa classe che mi permettono di operare su questi attributi potrebbero essere:
- Il metodo
accelerache mi permette di incrementare il valore dell’attributovelocita(entro un certo limite).- Il metodo
decelerache mi permette di decrementare il valore dell’attributovelocita(fino a raggiungere il valore0).- Il metodo
descrizioneche mi restituisce tutte le informazioni sull’auto, ossia i valori degli attributitarga,modelloeanno.
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
Per capire meglio le differenze tra oggetti, classi e istanze facciamo un esempio.
Esempio: istanze della classe
AutomobileRiprendendo 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
AutoMiadella classeAutomobile, i cui attributi hanno i seguenti valori:
targa = "DE217AJ".modello = "Peugeot 208".anno = 2010.velocita = 100.0.- L’istanza
PandinoDelNonnodella classeAutomobile, i cui attributi hanno i seguenti valori:
targa = "CM752BB".modello = "Fiat Panda".anno = 1989.velocita = 0.0.Le istanze
AutoMiaePandinoDelNonnorappresentano 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.
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.
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 --> AutIl 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.
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.
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"--> IntIn 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 studentiche diGestore 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.
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”.
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.
Esempio di astrazione
Un esempio di astrazione è quello di un autista alla guida di un’automobile: l’autista può guidare indifferentemente un’automobile diesel e una a benzina, una FIAT Panda o un 4x4, a prescindere dall’implementazione delle sue componenti interne, perché importa solo che l’interfaccia esterna dell’oggetto sia sempre la stessa.
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.
Esempio di incapsulamento
Consideriamo una classe
ContoBancario. Essa rappresenta un oggetto che gestisce il saldo di un cliente. L’attributosaldo, per questioni di sicurezza, è necessario che venga nascosto verso l’esterno della classe per evitare che chiunque possa modificarlo liberamente e senza restrizioni e facendo in modo che sia accessibile solo tramite degli appositi metodi che pongono dei limiti alle variazioni che può subire questo dato (es. ilsaldonon può diminuire di più di 100€ in una sola volta). Si può dire, quindi, che l’attributosaldoè stato incapsulato all’interno dell’oggetto.
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à.
Esempio: ereditarietà
Supponiamo di avere una superclasse chiamata
Veicolo, che rappresenta tutte le caratteristiche comuni dei veicoli: può muoversi, ha una velocità, e può fermarsi.Da questa superclasse derivano varie sottoclassi:
Automobile,BiciclettaeCamion. Tutte queste classi ereditano automaticamente gli attributi e i metodi generali delVeicolo(come la capacità di muoversi o fermarsi), ma ciascuna può anche definire proprietà o comportamenti specifici:
- L’
Automobilepuò avere un motore e un impianto di climatizzazione.- La
Biciclettapuò avere dei pedali e delle marce.- Il
Camionpuò trasportare merci pesanti e avere più assi.L’ereditarietà consente quindi di riutilizzare il codice comune relativo ai veicoli, evitando duplicazioni, e allo stesso tempo permette di specializzare ogni sottoclasse in base alle sue esigenze.
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
Esempio: interfaccia
Immaginiamo un telecomando universale. Esso può essere usato per controllare diversi dispositivi, come un televisore, un impianto stereo, un lettore DVD, purché tutti quei dispositivi espongano la stessa interfaccia: ad esempio, comandi come accendi, spegni, aumenta volume, diminuisci volume.
Ogni dispositivo implementa a modo suo questi comandi: il televisore alza il volume dell’audio, lo stereo regola l’amplificatore delle proprie casse, il lettore DVD potrebbe regolare il livello dell’uscita audio digitale. Ma dal punto di vista del telecomando (cioè dell’utente), non importa come viene realizzata l’azione: ciò che conta è che i metodi siano disponibili e funzionino come previsto.
In questo esempio:
- Il telecomando rappresenta un utilizzatore dell’interfaccia.
- Ogni dispositivo rappresenta una classe che implementa l’interfaccia.
- I comandi disponibili rappresentano i metodi definiti nell’interfaccia.
L’interfaccia cioè definisce cosa si può fare, mentre ogni dispositivo stabilisce come eseguire quell’azione. Questo consente di interagire con oggetti diversi in modo uniforme e prevedibile, senza conoscerne l’implementazione interna.
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. - 🛜 The Java™ Tutorials scritti dalla Oracle: