Sei sulla pagina 1di 64

Programmazione Object Oriented

Obiettivi: conoscere e saper utilizzare il


paradigma ad oggetti

• Analizzare e classificare la realtà


• Strutturare la classificazione
• Riconoscimento di pattern software

GianLuca DeMichelis - gdemichelis@e-ssentia.it


Prerequisiti

 Strutture dati
 Programmazione procedurale

 Familiarità con i concetti di array, lista, mappa, albero


Modellare la realtà

Automobile
Stato:
Motore_acceso: (booleano)
Marcia_inserita: (1,2,3,4,5,R)
Velocità: (valore numerico)
Comportamento:
Accendi_motore
Spegni_motore
Accelera
Frena
Sterza…
Modellare la realtà
Stato
L’insieme dei parametri caratteristici che
contraddistinguono un oggetto in un dato istante
Modellato come insieme di attributi
Comportamento
Descrive come si modifica lo stato a fronte degli
stimoli provenienti dal mondo esterno
Modellato come insieme di metodi
Classi
La classe costituisce il “progetto” di un oggetto
Specifica gli attributi
Descrive i metodi
Indica lo stato iniziale
Ogni classe ha un nome
Deve essere univoco
Oggetti
Data una classe, è possibile costruire uno o più oggetti
Gli oggetti vengono detti “istanze” della classe
In C# per creare un oggetto si utilizza la notazione
new NomeClasse ( );
Ogni oggetto “vive” all’interno della memoria del calcolatore
Qui viene memorizzato il suo stato
Oggetti differenti occupano posizioni differenti
Oggetti

•Ogni oggetto ha un proprio stato:


Insieme dei valori assunti dagli attributi dell’oggetto
Operando sui metodi, si può modificare lo stato
•All’atto della costruzione di un oggetto, occorre assegnare un
valore ai diversi attributi; questo è il compito di un metodo
particolare, detto costruttore
Oggetti: costruttori
Costruttore:
Metodo che inizializza gli attributi di una classe
Sintassi particolare:
Ha lo stesso nome della classe
Non indica nessun tipo ritornato
Oggetti: costruttori
• Normalmente un costruttore assegna valori “standard” agli
attributi
• Se ha dei parametri, può differenziare gli oggetti costruiti
Chi invoca il costruttore deve fornire i parametri richiesti
• Una classe può avere molti costruttori
Occorre che siano distinguibili attraverso il numero ed il tipo di
parametri richiesti
Riferimenti
 Si opera su un oggetto attraverso un riferimento
 Indica la posizione in memoria occupata dall’oggetto

 All’atto della costruzione, l’operatore new:


 Alloca un blocco di memoria sufficiente a contenere l’oggetto
 Invoca il costruttore, determinandone la corretta inizializzazione
 Restituisce il riferimento (indirizzo) del blocco inizializzato
Variabili
 I riferimenti possono essere memorizzati all’interno di variabili locali
 Devono avere un tipo compatibile con il tipo di riferimento che si intende
memorizzare al loro interno
 Devono essere dichiarate prima di essere usate

{…
Cerchio c;
c= new Cerchio();
…}
Ciclo di vita
delle variabili locali
 Le variabili locali “esistono” finché il metodo (blocco di codice) che le
definisce è in esecuzione
 Quando si incontra la loro definizione, il sistema riserva un’area di memoria per
ospitarne il contenuto
 Quando il metodo (blocco) termina, l’area viene rilasciata ed il contenuto della
variabile perso
 La memoria viene prelevata da una zona apposita detta “stack” (quella in cui si
trovano gli oggetti, invece, si chiama “heap”)
Riferimenti multipli
 Uno stesso oggetto può essere denotato da più variabili
 Succede quando si assegna ad un variabile il valore contenuto in un’altra variabile
 Le variabili condividono il riferimento allo stesso oggetto

 Se si opera sull’oggetto attraverso la prima variabile, le modifiche sono


visibili da tutte le altre variabili coinvolte
Riferimenti multipli
Memoria
Cerchio c1,c2;
c1= new Cerchio();
c2= c1;

r:1.0

c1

c2
Variabili elementari
 Non tutte le variabili contengono un riferimento
 Le informazioni più semplici possono essere memorizzate direttamente nella
variabile
 È il caso delle variabili il cui tipo è elementare (detto anche primitivo)
Copia di variabili elementari
 Se si assegna ad una variabile elementare il valore di un’altra variabile viene
eseguita una copia del valore
 I due valori diventano disgiunti e potranno evolvere indipendentemente
Riferimenti nulli
 Nel caso di variabili di tipo classe, a volte occorre indicare che non
contengono nessun valore
 In java ed in C# si usa la parola chiave null

 Se una variabile vale null, non è possibile accedere ai metodi o agli attributi
Riferimenti nulli
Memoria
Cerchio c;
c= new Cerchio();
...
c=null

r:1.0

c
Riferimenti nulli
Memoria
Cerchio c;
c= new Cerchio();
...
c=null

r:1.0

c
Accedere agli oggetti
 Noto il riferimento ad un oggetto, è possibile invocarne i metodi
 Si usa la notazione
variabile.metodo(...);
 Nelle parentesi si indicano gli eventuali parametri

 Se la variabile contiene un riferimento nullo si genera un errore di


esecuzione
 Il metodo è costituito da un insieme di istruzioni
 Il comportamento è simile ad una chiamata a procedura
 Il chiamante attende il completamento del metodo, poi prosegue la propria
elaborazione
Valori di ritorno
 Alcuni metodi restituiscono un valore
 Il tipo del valore ritornato viene dichiarato prima del nome del metodo
 double calcolaPerimetro() { … }

 Il valore ritornato può essere assegnato ad una variabile


 Occorre che la variabile sia compatibile con il tipo del valore ritornato

 Altri metodi non ritornano nulla


 In questo caso dichiarano di ritornare il tipo predefinito void
 void setRaggio(double r) { … }
Tipologie di metodi
 Un metodo, in generale, può operare liberamente sull’oggetto su cui viene
invocato…
 Modificandone lo stato
 Invocando metodi su altri oggetti conosciuti
 Effettuando calcoli
 Restituendo risultati

 Spesso, però, si progettano metodi specializzati in una delle funzioni citate


Tipologie di metodi
 Modificatori (mutator)
 Servono ad alterare, in modo controllato, lo stato dell’oggetto (o una sua
parte specifica)
 Di solito hanno parametri e non ritornano nulla
 Per convenzione, in Java, hanno un nome simile a
void setXyz(…);

 Lettori (accessor)
 Permettono di conoscere lo stato (o una sua parte specifca) di un oggetto
 Di solito, non hanno parametri, e ritornano il valore letto
 Per convenzione, in Java, hanno un nome simile a
<tipoRitornato> getXyz();
Attributi
 Come le variabili, anche gli attributi possono avere tipi
 Elementari
 Complessi (riferimenti ad oggetti)

 Un attributo di tipo elementare


 Contiene direttamente il valore

 Un attributo di tipo complesso


 Contiene il riferimento ad un oggetto (oppure null)
Attributi e costruttore
 All’atto dell’invocazione del costruttore, tutti gli attributi hanno un valore di
default
 Gli attributi semplici valgono 0 (false, nel caso dei valori booleani)
 Quelli complessi, valgono null

 È compito del costruttore assegnare valori opportuni se quelli di default non


sono adatti
 Questo può comportare anche la creazione di oggetti
Ciclo di vita di un oggetto
 L’operatore new, su richiesta del programmatore, alloca la memoria
necessaria a contenere un oggetto
 D1: quando viene rilasciata?
 R1: quando l’oggetto non serve più!
 D2: chi decide che non serve più?
 R2: l’ambiente di esecuzione (ad esempio la Java Virtual Machine oppure il
Framework .NET)
Accessibilità
 Un oggetto è accessibile fino a che ne esiste un riferimento

 Nel momento in cui non esistano più riferimenti, l’oggetto può essere
eliminato
 Rilasciando la memoria che occupa

 I riferimenti sono memorizzati in variabili e attributi


 Si cancellano quando la variabile cessa di esistere (fine del blocco)
 Oppure assegnando esplicitamente il valore null
Conteggio dei riferimenti
 All’interno di ogni oggetto, sia la JVM che l’ambiente di runtime
.NET mantengono un contatore nascosto
 Indica il numero di riferimenti esistenti a quello specifico oggetto
 Quando il suo valore scende a 0, indica che l’oggetto può essere eliminato,
rilasciando la memoria che occupa

 Un particolare sottosistema, il garbage collector, si occupa,


periodicamente, di riciclare la memoria degli oggetti eliminati
 Viene eseguito automaticamente dalla macchina virtuale Java
Accessibilità ed incapsulamento
 Non tutte le caratteristiche di un oggetto devono essere visibili
dall’esterno
 Rischio di manomissioni indebite
 Occorre separare l’interfaccia dall’implementazione

 Si realizza l’incapsulamento utilizzando un modificatore di visibilità


 Metodi e attributi possono preceduti da una parola chiave che indica il
livello di privilegio loro associato
Modificatori di visibilità
 Private
 Indica che si può accedere all’elemento solo da altri metodi appartenenti alla
stessa classe

 Public
 Indica che l’elemento può essere utilizzato da metodi di qualsiasi classe

 Potected
 Indica che si può accedere all’elemento solo da classi e metodi appartenenti allo
stesso namespace
Visibilità delle classi
 Anche le classi possono essere precedute da un modificatore di visibilità
 In questo caso, “private” non ha senso

 Se una classe è dichiarata pubblica, può essere utilizzata da classi


appartenenti a qualsiasi gruppo (package)
 Altrimenti, può essere usata solo nell’ambito del gruppo in cui è stata definita
Incapsulamento
 Per massimizzare il riuso, solo l’informazione minima necessaria al
funzionamento deve essere accessibile

 Di solito:
 Attributi privati
 Metodi pubblici
 Costruttori pubblici (*)

(*) C’è una importante eccezione a questa convenzione….


Metodi e attributi statici
 Oggetti appartenenti ad una stessa classe hanno lo stesso insieme
di attributi
 Ogni oggetto ha però i propri valori, indipendenti dagli altri
 Un metodo opera sui valori dell’oggetto su cui è invocato

 L’evoluzione dello stato di un oggetto è indipendente da


quella di altri oggetti della stessa classe
Attributi statici
 Talora, si vuole memorizzare un’informazione comune a tutti gli oggetti di
una data classe
 Si utilizzano gli attributi “statici”
 Il loro valore viene conservato in un blocco di memoria separato, relativo
alla classe
 Sono analoghi a variabili globali in altri linguaggi (C, C++, Pascal, ecc…)
Uso degli attributi statici
 Si può fare accesso ad un attributo statico anche in mancanza di un oggetto
specifico
 double d= Cerchio.pi/2;

 Gli attributi statici devono essere inizializzati


 Non si può fare nel costruttore!
 Occorre farlo all’atto della dichiarazione
 static double pi = 3.14 ;
Metodi statici
 Non fanno riferimento a nessun attributo specifico di un oggetto
 Preceduti dal modificatore “static”
 Equivalenti a procedure e funzioni di altri linguaggi
 Possono essere invocati a partire dal nome della classe
Esempi
 La classe java “Math” contiene metodi statici per le principali operazioni
matematiche
 double d1,d2;
 d1 = Math.sqrt( 2.0 );
 d2 = Math.sin( Math.PI / 2.0 );

 La classe java “System” contiene, sotto forma di attributi statici, oggetti che
modellano le interazioni con la console di sistema
 System.out //output su schermo
 System.in //input da tastiera
La classe String
 Classe che modella sequenze immutabili di caratteri

 Sintassi semplificata
 String s1= “Hello” ;
 String s2 = s1+ “ World”

 Offre molti metodi


 Confronto, ricerca, derivazione di nuove stringhe, informazioni generali, …
Confronto e ricerca
 public boolean Equals(String s)
 Restituisce true se il parametro contiene gli stessi caratteri dell’oggetto
corrente

 public boolean Contains(String s)


 Restituisce true se il parametro è contenuto nella stringa corrente.

 public int IndexOf(String s)


 Restituisce la posizione, all’interno della sequenza di caratteri, in cui inizia la
stringa indicata come parametro (-1 se non esiste)
Derivazione e informazioni
 public String toUpper()
 Restituisce una nuova stringa contenente gli stessi caratteri in versione
maiuscola

 public String Replace(String oldss, String newss)


 Restituisce una nuova stringa in cui tutte le occorrenze di oldss sono sostituite
con newss

 public String Substring(int beginIndex, int length)


 Restituisce una nuova stringa formata dai caratteri che iniziano alla posizione
beginIndex fino a beginindex+length.

 public int Length


 Restituisce la lunghezza in caratteri della stringa corrente
Esempi
String s1=“ciao”;
String s2= s1.ToUpper();
Boolean b= s2.Equals (s1);
int i= s2.Length;
int j= s2.IndexOf(“AO”);
String s3=s1.Substring(j,2);
Riusare il software
 A volte si incontrano classi con funzionalità simili
 In quanto sottendono concetti semanticamente “vicini”
 Una mountain bike assomiglia ad una bicicletta tradizionale

 È possibile creare classi disgiunte replicando le porzione di


stato/comportamento condivise
 L’approccio “Taglia&Incolla”, però, non è una strategia vincente
 Difficoltà di manutenzione correttiva e perfettiva

 Meglio “specializzare” codice funzionante


 Sostituendo il minimo necessario
Ereditarietà
 Meccanismo per definire una nuova classe (classe derivata)
come specializzazione di un’altra (classe base)
 La classe base modella un concetto generico
 La classe derivata modella un concetto più specifico

 La classe derivata:
 Dispone di tutte le funzionalità (attributi e metodi) di quella base
 Può aggiungere funzionalità proprie
 Può ridefinirne il funzionamento di metodi esistenti (polimorfismo)
Bicicletta

Esempio coppia
rapportoPosteriore

pedala(coppia)
cambiaRapporto(n)
frena(intensità)

MountainBike Tandem
rapportoAnteriore coppia2
cambiaRapportoAnt(n) pedala2(coppia)
Terminologia
Classe base,
Bicicletta superclasse

MountainBike Tandem

Classi derivate,
sottoclassi
Astrazione
 Il processo di analisi e progettazione del software di solito
procede per raffinamenti successivi
 Spesso capita che le similitudini tra classi non siano colte inizialmente
 In una fase successiva, si coglie l’esigenza/opportunità di introdurre un
concetto più generico da cui derivare classi specifiche

 Processo di astrazione
 Si introduce la superclasse che “astrae” il concetto comune condiviso
dalle diverse sottoclassi
 Le sottoclassi vengono “spogliate” delle funzionalità comuni che migrano
nella superclasse
Veicolo
double getVelocità()
double getAccelerazione()

Bicicletta Automobile
void pedala() void avvia()
void spegni()
Tipi ed ereditarietà
 Ogni classe definisce un tipo:
 Un oggetto, istanza di una sotto-classe, è formalmente compatibile con il tipo della
classe base
 Il contrario non è vero!

 Esempio
 Un’automobile è un veicolo
 Un veicolo non è (necessariamente) un’automobile

 La compatibilità diviene effettiva se


 I metodi ridefiniti nella sotto-classe rispettano la semantica della superclasse

 L’ereditarietà gode delle proprietà transitiva


 Un tandem è un veicolo (poiché è una bicicletta, che a sua volta è un veicolo)
Vantaggi dell’ereditarietà
 Evitare la duplicazione
di codice

 Permettere il riuso
di funzionalità

 Semplificare la costruzione
di nuove classi

 Facilitare la manutenzione

 Garantire la consistenza delle interfacce


Meccanismi
 Costruzione di oggetti di classi
derivate

 Accesso alle funzionalità della


superclasse

 Ri-definizione di metodi
Costruzione
 Per realizzare un’istanza di una classe derivata, occorre – innanzi tutto –
costruire l’oggetto base
 Di solito, provvede automaticamente il compilatore, invocando – come prima
operazione di ogni costruttore della classe derivata – il costruttore anonimo della
superclasse
 Si può effettuare in modo esplicito, attraverso il costrutto super(…)
 Eventuali ulteriori inizializzazioni possono essere effettuate solo successivamente
class Impiegato {
String nome;
Esempio
double stipendio;
Impiegato(String n) {
nome = n;
stipendio= 1500; class Funzionario
} : Impiegato {
}
Funzionario(String n) {
stipendio = 2000;
}
}
Accedere alla superclasse
 L’oggetto derivato contiene tutti i componenti (attributi e metodi)
dell’oggetto da cui deriva
 Ma i suoi metodi non possono operare direttamente su quelli definiti privati

 La restrizione può essere allentata:


 La super-classe può definire attributi e metodi con visibilità “protected”
 Questi sono visibili alle sottoclassi
Ridefinire i metodi
 Una sottoclasse può ridefinire metodi presenti nella superclasse

 A condizione che abbiano


 Lo stesso nome
 Gli stessi parametri (tipo, numero, ordine)
 Lo stesso tipo di ritorno
 (La stessa semantica!)

 Per le istanze della sottoclasse, il nuovo metodo nasconde l’originale


Ridefinire i metodi
class Derivata :
class Base {
Base {
int m() {
int m() {
return 0;
return 1;
}
}
}
}

Base b= new Base();


b.m());
System.Console.WriteLine(b.m()
Derivata d= new Derivata();
d.m());
System.Console.WriteLine(d.m()
Ridefinire i metodi
 A volte, una sottoclasse vuole “perfezionare” un metodo ereditato, non
sostituirlo in toto
 Per invocare l’implementazione presente nella super-classe, si usa il costrutto
base.<nomeMetodo> ( … )
class Base { class Derivata
int m() { : Base {
return 0; int m() {
} return base.m()
base.m()++ 1;
} }
}
Compatibilità formale
 Un’istanza di una classe derivata è formalmente compatibile con il
tipo della super-classe
 Base b = new Derivata( );

 Il tipo della variabile “b” (Base) limita le operazioni che possono


essere eseguite sull’oggetto contenuto
 Anche se questo ha una classe più specifica (Derivata), in grado di offrire
un maggior numero di operazioni
 Altrimenti viene generato un errore di compilazione
Polimorfismo
 In.NET viene mantenuta traccia della classe effettiva di un dato oggetto
 Seleziona sempre il metodo più specifico…
 …anche se la variabile che lo contiene appartiene ad una classe più generica!

 Una variabile generica può avere “molte forme”


 Contenere oggetti di sottoclassi differenti
 In caso di ridefinizione, il metodo chiamato dipende dal tipo effettivo dell’oggetto
Polimorfismo
 Per sfruttare questa tecnica:
 Si definiscono, nella super-classe, metodi con implementazione generica…
 …sostituiti, nelle sottoclassi, da implementazioni specifiche
 Si utilizzano variabili aventi come tipo quello della super-classe

 Meccanismo estremamente potente e versatile, alla base di molti “pattern”


di programmazione
Esempio
Forma
Forma f1 =
area() new Cerchio()
Cerchio();;
perimetro() Forma f2 = new
Rettangolo();;
Rettangolo()
double d1,d2;

Cerchio Rettangolo
d1=f1.area();
area() area() d2=f2.area();
perimetro() perimetro()
Controllare l’ereditarietà
 In alcuni casi, si vuole impedire esplicitamente l’utilizzo della tecnica
del polimorfismo
 Ad esempio, per motivi di sicurezza o per garantire il mantenimento di una
data proprietà del sistema
 In java si utilizza la parola chiave “final”, in c# si usa “sealed”.

 Un metodo “final” non può essere ridefinito da una sottoclasse


 Una classe “final” non può avere sottoclassi
 Un attributo “final” non può essere modificato
 In questo caso l’ereditarietà non c’entra nulla.
Controllare l’ereditarietà
 In altri casi si vuole obbligare l’utilizzo del polimorfismo
 Si introducono metodi privi di implementazione
 Facendoli precedere dalla parola chiave “abstract”

 Se una classe contiene metodi astratti:


 Deve essere, a sua volta, dichiarata abstract
 Non può essere istanziata direttamente
 Occorre definire una sottoclasse che fornisca l’implementazione dei metodi
mancanti
Interfacce

 Una classe astratta può contenere metodi non astratti


 A beneficio delle proprie sottoclassi

 In alcuni casi, si vogliono definire metodi astratti senza vincolare


la gerarchia di ereditarietà delle classi che li implementeranno
 Si utilizzano le interfacce:
 Insiemi di metodi astratti e costanti (attributi static final)
 Pubblici per definizione

 Una classe può implementare un’interfaccia


 Fornendo il codice relativo a tutti i metodi dichiarati nell’interfaccia
Interfacce e tipi
 Analogamente alle classi, ogni interfaccia definisce un tipo
 Un oggetto che implementa una data interfaccia ha come tipo anche il tipo
dell’interfaccia
 Un oggetto può implementare molte interfacce
 Di conseguenza può avere molti tipi

 Uno degli scopi principali delle interfacce è quello di fornire un approccio


alternativo al problema della Ereditarietà Multipla, disponibile in C++ ma non in
Java né in C#