Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
vuoi
o PayPal
tutte le volte che vuoi
Problemi legati all'overriding
Alcuni problemi possono essere legati all'overriding: se si ha fatto override di un metodo di A in B1 e B2, quale versione si ha in C?
Tutti i problemi presentati sono legati alla semantica.
La soluzione all'ereditarietà multipla
Java supporta l'ereditarietà multipla di interfaccia, ma solo l'interfaccia singola di implementazione.
Ritornando all'ereditarietà singola...
Correttezza e robustezza
Due aspetti da prendere in considerazione legati alle classi sono:
Correttezza: quanto un codice è stabile, privo di errori
Robustezza: quanto la classe è in grado di reggere successive evoluzioni nelle classi figlie per poter essere sostituibile e continui a mantenere la sua correttezza
La correttezza è un problema statico, ovvero quali sono le modalità di implementazione che consentono ad una classe di comportarsi in modo corretto.
La robustezza è una questione dinamica, ovvero come implementare una classe in modo da...
consentirle di tollerare l'evoluzione e le successive versioni delle sue superclassi e sottoclassi.
Il problema della classe base fragile (FBC)
Come si può far evolvere una classe senza che questo provochi problemi di funzionamento alle sue sottoclassi?
La stretta dipendenza fra una classe base e sottoclassi sviluppate in contesti indipendenti viene chiamata problema della classe base fragile.
Questo problema possiede due aspetti, uno legato alla sintassi (interfacce) e uno legato alle implementazioni: si parla di FBC sintattico e FBC semantico.
Il problema della classe base fragile sintattico
L'idea è che una classe non dovrebbe richiedere una ricompilazione solo a causa di cambiamenti sintattici all'interfaccia delle sue superclassi.
Java consente di compilare solo le classi che sono cambiate, senza dover ricompilare l'intero progetto, mentre in C++ si è costretti a ricompilare l'applicazione. È possibile quindi inserire solo le classi che
è che se si cambia l'ordine dei metodi nella VMT, la classe derivata salta ad una posizione sbagliata. Questo problema è noto come "classe base fragile sintattico". Una soluzione a questo problema è quella di utilizzare le tabelle di dispatch dei metodi (virtual method tables) durante il caricamento delle applicazioni anziché a tempo di compilazione. Questa è la soluzione adottata da Java, che non soffre del problema della classe base fragile sintattico.L'aspetto semantico è legato al fatto di come una classe può rimanere valida in presenza di cambiamenti dell'implementazione di una superclasse. Il problema è che i metodi di una sottoclasse hanno un accesso completo alla parte di stato ereditato dalle superclassi e ai metodi non pubblici, quindi possono operare su dettagli implementativi che potrebbero cambiare in una versione successiva della superclasse. Si ha una rottura dell'incapsulamento, in quanto è possibile accedere a quello che succede nella classe padre dentro alla classe figlia, operando in qualcosa che se cambiato può dare problemi.
Soluzione parziale al FBC semantico
La soluzione è quella di introdurre un'interfaccia di specializzazione, oltre alla classica interfaccia d'uso: è l'insieme dei campi e dei metodi marcati come protected nelle classi Java ed è pensata per la derivazione di sottoclassi. Si ha quindi un incapsulamento a tre
livelli:
- private: accessibile solo ai metodi di una classe
- protected: accessibile anche alle sottoclassi
- public: accessibile a tutti
Trattandosi di una questione semantica, l'introduzione dell'interfaccia protected
non risolve però completamente il problema.
Esempio di un problema semantico
Si ha una classe che stampa un documento ma senza stampare il numero della pagina. Si crea quindi una sottoclasse che ridefinisce il metodo di stampa aggiungendo il numero della pagina, ma dato che l'array di pagine parte da zero bisogna sommare sempre 1, per avere il numero di pagina corretto.
Se l'autore della classe padre decide di modificare l'implementazione in modo che il numero della pagina diventa effettivamente quello corretto, si ha che la classe figlia stampa il numero di pagina+1, che non è corretto.
Si ha quindi che l'ereditarietà è un meccanismo troppo fragile: la soluzione più nota è quella basata sul meccanismo di
composizione fra oggetti.
Composizione e forwarding
L'idea è che quando un oggetto non ha modo di eseguire un compito localmente, può inviare messaggi ad altri oggetti chiedendo supporto. Se questi oggetti di supporto sono parte dell'oggetto chiamante, si parla di composizione fra oggetti.
L'invio di un messaggio da un oggetto all'altro viene chiamato forwarding. Lo scambio di messaggio serve per comunicare cosa deve essere fatto.
Il forwarding delega la classe interna a fare delle operazioni senza riciclare il codice nella classe esterna, ovvero sfrutto quello che c'è scritto dentro ai metodi della classe figlia.
Limiti del forwarding
La combinazione di composizione fra oggetti e forwarding risulta simile all'ereditarietà: l'oggetto esterno non reimplementa le funzionalità dell'oggetto interno, per cui si ha il riuso se l'implementazione dell'oggetto interno viene migliorata, il miglioramento si trasmette.
che i dati passati dalla Classe1 alla Classe2 siano dati manipolabili dai metodi della Classe2. Delega La delega differisce dal forwarding in due aspetti:- i messaggi contendono un riferimento al chiamante (sender): i metodi di Classe2 hanno come parametro proprio l'interfaccia!
- il delegato deve poter accedere ad un'interfaccia di delega esposta dal delegante
Che il dato sia manipolabile. Quindi si ha che: la Classe1 implementa l'interfaccia di delega, passa se stessa (this) alla Classe2 che usa l'interfaccia, per cui si è sicuri che il dato sia manipolabile. Il parametro del metodo di Classe2 deve essere dello stesso tipo dell'interfaccia.
Ereditarietà e delega: conclusioni
Il passaggio del riferimento al sender assieme all'interfaccia di delega consentono di simulare il self comune e quindi di superare le limitazioni del forwarding. Si ha però che, quando si cerca di riprodurre le caratteristiche più potenti dell'ereditarietà, emergono gli stessi problemi di accoppiamento che sono alla causa del problema della classe base fragile. Il problema è generale: i meccanismi molto potenti sono anche potenzialmente pericolosi e richiedono cautela. Oggi si tende a riconoscere alla delega una funzione complementare all'ereditarietà di implementazione.
Esempio Sortable3-4 Java
ReflectionLa Reflection è la capacità di esplorare dinamicamente (a tempo di esecuzione) la struttura degli oggetti, delle classi e delle interfacce e di interagire con essi.
Si distinguono due aspetti:
- Introspezione: esplorazione di classi, oggetti e interfacce. È consentita grazie al fatto che la struttura delle classi viene salvata in un'area dei metadati.
- Interazione dinamica: con classi e oggetti. È la possibilità di eseguire dei metodi in modo dinamico, ad esempio evocando il metodo con una stringa contenente il suo nome.
Costituisce la base del modello di componenti di Java: i Java Beans.
Quando si utilizza un componente di terze parti e lo si ingloba nel progetto, per sapere come usarlo si può:
- Utilizzare il codice sorgente (non sempre possibile).
- Se si ha solo il codice oggetto, basta sapere come si chiama per scoprire dinamicamente attributi e metodi da utilizzare.
Gli IDE consentono di aggiungere questi componenti e leggere attraverso la reflection.
come è fatta la classe.Introspezione Per un oggetto, è la capacità di determinare la classe a cui appartiene. Per una classe, è la possibilità di ottenere informazioni su attributi, metodi, costruttori, interfacce, superclasse e modificatori. Per un interfaccia, è la possibilità di scoprire i metodi e le costanti contenute nella dichiarazione. Interazione dinamica Consente di: - Creare un'istanza di una classe il cui nome è noto solo a runtime - Elencare le interfacce implementate da una classe - Enumerare i costruttori, i metodi e i campi definiti da una classe - Leggere e scrivere il valore di un attributo di un oggetto in base al nome - Invocare un metodo in base al nome - Creare un nuovo array la cui dimensione e il cui tipo di elementi sono noti solo a runtime - Modificare i componenti di un array Caricamento dinamico di una classe Per le classi note a tempo di compilazione, il sistema Java provvede automaticamente al caricamento. Se la classe èsconosciuta a tempo di compilazione ma il suo nome è notoruntime, si può caricare dinamicamente:<Class c = Class.forName("MyClass");>
L'invocazione del metodo Class restituisce un puntatore all'oggetto.
È il meccanismo utilizzato da JDBC per caricare il driver di uno specifico DBMS:
<Class.forName("MySQL.JDBCDriver");>
La classe JDBCDriver può essere sconosciuta al momento in cui si compila il programma; basta cambiare il parametro per cambiare driver dinamicamente.
La classe Class è un descrittore di tipo.
Un'istanza di Class consente di accedere alle informazioni relative a una classe o a un'interfaccia.
<Class c = cnt.getClass();>
C è un puntatore che contiene i metadati dell'oggetto su cui è.