Programmazione orientata agli Oggetti
Classi Le classi sono uno strumento per costruire strutture dati che contengano non solo dati ma anche il codice per gestirli. Come tutti i costrutti che permettono di definire le strutture dati, una classe definisce un nuovo tipo di dato. I membri di una classe sono dati (esattamente come i membri di un record), chiamati attributi, e metodi, ovvero procedure, che operano su un oggetto. Dal punto di vista matematico, una classe definisce un insieme in modo intensivo, ovvero definendone le caratteristiche invece che elencandone gli elementi. Se l'accesso agli attributi è ristretto ai soli membri della classe, le caratteristiche dell'insieme possono includere vincoli sui possibili valori che la tupla degli attributi può o non può assumere, e anche sulle possibili transizioni tra questi stati. Una classe può dichiarare riservate una parte delle sue proprietà e/o dei suoi metodi, e riservarne l'uso a sé stesso e/o a particolari tipi di oggetti a lui correlati.
Oggetto Un oggetto è una istanza di una classe. Un oggetto occupa memoria, la sua classe definisce come sono organizzati i dati in questa memoria. Ogni oggetto possiede tutti gli attributi definiti nella classe, ed essi hanno un valore, che può mutare durante l'esecuzione del programma come quello di qualsiasi variabile. Il paradigma OOP suggerisce un principio noto come information hiding che indica che si debba accedere agli attributi dell'istanza solo tramite metodi invocati su quello stesso oggetto. Sintatticamente, i metodi di una classe vengono invocati "su" un particolare oggetto, e ricevono come parametro implicito l'oggetto su cui sono stati invocati. Questo parametro normalmente può essere referenziato esplicitamente; per esempio, a tale scopo in C++, inJava, e in C# si usa la parola chiave this, mentre in Smalltalk, in Objective-C, Python e in Ruby si usa la parola-chiave self.C++JavaC#SmalltalkObjective-CPythonRuby Gli oggetti effettivamente creati sono membri dell'insieme definito dalla loro classe. Molti linguaggi forniscono un supporto per l'inizializzazione automatica di un oggetto, con uno o più speciali metodi detti costruttori.
L'incapsulamento L'incapsulamento è la proprietà per cui un oggetto contiene ("incapsula") al suo interno gli attributi (dati) e i metodi (procedure) che accedono ai dati stessi. Lo scopo principale dell'incapsulamento è appunto dare accesso ai dati incapsulati solo attraverso i metodi definiti, nell'interfaccia, come accessibili dall'esterno. Gestito in maniera intelligente, l'incapsulamento permette di vedere l'oggetto come una black-box, cioè una scatola nera di cui, attraverso l'Interfaccia sappiamo cosa fa e come interagisce con l'esterno ma non come lo fa. I vantaggi principali portati dall'incapsulamento sono: robustezza, indipendenza e l'estrema riusabilità degli oggetti creati...
Ereditarietà L'OOP prevede un meccanismo molto importante, l'ereditarietà, che permette di derivare nuove classi a partire da classi già definite. L'ereditarietà permette di aggiungere membri ad una classe, e di modificare il comportamento dei metodi, in modo da adattarli alla nuova struttura della classe. Da una stessa classe è possibile costruire diverse classi derivate. Da una classe derivata è possibile derivarne un'altra con lo stesso meccanismo. Sintatticamente, una classe può essere definita come derivata da un'altra classe esistente. In molti linguaggi la classe derivata, o sottoclasse, eredita tutti i metodi e gli attributi della classe "genitrice", e può aggiungere membri alla classe, sia attributi che metodi, e/o ridefinire il codice di alcuni metodi. L'ereditarietà può essere usata come meccanismo per gestire l'evoluzione ed il riuso del software: il codice disponibile definisce delle classi, se sono necessarie modifiche, vengono definite delle sottoclassi che adattano la classe esistente alle nuove esigenze.
Sottotipazione Se un oggetto di una sottoclasse può essere utilizzato al posto di un'istanza della superclasse, il tipo della classe derivata è detto sottotipo. Questo richiede che tutti i metodi della superclasse siano presenti nella sottoclasse, e che le signature siano compatibili. Di conseguenza, una sottoclasse che voglia definire un sottotipo può ridefinire i metodi della superclasse, ma non può eliminarli sintatticamente né modificare le loro signature. In numerosi linguaggi, invece, una sottoclasse può decidere di eliminare o cambiare le proprietà di accesso ad un metodo, il che fa sì che l'operazione di subclassing non sia corrispondente a quella di subtyping.
Esempio di Ereditarietà: I mammiferi
Classi in java
Classi e Oggetti Java Esclusi i tipi primitivi (boolean, char, byte, short, int, long, float, double), in Java esistono solo: CLASSI – componenti software che possono avere i loro dati e le loro funzioni – vengono usate come “struttura” per costruire oggetti OGGETTI – entità dinamiche costruite al momento del bisogno secondo la definizione di una classe che ne descrive le proprietà
Nota Gli Oggetti sono elementi distinti di una classe che condividono delle caratteristiche. Ogni Metodo è un insieme di istruzioni che una classe o un oggetto rendono disponibili ad altre classi e/o oggetti, affinché possa essere eseguito su richiesta. Non occorre preoccuparsi della distruzione degli oggetti: Java ha un garbage collector.
Definire una classe in java equivale al creare un nuovo tipo di dato (una nuova classe di oggetti)
Attributi ▪Gli attributi di una classe definiscono la struttura dello stato degli oggetti che vi appartengono ▪Ogni attributo è definito usando la stessa sintassi usata per la definizione delle variabili locali ▪Convenzioni: scrivere gli attributi in minuscolo (tecnologia e non Tecnologia) ▪Sintassi: ogni attributo è definito usando la stessa sintassi usata per la definizione delle variabili locali
Attributo
Metodi
Ricapitoliamo.... Nella classe vengono dichiarati: ▪i costruttori, che determinano le modalità di creazione degli oggetti; ▪le variabili o attributi di istanza, che costituiscono lo stato degli oggetti; ▪i metodi di istanza, che realizzano le funzionalità degli oggetti.
Quando Creiamo un oggetto con il costruttore e l'operatore new "cosa succede ?" La creazione di un oggetto è un processo costituito dalle seguenti fasi: Allocare lo spazio di memoria necessario a contenere l'oggetto Inizializzare le variabili di istanza dell'oggetto Per contenere un oggetto la quantità di memoria necessaria è determinata soltanto dalla dichiarazione della classe (precisamente, dal numero e dal tipo delle variabili di istanza) Lo spazio necessario viene allocato in una zona di memoria chiamato heap.
Operatore New L’operatore new è alla base del linguaggio Java. Mediante esso viene creata una nuova istanza di un oggetto appartenente ad una determinata classe e, conseguentemente, viene allocata automaticamente la memoria necessaria nell’heap per conservare tale istanza. L’operatore new, per eseguire la creazione di un oggetto, invoca il costruttore della classe che si desidera istanziare.
Cosa abbiamo fatto fino ad ora
Esempio di classe public class Contatore { private int valore; public String tecnologia; public void incrementa (){ valore++; } public void reset() { valore=0; } public int getValore() { return valore=0; } public Contatore() { valore=0; }
Visibilità di metodi e attributi
Modificatori di Accesso. I modificatori sono parole riservate che forniscono al compilatore informazioni sulla natura del codice, dei dati e delle Classi contenuti nei file sorgenti. Fra tutti i modificatori alcuni possono essere raggruppati in una singola categoria che ne specifica il comportamento, essi sono i modificatori di accesso: ▪private ▪protected ▪public In assenza di questi tre, un elemento di programma viene considerato package-local o friend (si dice che assume la visibilità di default).
Private private è il modificatore di accesso più restrittivo. I metodi e gli attributi dichiarati private, non sono visibili né usabili all'esterno della classe che li contiene. Due oggetti della stessa classe però, possono vedere i rispettivi attributi e metodi privati. Tuttavia è possibile accedervi dall'esterno mediante metodi di lettura e modifica dei valori: metodi "get" e "set". La possibilità di usare valori con il modificatore private è un punto importante della programmazione a oggetti. In questo modo i valori private vengono incapsulati, rendendo impossibile il loro richiamo o modifica da parte di altre porzioni di codice, a causa di disattenzione o casualità, o perfino di codice malevolo. Ovvero per leggerli o modificarli occorre implementare e chiamare appositamente i metodi get e set.incapsulati Dunque ove possibile bisogna sempre editare come private gli elementi del programma, è una indicazione forte ma non un obbligo.
Protected Il modificatore protected può essere attribuito solo ai metodi e alle variabili interne alla classe e non può essere applicato alla classe stessa. I metodi e le variabili dichiarate come protected sono visibili da classi dichiarate nello stesso package e da sottoclassi e classi derivate dichiarate ovunque. Si può dire che è leggermente più restrittivo di public.
Default Si applica a tutti gli elementi della classe e ad essa stessa. Il modificatore di default viene assegnato automaticamente dal compilatore solo quando nella scrittura del sorgente si omettono gli altri modificatori. Di default indica che l'elemento è di accesso pubblico ma solo ed esclusivamente al package della classe dove è inserito, diversamente da public e protected dove è visibile anche in package esterni. Si può dire che è leggermente più restrittivo di protected.
Public Il modificatore public può essere attribuito a tutti gli elementi di una classe. public definisce l'elemento del programma come "pubblico" e quindi questo è visibile e modificabile dall'esterno della classe, da qualsiasi package.
Ereditarietà
Principio che consente a una classe di ereditare le variabili e i metodi di un’altra classe Consente di raffinare le caratteristiche di classi di oggetti a partire da classi generali riutilizzo del codice
Superclasse Sottoclasse
Esempio
A cosa serve l'ereditarietà Permette di riutilizzare il codice delle classi già definite ▪Posso definire un nuovo Pesce senza preoccuparmi di dover ridefinire come respira ▪Le nuove classi aggiungono funzionalità ma sono COMPATIBILI con le vecchie ▪Posso definire un nuovo tipo di Uccello e utilizzarlo come se fosse un Uccello o un Animale e basta ▪Posso non dover/voler conoscere il tipo specifico! principio di sostituibilità
Sintassi
Costruttori e operatore super La classe derivata eredita tutte le variabili e i metodi della classe base La classe derivata NON eredita il costruttore Se la classe base ha un costruttore senza parametri la classe derivata richiama quello Se la classe base NON ha un costruttore senza parametri la classe derivata deve esplicitamente richiamarlo: super
Classe astratta Una classe astratta non può in alcun modo essere istanziata, quindi può essere utilizzata esclusivamente come classe base. Quando estendiamo (o deriviamo) da una classe astratta, la classe derivata deve fornire una implementazione per tutti i metodi astratti dichiarati nella classe genitrice; se così non dovesse essere, anche la sotto-classe deve essere dichiarata come abstract.
Abstract Una classe astratta (abstract) è una classe di cui non possono essere istanziati oggetti Possono essere istanziati oggetti solo delle sue sottoclassi Permette di definire un comportamento generale
MyProcess myProcess = new MyProcess(); // AbstractProcess myProcess = new MyProcess(); // equivalente alla precedente myProcess.process(); public abstract class AbstractProcess { public void process() { init(); baseAction(); stepAfter(); } public void init() { // metodo dichiarato direttamente // all'interno della classe astratta } public abstract void baseAction(); // metodo astratto la cui implementazione // è demandata alla sottoclasse public void stepAfter() { // metodo dichiarato direttamente // all'interno della classe astratta } class MyProcess extends AbstractProcess public void baseAction() { // implementazione del metodo baseAction() } Notiamo che il metodo baseAction() nella classe AbstractProcess è astratto, quindi la sottoclasse che la estende deve effettuarne l’Overidde e darne l’implementazione (altrimenti anch’essa dovrebbe essere dichiarata abstract). A questo punto la chiamata al metodo process():
Interfaccia Molto spesso le interfacce vengono confuse con le classi astratte dato che dal punto di vista logico sono molto simili, ma le interfacce possono essere intese come un'evoluzione delle classi astratte e permettono, di fatto, di simulare l'ereditarietà multipla. Il vantaggio principale di una interfaccia, essendo comunque una classe anche se con proprietà particolari, è quello che oltre ad essere estesa può essere implementata. La differenza tra estendere e implementare è molto grande in quanto una classe può essere eriditata solo ed esclusivamente una volta, mentre una classe può implementare infinite interfacce permettendo così l'ereditarietà multipla.
Vediamo adesso, dal punto di vista del codice, come si dichiara un'interfaccia e come la si implementa. La dichiarazione avviene usando la parola chiave interface in questo modo: public interface Prova { //contenuto } L'implementazione dell'interfaccia è invece possibile utilizzando la parola chiave implements: public class Implementazione implemets Prova { //contenuto }
Interfacce Classi astratte Istanziabileno Fieldssolo static finalsì Costruttorenosì Metodi staticiJava8+sì Dichiarazione metodi (virtual)nosì Implementazione metodijava8+ (con il qualificatore default)sì
Quando utilizzare l’interfaccia e quando la classe astratta? In base a quanto detto, cosa è meglio utilizzare? Ed in quali circostanze? ▪Si usa una classe astratta per condividere codice fra più classi, se più classi hanno in comune metodi e campi o se si vogliono dichiarare metodi comuni che non siano necessariamente campi static e final. ▪Si decide di utilizzare una interfaccia se ci si trova nella situazione in cui alcune classi (assolutamente non legate fra di loro) si trovano a condividere i metodi di una interfaccia, se si vuole specificare il comportamento di un certo tipo di dato (ma non implementarne il comportamento) o se si vuole avere la possibilità di sfruttare la “multiple inheritance”.
Polimorfismo Java
Cosa è esattamente il polimorfismo? Il polimorfismo ci permette di fare riferimento ad un’entità utilizzandone un’altra; il concetto in sè non è semplice da comprendere ma cerchiamo di analizzarlo più nel dettaglio, distinguendo due tipi di polimorfismo: ▪Polimorfismo per metodi ▪Polimorfismo per dati
Overriding (polimorfismo dei metodi)
Final
Casting
Un po' di esercizi
Ripassiamo OVERRIDING : si scrive in una sottoclasse un metodo della superclasse con la stessa segnatura OVERLOADING: è possibile definire metodi con lo stesso nome ma con segnature differenti Cos’è la SEGNATURA? È la «firma» del metodo costituita dal nome del metodo, dal numero dei suoi parametri e dal loro tipo Lo stesso nome di un metodo può essere usato per operazioni diverse, con definizioni diverse che richiedono un diverso tipo o numero di parametri.
Definire una classe Dipendente che ha i seguenti campi: Nome Cognome oreLavorativeMensili retribuzioneOraria Scrivere un metodo che calcola lo stipendio del dipendente nel seguente modo: Stipendio=oreLavorativeMensili * retribuzioneOraria Si definisca anche un particolare tipo di dipendente responsabile di progetto, al cui stipendio del dipendente (vedi sopra) viene aggiunto un bonus: Stipendio=oreLavorativeMensili * retribuzioneOraria + bonus Scrivere un main() che calcoli lo stipendio per una persona Dipendente e per una persona DipendenteResponsabile.
class Animale{ private String nome; public Animale(String s){ nome = s; } public String comeTiChiami() { return nome; } public void parla(){} public void incontra(Animale a){ System.out.println(nome + “: ”); parla(); }
Si costruiscano due classi Topo e Gatto che estendono la classe Animale. In particolare le due classi devono ridefinire il metodo parla(): Il topo deve ridefinire il metodo in modo che esso stampi sullo schermo «Squit» Il gatto deve ridefinire il metodo in modo che esso stampi sullo schermo «Miao» Si aggiunga ora nella classe Topo una nuova versione del metodo incontra() che prende come argomento un oggetto di tipo Gatto e stampa: System.out.println(nome + “: ”); Analogamente per il Gatto si aggiunga una nuova versione del metodo incontra() che prende come argomento un oggetto di tipo Topo e stampa: System.out.println(nome + “: ”); Scrivere un main() per verificarne il funzionamento