Corso JAVA Lezione n° 05 Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 I Caratteri Prima di riprendere con le classi e gli oggetti, faremo una breve digressione su un argomento che abbiamo solo accennato nelle lezioni precedenti, ovvero l’utilizzo dei caratteri e delle stringhe. Quando una variabile è dichiarata come char, per assegnargli un valore (ovvero una lettera) si utilizza il seguente tipo di assegnamento: char lettera; // Si dichiara che alla variabile lettera è associato un carattere. lettera = ‘a’; // Per assegnare una lettera alla variabile occorre racchiuderla tra gli apici. Un array di caratteri si inizializza nel modo seguente: char lettere[] = {‘a’,‘b’,‘c’,‘d’,‘e’}; 2 Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Le Stringhe A differenze degli int, float, char, bool, ecc.. le stringhe non sono un tipo fondamentale, String infatti è una classe pertanto le stringhe non sono variabili ma sono oggetti, anche se sono oggetti un po’ particolari. String Nome; // Si dichiara che la variabile Nome contiene un oggetto di tipo stringa. Nome = “Carlo”; //Per assegnare una stringa ad una variabile, la si racchiude tra i doppi apici (per i caratteri gli apici sono singoli). Un array di stringhe di inizializza nel modo seguente; Nomi String [] = {“Carlo”, “Michele”, “Erica”, “Marta”}; Le stringhe, a differenza degli altri oggetti, dispongono di uno speciale operatore + che permette la concatenazione. 3 Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 I Metodi Abbiamo visto la scorsa lezione che un metodo di una classe si dichiara così: <Modificatore> <Tipo> NomeMetodo( <Tipo> Nome_Parametro, … ) { Istruzioni; } Nell’esempio del ContoCorrente il metodo Prelievo_Consentito infatti era dichiarato nel seguente modo: public boolean Prelievo_Consentito(float importo_prelievo){ istruzioni }; Questo metodo quindi prende in input un valore float (prelievo) e mi restituisce un booleano. Come fa il metodo a restituirmi un valore booleano? Semplice!! Tra le varie istruzioni presenti tra le parentesi graffe ci sarà un istruzione return (valore da restituire). Nell’esempio infatti per ogni ramo dell’istruzione if era presente un return; se l’importo era sufficiente return(true); se non lo era return(false); 4 Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Istanziare un Oggetto Sappiamo adesso come si definiscono delle semplici classi, ma ancora non abbiamo visto come si utilizzano. Mettiamo il caso di aver definito una classe Studente: public class Studente { private String Nome; private String Cognome; private int Matricola; public int Media; Manca qualcosa!!! Il Costruttore!!! public Studente (String nome, String cognome, int matricola) { Nome=nome; Cognome=cognome; Matricola=matricola; } public void Stampa_Nome( ) { System.out.println("Il nome dello studente è: " + Nome); } public void Stampa_Cognome( ) { System.out.println("Il cognome dello studente è: " + Cognome); } public void Aggiungi_Voto_per_Media(int Voto) { } } 5 Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Istanziare un Oggetto (2) Se nel nostro programma vogliamo creare un oggetto studente utilizzeremo la seguente sintassi: nome_classe nome_oggetto = new nome_classe (parametri, dipende dal costruttore); Quindi se vogliamo creare un istanza della classe Studente dovremo scrivere: Studente Stefano = new Studente (“Stefano”, “Bianchi”, 208300); In questo modo ho creato un oggetto Studente chiamato Stefano. Come vedete i parametri che sono inseriti tra parentesi sono lo stesso numero e dello stesso tipo di quelli presenti nella dichiarazione: Studente (String nome, String cognome, int matricola); A questo punto nella mia applicazione potrò effettuare la chiamata di uno dei metodi che l’oggetto offre, es. Stefano.Stampa_Cognome(); //eseguirà la stampa del cognome dell’oggetto Stefano 6 Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 I tre principi della programmazione ad oggetti. Tutti i linguaggi di programmazione orientati agli oggetti offrono i seguenti meccanismi che implementano il modello orientato agli oggetti: INCAPSULAMENTO: E’ il meccanismo che controlla l’accesso ad una struttura dati solo attraverso opportune operazioni. Abbiamo visto questa caratteristica nella scorsa lezione, quando abbiamo parlato dei modificatori d’accesso. Il metodo private ad esempio impedisce di vedere metodi e strutture dati dall’esterno di una classe. Le altre due caratteristiche che vedremo in questa lezione sono: EREDITARIETA’: E’ il meccanismo attraverso il quale un oggetto acquisisce le proprietà di un altro. Fondamentale perché sostiene il concetto di della classificazione gerarchica. POLIMORFISMO: E’ il meccanismo attraverso il quale si è possibile progettare un’interfaccia generica per specificare una classe generale di azioni (una sola interfaccia, più implementazioni); 7 Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Ereditarietà Il meccanismo dell’ereditarietà, dal punto di vista semantico (e fortunatamente, come vedremo anche dal punto di vista realizzativo) è piuttosto semplice. Nella vita reale quando si sente parlare di ereditarietà, ci si riferisce molto spesso alla sua concezione medico-genetica, ovvero si intende la trasmissibilità per via genetica di determinate caratteristiche degli individui. Es. Il colore dei capelli o degli occhi di solito è uguale a quello di uno dei due genitori, perché è un carattere ereditario. E’ possibile estendere questo concetto anche in ambito informatico: l’ereditarietà consente di definire una classe detta sottoclasse (o classe derivata) a partire da una classe preesistente detta superclasse o classe base. La sottoclasse "eredita" implicitamente tutte le caratteristiche (attributi e metodi) della classe base. Concettualmente, l'ereditarietà indica una relazione di generalizzazione: essa corrisponde infatti all'idea che la superclasse rappresenti un concetto generale e la sottoclasse rappresenti una variante specifica di tale concetto generale. 8 Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Ereditarietà (2) L’ereditarietà permette quindi di concentrarsi sulle definizioni delle classi che conosciamo, mantenendo la possibilità di definire in seguito nuove versioni, le quali potranno ereditare le proprietà e le caratteristiche delle classi originali, aggiungendone delle nuove. Così facendo sarà possibile costruire una gerarchia di classi in cui le modifiche e le estensioni siano concentrate nei punti in cui sono realmente necessarie. Ad esempio: (l’avevamo già accennato alla lavagna nella scorsa lezione…) supponiamo di aver definito una classe Persona… se vogliamo fare una classe Studente, perché replicare i campi Nome, Cognome, ecc.. E i metodi Stampa_Nome.. se c’è una classe che ce li ha già! Persona Studente String Nome String Cognome int Età int Esami Meglio concentrarsi sullo sviluppo di nuove funzionalità void Chi_Sei( ); 9 Istituto Statale di Istruzione Superiore “F. Enriques” int Matricola String Facoltà void Chi_Sei( ); Corso di Programmazione in Java – Lezione n° 05 Ereditarietà (3) Consideriamo l’ipotesi che nel nostro passato avessimo implementato la classe Persona come illustrato qui di seguito… class Persona { protected int eta; protected String nome; protected String sesso; public Persona(String nome, String sesso, int eta) { this.nome=new String(nome); this.sesso=new String(sesso); this.eta=eta; } public void ChiSei() {System.out.println("Nome: "+nome+", sesso: "+sesso+", eta: "+eta); } } 10 Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Ereditarietà (4) 11 …poi è possibile creare la classe Studente che eredita da Persona tramite il comando extends, senza preoccuparsi di nuovo su attributi come il nome l’età ecc. ma concentrandosi su quello che servono… class Studente extends Persona { protected int esami; protected int matricola; protected String facolta; public Studente(String nome, String sesso, int eta, int esami,int matricola, String facolta) { super(nome, sesso,eta); this.esami=esami; this.matricola=matricola; this.facolta=new String(facolta); } public void ChiSei() { super.ChiSei(); System.out.println(“Facolta di "+facolta+", matricola: "+matricola+", esami sostenuti: "+esami); } } Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Ereditarietà (5) Java implementa soltanto l’ereditarietà singola, in quanto ciascuna classe di Java ha una sola superclasse. Al vertice della gerarchia di tutte le classi Java c’è la classe Object Un nuovo oggetto ha accesso a tutti i metodi della sua classe e delle sue progenitrici. MECCANISMO DEL LATE BINDING Quando si richiama un metodo su un oggetto, l’interprete ne cerca dapprima la definizione nella classe dell’oggetto stesso; se non la trova, cerca nella superclasse e risale la gerarchia fino a trovarla. Nel caso esistano più metodi con lo stesso nome,tipo restituito e stessi parametri, viene eseguito il metodo trovato per primo. 12 Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Ereditarietà (6) 13 Nell’esempio precedente, oltre ad extends, sono state utilizzate delle keywords che fino ad ora non avevamo visto: this, super. THIS: Ogni oggetto può accedere a tutti i suoi membri (variabili e metodi) usandone semplicemente il nome; il nome completo di un membro è però this.membro dove this è un identificatore che è associato all’oggetto corrente. Nel corpo di un metodo o di un costruttore, la variabile predefinita this quindi denota l'oggetto che sta eseguendo il metodo (o costruttore). Normalmente quando non esiste ambiguità sui nomi dei membri, l’uso dell’identificatore è superfluo. Lo si usa quando un campo o un parametro locale ha lo stesso nome di uno definito nella classe. SUPER: L'identificatore super ha un uso simile a this ma, a differenza di quest'ultimo, invece di fare riferimento all’oggetto corrente fa riferimento alla superclasse. Attraverso super è possibile invocare la versione originale di un metodo di cui è stato fatto l’overridind, che altrimenti risulterebbe inaccessibile a causa del meccanismo del late binding visto prima (eseguirebbe il primo che trova avente num. e tipo parametri corretti). Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Polimorfismo(1) Il polimorfismo è considerato, dopo l’incapsulamento e l’ereditarietà, il “terzo pilastro” della programmazione ad oggetti; per spiegarlo però dobbiamo partire dall’ereditarietà…. Proviamo a scrivere una semplice gerarchia di classi: public class Animale { public void interroga( ) { System.out.println(“Grunt”); } } public class Ghepardo extends Animale { public void interroga( ) { System.out.println(“Groar!”); } } public class Muflone extends Animale { public void interroga( ) { System.out.println(“MOOOO!”); } public void salta( ) { System.out.println(“hop!”); } } public class Armadillo extends Animale { 14 Istituto Statale di Istruzione Superiore “F. Enriques” } Corso di Programmazione in Java – Lezione n° 05 Polimorfismo (2) La classe Animale definisce un metodo interroga( ) che stampa una stringa sullo schermo. Grazie all’ereditarietà, sono assolutamente sicuro che tutte le classi che ereditano da Animale avranno questo metodo, in particolare si ha che la classe Armadillo non aggiunge niente alla sua superclasse, mentre le altre due sottoclassi (Ghepardo e Muflone) ridefiniscono il metodo interroga( ) per fornire la loro particolare implementazione. La classe Muflone aggiunge anche un nuovo metodo che non è contenuto nella classe base. Il senso di tutto questo è: se un metodo (o un campo) non privato è definito nella classe base, allora è sicuramente definito anche in tutte le classi derivate. Una classe derivata può fornire la propria particolare implementazione del metodo, cioè fare quello che si chiama un “overriding” (ridefinizione) del metodo, ma non può eliminarlo; se non presente abbiamo visto che sarà il meccanismo del late binding a cercare nelle classi progenitrici il metodo appropriato. Queste sono tutte caratteristiche fondamentali dell’ereditarietà. 15 Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Polimorfismo(3) Possiamo dire che le classi derivate sono in relazione “è un” con la classe base. Esempio, ciascun oggetto di tipo Armadillo “è un” oggetto di tipo Animale. A sua volta la classe Animale eredita implicitamente dalla classe Object, quindi un Armadillo è anche un Object. Ciascun oggetto, quindi, può essere visto come se appartenesse a più classi: la sua classe vera e propria e tutte le classi da cui eredita. Quando scriviamo: Armadillo arm = new Armadillo(); //Per istanziare un oggetto Armadillo. abbiamo un oggetto di nome arm che è allo stesso tempo un Armadillo, un Animale e un Object. Questo è un concetto intuitivo: tutti gli armadilli sono animali, che a sua volta sono “oggetti”. Il fatto che un oggetto abbia più di un tipo ha alcune conseguenze interessanti, guardate questo semplice programma: public class Upcast { public static void main(String[] args) { Animale bestia; bestia = new Armadillo(); } 16 Istituto Statale di Istruzione Superiore “F. Enriques” } Corso di Programmazione in Java – Lezione n° 05 Polimorfismo(4) Il codice della diapositiva precedente è semplicissimo, eppure può lasciare perplessi. Nella prima riga del main si dichiara una variabile bestia contenente un oggetto Animale. Nella seconda riga creiamo un oggetto di tipo Armadillo e lo assegniamo a tale variabile. Quindi abbiamo definito una variabile di un tipo e gli abbiamo assegnato un oggetto di un altro tipo, eppure il programma può essere compilato e lanciato senza errori. Per quale motivo il compilatore non restituisce un errore? Il compilatore non “protesta” perché non ne trova il motivo: il nostro assegnamento è valido. Possiamo sempre assegnare un Armadillo ad una variabile contenente oggetti di tipo Animale, perché un Armadillo è a tutti gli effetti un animale. Avremmo potuto anche assegnare lo stesso oggetto ad una variabile contenente un tipo Object: Object o = new Armadillo( ); Attenzione: un Armadillo è un animale, ma non vale il contrario: Armadillo a = new Animale( ); // errore! 17 Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Polimorfismo(5) Il motivo per cui non restituisce un errore nel primo caso, ma lo restituisce nel secondo, stà nel fatto che una sottoclasse ha sicuramente (come minimo) tutta l’interfaccia della classe base, ma non è detto che la classe base abbia tutta l’interfaccia sottoclasse (e’ il principio dell’ereditarietà visto fino ad ora). In generale, Java non ci permette di assegnare liberamente un oggetto ad una variabile di tipo diverso, esempio: Armadillo a = new Muflone(); // errore! L’unico caso, quindi, in cui possiamo assegnare un oggetto ad una variabile di tipo diverso è quando il tipo dell’oggetto è un sottotipo del tipo di tale variabile. In questo caso il compilatore fa un’operazione che si chiama “cast verso l’alto” o, in inglese, “upcasting”: usa una variabile di tipo “superiore” per contenere un oggetto di tipo “inferiore”. La stessa cosa succede se anziché usare un assegnamento facciamo la conversione in modo meno esplicito, esempio: se un metodo richiede come parametro un tipo animale, è possibile passargli un oggetto armadillo, visto che è anch’esso un animale. 18 Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Polimorfismo(6) Tutto questo può sembrare inutile. Perché non dovremmo essere precisi, e chiamare armadilli gli armadilli e mufloni i mufloni? La risposta richiede un esempio pratico: public class Upcast2 { public static void main(String[] args) { Armadillo arm = new Armadillo(); RiproduciVerso(arm); Muflone muf = new Muflone(); RiproduciVerso(muf); Ghepardo ghep = new Ghepardo(); RiproduciVerso(ghep); } private static void RiproduciVerso(Animale anim) { anim.interroga( ); } } 19 Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Polimorfismo(7) In UpCast2 abbiamo un metodo RiproduciVerso(), che essendo statico si comporta come una normale funzione; Il metodo accetta come parametro un animale, ma anziché passare alla funzione oggetti di classe Animale, le passiamo oggetti di sottoclassi di Animale, che vengono convertiti con un cast verso l’alto. La novità sta nel fatto che la funzione RiproduciVerso() non si limita a ricevere gli oggetti, ma chiama il loro metodo interroga() per scrivere sullo schermo il verso dell’animale in questione. Cosa appare sullo schermo? Il metodo RiproduciVerso() non ha idea dell’esistenza delle classi Armadillo, Muflone e Ghepardo, aa solo che riceverà un Animale, e “vede” sempre l’oggetto che gli viene passato come se fosse un animale. Dato che stiamo invocando il metodo interroga() di un animale, potremmo pensare che sullo schermo sarà sempre stampato il verso del generico animale: Grunt Grunt Grunt 20 In realtà, il risultato è questo: Istituto Statale di Istruzione Superiore “F. Enriques” Grunt MOOOO! Groar! Corso di Programmazione in Java – Lezione n° 05 Polimorfismo(8) Ciascun animale ha prodotto il suo verso (con l’eccezione dell’armadillo, che non ridefinisce il metodo interroga( ) e usa quindi l’implementazione fornita nella classe Animale). Si è ottenuto un risultato tale che sembra che la funzione RiproduciVerso( ) “sapesse” che l’oggetto che le veniva passato non era un generico animale, ma di volta in volta un armadillo, un muflone, un ghepardo. In realtà quello che succede è ancora più sottile: la funzione RiproduciVerso() si disinteressa completamente delle sottoclassi, tutto quello che le interessa è ricevere un animale. Quindi, poiché l’oggetto è un animale, la funzione RiproduciVerso( ) è sicura che l’oggetto che ha ricevuto avrà un metodo interroga( ), pertanto “manda un messaggio” a questo oggetto, chiedendogli di eseguire interroga( ). L’oggetto sa di essere, ad esempio, un muflone, quindi usa l’implementazione del metodo interroga() specifica della classe Muflone, ecc.. Riassumendo la funzione conosce una “caratteristica generale” dell’oggetto (il fatto che esiste un metodo interroga()) e lascia all’oggetto la responsabilità di comportarsi secondo le sue caratteristiche particolari (la particolare implementazione del metodo). Questa caratteristica del linguaggio, grazie alla quale possiamo usare un oggetto senza sapere esattamente di che tipo sia e come si comporterà, si chiama Polimorfismo. 21 Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Overriding e Overloaded Abbiamo detto che quando una sottoclasse ridefinisce un metodo ereditato, si dice che fa un “overriding”. Grazie all’overriding, una classe derivata può avere un’implementazione diversa dalla propria classe base, pur condividendone l’interfaccia; nel nostro esempio, le classi Ghepardo e Muflone fanno entrambe un overriding del metodo interroga() della classe Animale. Attenzione a non confondere l’overriding con l’overloading, che è un’operazione molto diversa. L’overloading è il “sovraccarico” di significati di un metodo; In pratica, significa che abbiamo più metodi con lo stesso nome, che si distinguono solo per il numero e il tipo dei parametri in ingresso. Ecco un esempio: public class Vongola extends Animale { public void interroga() { System.out.println(“Glup!”); } public void interroga(int num) { for (int i = 1; i <= num; i++) System.out.println(“Glup!”); } 22 } Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Overriding e Overloaded(2) 23 La classe Vongola contiene un metodo “overloaded” infatti esistono due versioni del metodo interroga(), e il compilatore sceglie di volta in volta la versione giusta in base ai parametri della chiamata. Due metodi con lo stesso nome convivono tranquillamente se hanno parametri in ingresso diversi. Quando si usa l’overloading dobbiamo sempre ricordare particolari: Il primo è che il tipo del parametro in uscita non è sufficiente a distinguere i metodi; Il secondo è che due metodi che si distinguono solo per i parametri di ingresso sono, a tutti gli effetti, due metodi diversi, anche se hanno lo stesso nome. Ecco un esempio sbagliato relativo al primo particolare: public class Bah { public void unMetodo( ) { System.out.println(“Glup!”); } public int unMetodo( ) { System.out.println(“Glup!”); return (1); } } Questa classe dà un errore di compilazione, perché una classe non può contenere due metodi con lo stesso nome e gli stessi parametri in ingresso, anche se i due metodi restituiscono tipi diversi. Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Overriding e Overloaded(3) La classe Vongola contiene due versioni del metodo interroga() (e questo è un overloading), e una delle due sostituisce l’implementazione della classe base Animale (questo è un overriding). Quindi è possibile usare overriding e overloading contemporaneamente, a patto di non fare confusione: la versione di interroga() che non prende parametri in ingresso è quella che si ritrova nell’interfaccia di Animale, mentre la versione che accetta un intero è un metodo diverso e completamente nuovo aggiunto dalla classe Vongola. 24 Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Final 25 Esistono metodi che per loro natura non dovranno mai essere modificati, in particolare non dovrà essere permesso di fare overriding su questi metodi. Possiamo usare la parola chiave final per indicare che un metodo non può essere ridefinito nelle classi derivate; ad esempio: public class Pesce extends Animale { public final void interroga( ) { System.out.println(“...”); } } Anche la classe Pesce eredita dalla classe Animale, e come tutti gli oggetti di classe Animale può essere interrogato, pertanto si ridefinisce il metodo interroga(). Ma i pesci sono muti, e il metodo stampa solo tre puntini di sospensione. Possiamo anche scrivere altre classi che derivano da Pesce, esempio, un Branzino, ma tutte le sottoclassi di Pesce avranno una limitazione: il metodo interroga() di Pesce è stato dichiarato final, quindi non può essere ridefinito in una sottoclasse. Dal punto di vista del design, la cosa ha perfettamente senso: quando abbiamo scritto la classe Pesce abbiamo deciso che tutti i pesci sono muti, e non vogliamo che qualche sottoclasse trasgredisca a questa regola. Se la classe Branzino provasse a fornire una propria implementazione del metodo interroga(), il risultato sarebbe un errore di compilazione. Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Final(2) Se ci sono motivi di design particolari, possiamo anche dichiarare final un’intera classe: public final class Uomo extends Animale { public void interroga( ) { System.out.println(“So’ er mejo dell’evoluzione!”); } public void inquina() { // … } } Ora, nessuna classe può ereditare da Uomo, provateci e otterrete un errore di compilazione. Naturalmente, tutti i metodi di una classe final sono implicitamente final. Avevano già visto la parola chiave final in una delle prime lezioni, vi ricordate? Quando abbiamo parlato delle costanti…l’utilizzo di final è lo stesso, si vuole impedire che qualcuno modifichi il valore contenuto in una variabile dopo la sua inizializzazione con un certo valore. 26 Istituto Statale di Istruzione Superiore “F. Enriques” Corso di Programmazione in Java – Lezione n° 05 Vantaggi dell’Ereditarietà e del Polimorfismo. L’Ereditarietà ed il Polimorfismo hanno diversi vantaggi, ma occorre conoscerli bene per apprezzarli a fondo. In caso contrario si rischia di applicarli in modo inutile, o forse anche dannoso. Sappiate comunque che i programmi che usano bene il polimorfismo e l’ereditarietà sono più facili da espandere e modificare, infatti di solito i problemi più grandi non si incontrano quando si scrive un programma, ma quando lo si modifica, di solito per aggiungere funzionalità nuove. Questa operazione non è quasi mai indolore, e spesso costringe a riscrivere intere parti del programma, nei casi peggiori, il programma diventa così disordinato e complesso che conviene buttarlo via e ricominciare da zero. Nessuna tecnica può, da sola, risolvere questo problema, ma il polimorfismo e l’ereditarietà sono due degli strumenti che possono rendere l’aggiornamento dei programmi molto più facile. 27 Istituto Statale di Istruzione Superiore “F. Enriques”