Le gerarchie di tipi:
implementazioni multiple e
principio di sostituzione
1
Come si può utilizzare una gerarchia
di tipi
 Il sottotipo estende il comportamento del tipo
–
fornendo nuovi metodi
 Implementazioni multiple di un tipo
– i sottotipi non aggiungono alcun comportamento nuovo
– la classe che implementa il sottotipo implementa esattamente il
comportamento definito dal supertipo
 dal punto di vista semantico, supertipo e sottotipo sono
legati dal principio di sostituzione: un oggetto del sottotipo
può essere sostituito ad un oggetto del supertipo senza
influire sul comportamento dei programmi che utilizzano il
tipo
2
Implementazioni multiple
 il tipo superiore della gerarchia
– un’interfaccia (solo specifica)
– una classe astratta (implementazione parziale)
 definisce una famiglia di tipi tale per cui
– tutti i membri hanno esattamente gli stessi
metodi e la stessa semantica (specifica)
 Esempio: Stack, ListStack, VectorStack
3
Implementazioni multiple
 Consideriamo le due implementazioni per Stack
• un programma potrebbe voler usare tutte e due le
implementazione (scegliendo ogni volta la più adeguata)
• La scelta viene fatta chiamando uno o l’altro costruttore
– dall’esterno si vedono solo i costruttori dei sottotipi
– gli oggetti dei sottotipi vengono dall’esterno tutti visti come oggetti
dell’unico supertipo (la specifica e’ uguale)
4
IntList
vogliamo dare implementazioni “alternative”
alcune operazioni possono essere comuni alle
implementazioni
–IntList è una classe astratta e non un interfaccia
5
Specifica della classe astratta
–la classe astratta ha alcuni metodi non astratti
•comuni alle sottoclassi
•definiti in termini dei metodi astratti
–la classe astratta non ha variabili di istanza e
quindi nemmeno costruttori
–E’ simile a quella vista, solo che il tipo e’
non modificabile
6
Cosa vuol dire?
 Non ci sono metodi modificatori
 I metodi che “modificano” la lista devono
essere trasformati in produttori; ritornano
una lista modificata, invece di modificare
this
 Esempi: addEl, first, rest
7
Specifica del supertipo IntList
public abstract class IntList {
// OVERVIEW: un IntList è una lista
// non modificabile di Integers.
// Elemento tipico [x1,...,xn]
//metodi astratti
public abstract Integer first ()
throws EmptyException;
//EFFECTS: se this è vuoto solleva
// EmptyException, altrimenti
// ritorna il primo elemento di this
public abstract IntList rest ()
throws EmptyException;
// EFFECTS: se this è vuoto solleva
// EmptyException, altrimenti
// ritorna la lista ottenuta da this togliendo
// il primo elemento
8
Specifica del supertipo IntList
public abstract Iterator elements ();
// EFFECTS: ritorna un generatore che produrrà
// tutti gli elementi di this (come Integers)
// nell’ordine che hanno in this
public abstract IntList addEl (Integer x);
// EFFECTS: ritorna una lista ottenuta
// aggiungendo x all’inizio di this
public abstract int size ();
// EFFECTS: ritorna il numero di elementi
// di this
public abstract boolean repOk ();
9
Implementazione del supertipo
IntList
// metodi concreti
public String toString () {....}
public boolean equals (Object o) {
//EFFECTS: restituisce true se this ed o sono la
//stessa lista, false altrimenti
}
}
vanno implementati sono metodi concreti
si possono implementare usando il generatore
elements (astratto)
si ottiene una implementazione comune per tutti
i possibili sottotipi
10
Implementazione
 Non ci sono variabili d’istanza e costruttori
 Vanno implementati solo i metodi concreti
11
ToString()
public String toString () {
Iterator g=elements();
String s=“”;
while (g.hasnext())
{Integer el=(Integer) g.next();
s=s + el.intValue();}
return s; }
Usando il generatore possiamo leggere la
lista senza sapere come e’implementata
(addirittura non e’ ancora implementata)
Meccanismo di iterazione astratta
Alternativa: si potrebbero usare first() e
rest()
12
Equals()
public boolean equals (Object o) {
//EFFECTS: verifica se this ed o sono
//la stessa lista
}
Metodo sovrascritto da Object
Quello ereditato e’ poco utile perche’ verifica
se il riferimento e’ lo stesso; vorremmo invece
vedere se hanno gli stessi elementi
13
equals()
public boolean equals (Object o) {
IntList l= (IntList) o;
if (size()!= l.size())
{return false;}
if (size()==0 && l.size()==0)
{return true;} else
{Iterator g=elements();
Iterator h=l.elements();
while (g.hasnext())
{ Integer el1=(Integer) g.next();
Integer el2=(Integer) h.next();
if ( ! el1.equals (el2)) {return false;}
}
return true;}
}
14
IntList :sottoclassi
Le sottoclassi definiscono implementazioni
“alternative”: ne vediamo per esempio due
Una è quella che abbiamo visto
–ogni oggetto ha 4 campi (ne basterebbero 3)
•solo alcuni di questi sono utilizzati nei vari casi della definizione
e ricorsiva
15
Prima Implementazione
 Implementazione vista con la lista concatenata
class IntListUno extends IntList{
private
private
private
private
boolean vuota;
Integer val;
IntList next;
int sz;
Definizione ricorsiva: caso base la lista vuota
(rappresentata tramite la variabile booleana vuota)
16
Rappresentazione Lista
val
next
vuota
Lista
vuota:
any
any
154
24
any
any
true
false
false
true
Lista non vuota:
17
Commenti
 Vantaggio: definizione ricorsiva pulita, caso base: lista
vuota
 Svantaggio dell’implementazione: ogni lista vuota o non
vuota ha 4 variabili d’istanza (ne bastano tre)
 Nei metodi sono necessari continui test su “vuota” per
distinguere i due casi
18
Implementazione alternativa
 usiamo due sottotipi differenti per implementare i due casi
della definizione ricorsiva
– lista vuota
– lista non vuota
– Piu’ efficiente, occupa meno spazio, meno test necessari
– Unisce i vantaggi delle due implementazioni viste in
precedenza
19
Implementazione del sottotipo
EmptyIntList
 Implementa il caso della lista vuota
 Non ha variabili d’istanza (e quindi neanche
invariante)
 Dobbiamo fornire i costruttori e
l’implementazione dei metodi astratti di
IntList
20
Implementazione del sottotipo
EmptyIntList
 Presentiamo la specifica insieme
all’implementazione, direttamente
 La specifica e’ leggermente diversa da quella del
supertipo, e’ specializzata al caso della lista vuota
 le parti della specifica del sottotipo che sono
uguali a quelle del supertipo non le riportiamo
21
Implementazione del sottotipo
EmptyIntList
public class EmptyIntList extends IntList {
// OVERVIEW: un EmptyIntList è una lista
//di Integers vuota. Elemento tipico []
public EmptyIntList () {}
Non ha variabili d’istanza!
22
Implementazione del sottotipo
EmptyIntList
public Integer first () throws EmptyException
// EFFECTS: solleva EmptyException
{ throw new EmptyException ("EmptyIntList.first"); }
public IntList rest () throws EmptyException
// EFFECTS: solleva EmptyException
{ throw new EmptyException ("EmptyIntList.rest"); }
Sono specializzati per il caso della lista vuota!!!!
23
Implementazione del sottotipo
EmptyIntList
public IntList addEl (Integer x)
{FullIntList n = new FullIntList(x);
return n; }
public int size () {return 0;}
public boolean repOk () {return true;}
Deve essere chiamato il costruttore dell’altro
sottotipo quando si aggiunge un elemento
24
Implementazione del sottotipo
EmptyIntList
public abstract Iterator elements (){
// EFFECTS: ritorna un generatore che produrrà
// tutti gli elementi di this (come Integers)
// nell’ordine che hanno in this
return new EmptyGen();}
private static class EmptyGen implements Iterator {
EmptyGen () {}
public boolean hasNext () { return false; }
public Object next () throws NoSuchElementException
{throw new NoSuchElementException("IntList.elements");
}
}
25
Invariante e funzione di
astrazione
 Invariante non c’e’ (non ci sono variabili
d’istanza)
 Funzione di astrazione (rappresenta la lista
vuota)
aEmptyIntList(c) = []
26
Implementazione del sottotipo
FullIntList
public class FullIntList extends IntList {
//OVERVIEW: un FullIntList è una lista di Integers
//non vuota. Elemento tipico [x1, …,xn] n>0
private Integer val; valore
private IntList next; resto della lista
private int sz; numero di elementi
Implementazione ricorsiva (caso nodo)
Non serve la variabile booleana
sz variabile ausiliaria per contare il numero di elementi
27
Invariante e f. di astrazione
IFullIntlist(c) = (c.next != null e
c.sz= 1 + c.next.size()
e c.next e’ EmptyIntList o
IFullIntlist(c.next) )
Le condizioni si separano e dividono nei due casi
a(c) = [c.val] + a(c.next)
28
Implementazione del sottotipo
FullIntList
public FullIntList (Integer x)
// EFFECTS: inizializza this alla lista che contiene x
{sz=1; val=x; next=EmptyIntlist();}
public Integer first () throws EmptyException
// EFFECTS: ritorna il primo elemento della lista
{return val; }
public IntList rest () throws EmptyException
// EFFECTS: ritorna il resto della lista
{ return next; }
Non ci sono piu’ da fare test , in questo caso di sicuro
val e next sono definiti (vedi anche l’invariante)
29
Implementazione del sottotipo
FullIntList
public Iterator elements () {return new FullGen(this)}
public IntList addEl (Integer x)
{FullIntList n = new FullIntList(x);
n.next=this;n.sz=this.sz+1;
return n; }
public int size () {return sz;}
public boolean repOk () {……..}
Omettiamo la definizione del generatore; pensate
voi a come adattarlo
30
Vantaggi
 I metodi per i due casi (lista vuota e non vuota)
sono implementati in modo più efficiente
 Si evitano tutta una serie di test per distinguere i
due casi (il metodo più specifico viene chiamato
automaticamente)
 Anche dal punto di vista della correttezza e’ piu’
facile ragionare sui due casi separamente (si
semplificano l’invariante e la funzione di
astrazione)
31
Cosa cambia dal punto di vista
dell’utente del tipo di dato?
 Puo’ scegliere l’implementazione piu’
efficiente (scegliendo l’opportuno
costruttore)
 Siamo sicuri che tutti i sottotipi della classe
astratta si comportano nello stesso modo?
 Sarebbe ovviamente vero se tutti i sottotipi
avessero per ogni metodo esattamente le
stesse specifiche (ma…)
32
Differenza tra le specifiche
 Abbiamo visto che gli oggetti del tipo
FullIntList differiscono da quelli del
supertipo (vedi OVERVIEW)
 Anche le postcondizioni di alcuni metodi
(es. first, rest)
 Analogo per EmptyIntList
 Ma esiste una relazione particolare tra
specifiche del supertipo e del sottotipo
33
Prima proprieta’:dati
 Le proprieta’ dei dati del supertipo sono piu’
generali di quelle dei sottotipi
 Una lista non vuota e’ un tipo particolare di lista
 Una lista vuota e’ un tipo particolare di lista
 In conclusione, i dati del sottotipo soddisfano
anche le richieste del supertipo
34
Seconda proprieta’:metodi
 Le specifiche dei metodi (p.e. first) del supertipo e
di quelli dei sottotipi richiedono lo stesso risultato
per la lista vuota o non vuota rispettivamente
 Le specifiche dei metodi del sottotipo non
differiscono da quelle del supertipo, sono solo
specializzate sui due casi rappresentati dai due
sottotipi, lista vuota e non vuota
35
Esempio
//metodo del supertipo
public abstract Integer first () throws
EmptyException;
// EFFECTS: se this è vuoto solleva
// EmptyException, altrimenti
// ritorna il primo elemento di this
//metodo del sottotipo Full
public Integer first () throws EmptyException
// EFFECTS: ritorna il primo elemento della lista
Quando viene selezionato quello del sottotipo, la
lista e’ non vuota, il comportamento richiesto e’
esattamente quello del supertipo
36
Di conseguenza
 Vuol dire che un programma scritto in
termini del supertipo IntList lavora
correttamente su oggetti del sottotipo
FullIntList o EmptyIntList
Vale il Principio di Sostituzione: un oggetto del
sottotipo può essere sostituito ad un oggetto del
supertipo senza influire sul comportamento dei
programmi che utilizzano il tipo
37
Esempio
 Supponiamo di avere un metodo statico che e’
scritto guardando la specifica del supertipo IntList
public static boolean cerca (Intlist l,Integer x)
throws NullPointerException {
//EFFECTS: se l o x sono null solleva
//NullPointerException, altrimenti restituisce
// true se x occorre nella lista l, false se
// non occorre}
38
Implementazione ricorsiva
public static boolean cerca(Intlist l,Integer x)
throws NullPointerException {
if (x==null|| l==null)
throw new NullPointerException(“Lista.cerca”);
if (l.size()==0) {return false;}
else
{Integer el=l.first();
if (el.equals(x)) {return true;}
return cerca(l.rest(),x);}
}
39
Principio di Sostituzione
 I metodi dei due sottotipi garantiscono lo
stesso comportamento di quelli di IntList
 Di conseguenza cerca puo’ essere utilizzato
sia con un parametro di tipo FullIntList che
EmptyIntList
 Inoltre per provarne la correttezza e’
sufficiente avere considerato le specifiche di
IntList
40
Principio di Sostituzione
 Deve valere sempre, altrimenti il meccanismo di
fattorizzazione offerto dall’ereditarieta’ servirebbe a poco
 Bisogna garantire la modularita’ ovvero che la definizione
di sottotipi non richieda di dimostrare da capo la
correttezza di tutto il codice corretto rispetto al supertipo
41
Principio di sostituzione
 Non vale sempre in generale
 Quale deve essere la relazione tra le
specifiche del sottotipo e del supertipo?
 Perche’ valga il sottotipo deve soddisfare le
specifiche del supertipo
 Cosa vuol dire?
 Cosa succede se non vale?
42
Intuitivamente
 La specifica del sottotipo deve implicare
quella del supertipo
 Proprieta’ dei dati (OVERVIEW)
 Proprieta’ dei metodi riscritti
–se la specifica nel supertipo è nondeterministica
(ammette varie possibilità) il sottotipo può avere una
specifica più forte che risolve (in parte) il
nondeterminismo
43
Esempio
 SortedIntSet (insiemi ordinati) sottotipo di IntSet
(insiemi non ordinati)
public class IntSet {
public Iterator elements ()
// EFFECTS: ritorna un generatore che produrrà tutti gli elementi di
// this (come Integers) ciascuno una sola volta, in ordine arbitrario
 il metodo elements di SortedIntSet assume
l’ordine crescente
public class SortedIntSet {
public Iterator elements ()
// EFFECTS: ritorna un generatore che produrrà tutti gli elementi di
// this (come Integers) ciascuno una sola volta, in ordine crescente
44
Vale il principio di sostituzione
 Specifica del sottotipo e’ piu’ forte, implica anche
le proprieta’ del supertipo
 Per i dati: un insieme ordinato e’ anche un insieme
 Il generatore che restituisce gli elementi in ordine
e’ un caso particolare di quello che lo restituisce in
qualsiasi ordine
45
Di conseguenza
 I programmi provati corretti rispetto alla
specifica di IntSet funzionano
correttamente anche per il sottotipo
SortedIntSet
 Vediamo un esempio
46
Procedura per l’uguaglianza
public static boolean uguali(IntSet x,IntSet y){
// EFFECTS: verifica se x ed y hanno gli stessi elementi
{{if (x == null || y==null) return false;
Iterator g=x.elements();
while (g.hasnext())
{int temp=( (Integer) g.next()).intValue();
if (!y.isIn(temp)) {return false;}
}
Iterator g=y.elements();
while (g.hasnext())
{int temp=( (Integer) g.next()).intValue();
if (!x.isIn(temp)) {return false;}
}
return true;}
47
Procedura per l’uguaglianza
 Scritta guardando la specifica di IntSet
 Siccome non sono ordinati deve generare tutti gli elementi
di x e di y
 La procedura uguale e’ corretta rispetto alla
specifica del supertipo IntSet
48
Commenti
 Dato che vale il p. di sostituzione e’ corretta
anche se chiamata con parametri del
sottotipo SortedIntSet
 In tal caso il generatore e’ quello del
sottotipo e genera i valori in modo ordinato
(tutto continua a funzionare)
49
Vantaggio
 Non dobbiamo ridimostrare la correttezza
del metodo uguali per il sottotipo
SortedIntSet
 La correttezza del sottotipo (pr. Di
sostituzione) garantisce che valga
 Sarebbe impensabile dover ripetere tutte le
dimostrazioni per ogni sottotipo
50
Cosa succede se non vale?
 Se non vale il principio di sostituzione programmi
dimostrati corretti rispetto alla specifica del
supertipo potrebbero non esserlo quando si usa il
sottotipo
 Dovrei ridimostrare la loro correttezza rispetto alle
specifiche di tutti i sottotipi
 Sconsigliato: vuol dire che non sono ben definiti
(perdo la flessibilita’ dell’uso dei sottotipi)
51
Esempio
 Se ammettessi di definire IntSet come
sottotipo di SortedIntSet
 Non vale il principio di sostituzione: il
sottotipo soddisfa proprieta’ piu’ deboli
 Un insieme in cui gli elementi sono messi in
modo arbitrario non e’ ordinato (analogo per
il generatore)
52
Procedura che verifica
l’uguaglianza (per SortedIntset)
public static boolean uguali(SortedIntSet x,SortedIntSet y){
// EFFECTS: verifica se x ed y hanno gli stessi elementi
{{if (x == null || y==null) return false;
Iterator g=x.elements();
Iterator h=y.elements();
while (g.hasnext() & & h.hasnext())
{int temp1=( (Integer) g.next()).intValue();
int temp2=( (Integer) h.next()).intValue();
if (!temp1.equals (temp2)) {return false;}
}
if (! (g.hasnext() | | h.hasnext()))
{return true;} else {return false;}
}
53
Procedura che calcola
l’uguaglianza
 Corretta rispetto alla specifica del supertipo
 Sfrutta le caratteristiche del supertipo
ovvero l’ordinamento (piu’ efficiente)
 Se chiamata con parametri del sottotipo
IntSet (in tal caso non funziona
correttamente)
54
Dove sta l’errore?
 Il sottotipo non ordinato non e’ un sottotipo
corretto di ordinato
 Il principio di sostituzione non vale
 Il principio di sostituzione e’ fondamentale
per riutilizzare il codice
55
Principio di sostituzione
 Perche’ valga in generale bisogna verificare che
– la regola delle proprietà
• il sottotipo deve preservare tutte le proprietà che valgono sugli oggetti
del supertipo
– la regola dei metodi
• le chiamate dei metodi del sottotipo devono comportarsi come le
chiamate dei corrispondenti metodi del supertipo
 tutte le regole riguardano solo le specifiche di supertipo e
sottotipo
56
Regola dei metodi 1
 le chiamate dei metodi del sottotipo devono
comportarsi come le chiamate dei corrispondenti
metodi del supertipo
 va bene se i metodi overriden del sottotipo
hanno esattamente le stesse specifiche di
quelli del supertipo
 Possono essere diverse se la specifica dei
metodi del sottotipo e’ piu’ forte
– Come nell’esempio del generatore visto prima
57
Regola dei metodi 3
 Per formalizzare bene il concetto di specifica più forte
bisogna considerare
– La precondizione (specificata dalla clausola REQUIRES)
definisce i vincoli sugli inputs
– La postcondizione (specificata dalla clausola EFFECTS) definisce
le proprietà che valgono dopo l’esecuzione del metodo sugli inputs
che soddisfano la precondizione
58
Regola dei metodi 4
 in generale un sottotipo può indebolire le precondizioni e
rafforzare le post condizioni
 per avere compatibilità tra le specifiche del supertipo e
quelle del sottotipo devono essere soddisfatte le regole
– regola delle precondizione
• pre super ==> pre sub
– regola delle postcondizione
• pre super && post sub ==> post super
59
Regola dei metodi 4
 indebolire la precondizione
– pre super ==> pre sub
ha senso, perché il codice che utilizza il metodo è scritto
per usare il supertipo
– ne verifica la precondizione
– verifica anche la precondizione del metodo del sottotipo
 esempio: un metodo in IntSet
public void addZero (
)
// REQUIRES: this non è vuoto
// EFFECTS: aggiunge 0 a this
potrebbe essere ridefinito in un sottotipo
public void addZero (
)
// EFFECTS: aggiunge 0 a this
60
Regola dei metodi 5
 rafforzare la post condizione
– pre super && post sub ==> post super
ha senso, perché il codice che utilizza il metodo è scritto
per usare il supertipo
– È soddisfatta la precondizione più forte (quella del supertipo)
– gli effetti del metodo del sottotipo includono comunque quelli del
supertipo (se la chiamata soddisfa la precondizione più forte)
 esempio: un metodo in IntSet
public void addZero (
)
// REQUIRES: this non è vuoto
// EFFECTS: aggiunge 0 a this
potrebbe essere ridefinito in un sottotipo
public void addZero (
)
// EFFECTS: se this non è vuoto aggiunge 0 a this altrimenti aggiunge 1
a this
61
Regola dei metodi 6
public class IntSet {
public Iterator elements ()
// EFFECTS: ritorna un generatore che produrrà tutti gli elementi di
// this (come Integers) ciascuno una sola volta, in ordine arbitrario
public class SortedIntSet extends IntSet {
public Iterator elements ()
// EFFECTS: ritorna un generatore che produrrà tutti gli elementi di
// this (come Integers) ciascuno una sola volta, in ordine crescente
 entrambi i metodi hanno precondizione true
 la postcondizione del metodo del sottotipo
– gli elementi sono generati in ordine crescente
implica la postcondizione del metodo del supertipo
62
Regola dei metodi: violazioni 1
 consideriamo insert in IntSet
public class IntSet {
public void insert (int x)
// MODIFIES: this
// EFFECTS: aggiunge x a this
 supponiamo di definire un sottotipo di IntSet con la seguente specifica di
insert
public class StupidoIntSet extends IntSet {
public void insert (int x)
// MODIFIES: this
// EFFECTS: aggiunge x a this se x è pari, altrimenti
non fa nulla
 La postcondizione è stata indebolita (errore)
 Chi usa insert si aspetta che x venga aggiunto comunque!
 Se vogliamo un metodo di questo tipo nel sottotipo perche’ chiamarlo uguale
esovrascriverlo?
63
Regola dei metodi: violazioni 2
 consideriamo addEl in OrderedIntList
public class OrderedIntList {
public void addEl (int x) throws DuplicateException {
// MODIFIES: this
// EFFECTS: aggiunge x a this se non c’è già, altrimenti solleva
l’eccezione
 supponiamo di definire un sottotipo di
OrderedIntList con la seguente specifica di addEl
public void addEl (int x)
// MODIFIES: this
// EFFECTS: aggiunge x a this
 non è un problema la differenza della segnatura
 ma c’è un problema con la regola dei metodi
– se l’elemento c’è già, i due metodi hanno comportamento diverso
– e quello del sottotipo fa “meno cose” (è più debole)
64
Regola delle proprietà
 il ragionamento sulle proprietà degli oggetti basato sul
supertipo è ancora valido quando gli oggetti appartengono
al sottotipo
 proprietà degli oggetti astratti
– non proprietà dei metodi
 da dove vengono le proprietà degli oggetti?
– dal modello del tipo di dato astratto
• le proprietà degli insiemi matematici, etc.
• le elenchiamo esplicitamente nell’overview del
supertipo
 Un insieme ordinato e’ anche un insieme
65
Scarica

Part II