Tecnologia OO
Information Hiding Attributi nascosti accessibili mediante opportuni metodi
ereditarietà
Costruzione modulare e incrementale Il software dovrebbe essere protetto, riusabile, documentato, modulare, incrementalmente estendibile L’OBIETTIVO costruzione modulare e incrementale del software ottenuta per estensione / specializzazione di componenti tecnologia component-based LE NECESSITÀ servono strumenti per la costruzione modulare e incrementale del software
UN MECCANISMO RISOLUTIVO: EREDITARIETÀ l’ereditarietà serve per non ripartire da zero quando serve una nuova classe, permettendo di poterla definire a partire da una già esistente nella logica del riuso
Java: gerarchia di classi ereditarietà singola Tutte le classi Java ereditano da Object Ereditarietà singola: ogni classe, unica superclasse
Ereditarietà: sottoclassi Bisognerà specificare ciò che distingue la classe figlia dalla madre: cosa la nuova classe ha in più rispetto alla precedente sia in termini di dati, sia in termini di operazioni cosa la nuova classe ha di diverso rispetto alla precedente sia in termini di operazioni. ogni classe derivata eredita tutte le proprietà della classe base di partenza.
Esempio: classe Persona e sottoclassi
Ereditarietà: sottoclassi LA CLASSE DERIVATA PUÒ: aggiungere nuovi campi dati e nuovi metodi a quelli ereditati dalla madre ridefinire alcuni dei metodi ereditati dalla madre POLIMORFISMO LA CLASSE DERIVATA NON PUÒ: eliminare campi dati o metodi (comportamento monotono, si accresce sempre) Ridefinire (sovrascrivere) Sovraccaricare
Ereditarietà : costruttori Per costruire un oggetto di una classe derivata, è sempre opportuno chiamare un costruttore della classe madre, in quanto: solo il costruttore della classe madre può sapere come inizializzare i campi-dati ereditati da tale classe in modo corretto è il solo modo per garantire l’inizializzazione di campi-dati privati (a cui la sottoclasse non può accedere direttamente) si evita una inutile duplicazione di codice nella sottoclasse. riuso
Ereditarietà : costruttori Quale costruttore della classe madre viene chiamato? il costruttore di default definito da Java (se noi non lo definiamo) chiama il costruttore di default della classe madre (se questo non esiste si ha errore). Tale costruttore della classe figlia è pubblico ed invoca il costruttore della superclasse per i costruttori definiti dall’utente occorre specificare esplicitamente quale costruttore della classe madre vada chiamato, mediante la notazione super(..), che deve essere la prima istruzione nel corpo del costruttore. Se manca, Java di solito inserisce automaticamente una chiamata al costruttore di default della classe madre cioè super() non parametrico
Ereditarietà: sintassi in Java public class <Nuova_(sotto)classe> extends <Vecchia_(super)classe> { <nuove_variabili_istanza> <nuovi_metodi> } public class Donna extends Persona{ public class Uomo extends Persona{
Ereditarietà: omonimie NB: this è un riferimento all’istanza corrente super è un riferimento alla classe madre Sono espressioni lecite: this.val che indica il campo val della classe dell’istanza corrente this.f() che richiama il metodo f() della classe dell’istanza corrente super(..) che richiama un costruttore parametrico della classe madre super.val che indica il campo val della classe madre super.f() che richiama il metodo f() della classe madre Le ultime due notazioni sono utili se la sottoclasse definisce un campo dati o un metodo omonimo con uno della classe madre
Visibilità Nella classe madre: Perciò, la classe derivata: ciò che è privato è visibile solo ai metodi della classe stessa ciò che è di package è visibile solo ai metodi delle classi dello stesso package ciò che è pubblico è visibile a tutti Perciò, la classe derivata: non può vedere la parte privata della madre, appunto perché privata; può vedere la parte di package della madre, purché la nuova classe sia definita nello stesso package vede, ovviamente, la parte pubblica della madre, in quanto visibile a tutti. Ma non basta…..
Protected Per sfruttare davvero l’ereditarietà occorre superare la rigidità dei tre livelli di visibilità privato / pubblico / package: occorre una visibilità specifica per le classi derivate protected
Protected Un campo dati o un metodo protected: è visibile alle classi derivate indipendentemente dal package in cui esse sono definite per il resto si comporta come la visibilità package.
Protected La qualifica protected: occorre dunque valutare con attenzione quando sia opportuna. rende visibile un attributo a tutte le sottoclassi di quella classe, presenti e future perciò, costituisce un permesso di accesso “indiscriminato”, valido per ogni possibile sottoclasse che possa in futuro essere definita, senza possibilità di distinzione.
polimorfismo ereditarietà
POLIMORFISMO Una funzione si dice POLIMORFA se è capace di operare su oggetti di tipo diverso specializzando il suo comportamento in base al tipo dell’oggetto su cui opera Ad esempio il metodo fai_verso() per un cane sarà abbaiare, per un gatto miagolare.
POLIMORFISMO: forma diversa I linguaggi a oggetti hanno intrinseco il polimorfismo, in quanto possono esistere, in classi diverse, funzioni con lo stesso nome ma con effetti completamente diversi: operazioni di overriding (ridefinizione di metodi in una sottoclasse) oppure operazioni di overloading (definizione di metodi con lo stesso nome ma differente numero e/o tipo di parametri; ne sono tipico esempio i diversi costruttori di una classe)
parametri diversi stessa firma
Sovraccaricare un metodo (stesso nome, diversi parametri) Ridefinire un metodo Sovraccaricare un metodo (stesso nome, diversi parametri) http://pages.di.unipi.it/corradini/Didattica/LIP-07/Ereditarieta/ClasseObject/main.html
Override: tecnica per sovrascrivere public class Persona { public String toString (){ String p = " Classe Persona "; System.out.println(p); return p; } } public class Figlia extends Persona { String f = " Classe Figlia"; System.out.println(f); return f; Metodi con stessa firma
Override: regole È bene notare che ci sono delle regole da rispettare per l'override: 1) se si decide di riscrivere un metodo in una sottoclasse, dobbiamo utilizzare la stessa identica segnatura, altrimenti utilizzeremo un overload in luogo di un override. 2) il tipo di ritorno del metodo deve coincidere con quello del metodo che si sta riscrivendo. 3) il metodo ridefinito, non deve essere meno accessibile del metodo che ridefinisce. Per esempio se un metodo ereditato è dichiarato protetto, non si può ridefinire privato, ma semmai, pubblico.
Overriding massima espressione di Polimorfismo public class Persona { protected int anni=0; // Verrà ereditato da Uomo, è protected public void setAnni(int anni){ this.anni=anni; } } //fine class Persona public class Uomo extends Persona{ public void setAnni (int anni) { // Tutto uguale, overriding this.anni=anni*2; } public void setAnni (double anni){ // Non c'è overriding (lista parametri diversi), // ma c'è overloading //... neonato 0.5 } } // fine class Uomo
Overloading: tecnica per sovraccaricare Metodi con lo stesso nome public int somma(int a, int b){ return a + b; } public double somma(double a, double b){ return a + b; } lista di argomenti differente
Un altro esempio Codice commentato e scaricabile
Un altro esempio: Persona public class Persona{ String nome; // public visibilità massima String indirizzo; // public public Persona(){ this("John Doe","ignoto"); } public Persona(String nome) { this(nome,"ignoto"); } public Persona(String nome, String indirizzo){ this.nome = nome; this.indirizzo = indirizzo; } public String getNome() { return nome; } public String getIndirizzo() { return indirizzo; } public void visualizza(){ System.out.println("Nome: " + nome + "\nIndirizzo: " + indirizzo); } public boolean omonimo(Persona p){ return this.nome.equalsIgnoreCase(p.nome); } ... }
Un altro esempio: Studente ereditati aggiunti
Studente … senza riuso public class Studente extends Persona { // Studente eredita variabili e metodi da Persona int matricola; // nuova variabile istanza .. per default zero String pianoDiStudio; // nuova variabile istanza … // si vedrà in seguito l’uso di variabili static (di classe) // Costruttore parametrico non opportuno NB: i costruttori non si ereditano public Studente(String nome, String indirizzo) { this.nome = nome; this.indirizzo = indirizzo; this.pianoDiStudio = ""; } // nuovo metodo public String getPdS() { return pianoDiStudio;} // nuovo metodo public void modificaPdS(String nuovoPdS) { pianoDiStudio += nuovoPdS + "\n"; } …}
Overriding (sovrascrittura) public class Studente extends Persona { ... // metodo sovrascritto …. senza riuso public void visualizza() { System.out.println("Nome: "+ nome + "\nIndirizzo: " + indirizzo +"\nMatricola: " + matricola + "\nPiano di Studio: " + pianoDiStudio); } }
Super Con riuso public class Studente extends Persona { … // Costruttore parametrico … con riuso public Studente(String nome, String indirizzo) { super(nome, indirizzo); // invoca costruttore della superclasse this.pianoDiStudio = ""; } //... } // metodo sovrascritto … con riuso public void visualizza() { super.visualizza(); // riuso System.out.println("Matricola: " + matricola + "\nPiano di Studio: " + pianoDiStudio); } // ...
Inclusione su di un'istanza di Studente (classe figlia) possono essere invocati anche i metodi della superclasse Persona più in generale, è possibile utilizzare un'istanza di una sottoclasse (Studente) dovunque sia richiesto un oggetto della superclasse (Persona), come in un assegnamento o nel passaggio di parametri. Persona tizio = new Studente("Mario Rossi", “via Pisa"); /* corretto: adesso su tizio posso invocare i metodi di Persona, grazie all'ereditarietà */ tizio. visualizza() ; Studente pippo = new Studente("Pinco Pallino", “via Empoli"); ... if ( tizio.omonimo (pippo) ) ... /* corretto: posso passare pippo come argomento attuale, anche se, nel metodo, il parametro era definito di tipo Persona */
Binding (legame) dinamico in Java Il meccanismo che determina quale metodo deve essere invocato in base alla classe di appartenenza dell'oggetto si chiama binding (legame). Esiste una distinzione tra: Binding statico o early binding: il metodo da invocare viene determinato a tempo di compilazione. Binding dinamico o late binding: il metodo viene determinato durante l'esecuzione.
Binding (legame) dinamico in Java … // cond è un intero letto da tastiera Persona tmp; if (cond > 0) tmp = new Persona("Aldo Bianchi", "via Roma"); else tmp = new Studente("Mario Rossi", "via Pisa"); tmp.visualizza(); Nell'ultimo comando, il compilatore non può sapere se a tmp sarà associata un'istanza di Persona o di Studente, perché questo dipenderà dal valore fornito dall'utente. Il binding dinamico demanda la scelta del metodo da invocare all'interprete.
Applicazione di test
Come scoprire a quale classe appartiene un oggetto Il predicato instanceof permette di controllare la classe di appartenenza di un oggetto La condizione (<unOggetto> instanceof <UnaClasse>) restituisce true se e solo se <unOggetto> è una istanza della classe <UnaClasse> ... if (tizio instanceof Studente) // ... se vero tizio è istanza di Studente
Un altro esempio: Professore Con riuso public class Professore extends Persona{ String ruolo; int stipendio; String corsiAffidati; public Professore(String nome, String indirizzo, Strin ruolo) { // costruttore super(nome, indirizzo); // riuso this.ruolo = ruolo; this.corsiAffidati = ""; } public void setStipendio(int stipendio) { this.stipendio = stipendio; } public void aumentaStipendio(int aumento) { this.stipendio += aumento; } public void aggiungiCorso(String corso) { corsiAffidati += corso + "\n"; } public void visualizza(){ super.visualizza(); // riuso System.out.println("Ruolo: " + ruolo + "\nCorsi affidati: " + corsiAffidati); } // …}
Esercizio Progettare nell'ottica della OOP e realizzare una classe Java con nome UtilityArray che "specializza" la classe Matrice da cui eredita tutti i metodi ed "estende" la classe genitrice aggiungendo nuovi metodi necessari per implementare alcune classiche operazioni su un array monodimensionale di interi: • inserire i valori degli elementi leggendoli da tastiera (metodo ereditato) • visualizzare i valori degli elementi su video • trovare il valore massimo tra gli elementi • trovare il valore minimo tra gli elementi • effettuare la somma dei suoi elementi e calcolare il valor medio
Esercizio Inoltre proporre un'applicazione Java con nome TestUtilityArray che crea un array di 10 interi e testa le funzionalità della classe UtilityArray Specifiche La superclasse Matrice deve possedere: • attributo visibile alle classi figlie (protected): l’array di interi • un costruttore di default (che crea l'array di interi con dimensione fissa pari a 10) • altro costruttore che permette di creare l'array di interi con dimensione passata come parametro • un metodo pubblico leggiInt() per leggere da tastiera un valore intero • un metodo pubblico inizio() che inizializza l'array con valori letti da tastiera • un metodo pubblico stampa() per visualizzare su video gli elementi dell'array
Esercizio La classe UtilityArray deve possedere: • un costruttore di default (che crea l'array di interi con richiamo alla superclasse e inizializza l'attributo privato cioè la dimensione o numero di elementi dell'array) • altro costruttore con richiamo alla superclasse per creare l'array di interi con dimensione passata come parametro • metodi di accesso (in particolare, setDim() che modifica la dimensione, crea un nuovo array con la lunghezza desiderata) • un metodo inizio() che ridefinisce quello della superclasse ed inizializza l'array con valori decrescenti • metodi per trovare il valore massimo e minimo tra gli elementi • metodo per sommare i valori degli elementi e calcolare il valor medio