Estensioni

04/06/2014
Estensioni
Estensioni pure
Estensione e' il termine java per ereditarietà :
Un estensione C' di una classe C è un erede di C
Un estensione è detta pura se non modifica la
specifica dei metodi ereditati
Un estensione pura può quindi solo estendere la
specifica, aggiungendo nuove operazioni...
...tuttavia può modificare (a parità di specifica)
l implementazione dei metodi ereditati
altrimenti è detta non pura
2
1
04/06/2014
Estensione pura MaxIntSet: Specifica
Estensione di IntSet, che aggiunge solo il metodo per estrarre
il massimo elemento.
Specifica di tutti gli altri metodi è inalterata.
Costruttore non e' ereditato, quindi potrebbe cambiare.
public class MaxIntSet extends IntSet {
// OVERVIEW: MaxIntSet è sottotipo di IntSet con metodo max per
//estrarre il massimo elemento
//@ensures (*inizializza this come il MaxIntSet vuoto*)
\public MaxIntSet
//@ ensures this.size()>0 &&\result == (\max int i; this.isIn(i);i);
//@signals(EmptyException) this.size() == 0;
public int /* pure */ max() throws EmptyException { }
La specifica di MaxIntSet assume come data la specifica di IntSet
Aggiunge solo ciò che è nuovo (max() e costruttore)
3
MaxIntSet: Implementazione
Per migliorare efficienza, si memorizza il max elemento => occorre reimplentare
tutti i metodi modificatori, anche se specifica non è cambiata! RI cambia.
public class MaxIntSet extends IntSet {
private int biggest;//memorizza il max. Non definito per this=vuoto.
public MaxIntSet(){ super(); }
public int max() throws EmptyException {
if (size() == 0) throw new EmptyException( MaxIntSet.max );
return biggest;
}
@Override
public void insert(int x){
if (size() == 0 || x>biggest) biggest = x;
super.insert(x); //chiama metodo insert di IntSet
}
overriding (ridefinizione)
@Override
public void remove(int x){
super.remove(x);
if(size()==0||x<biggest) return;
(for Iterator<Integer> g: IntSet) {Integer z=g; if(z>biggest) biggest=z;}
}
4
2
04/06/2014
AF e RI?
AF di sottoclasse di solito è uguale a quella della sovraclasse
AF di MaxIntSet è identica a AF di IntSet: MaxIntSet utilizza ancora IntSet per
memorizzare gli elementi.
// la funzione di astrazione è:
//AF_MaxIntSet(c) = AF_IntSet(c)
Rep invariant della classe base è ereditato senza possibilità di modifica e si
aggiunge (in JML con also ) un nuovo RI:
// il rep invariant RI_MaxIntSet(c) è:
//@ also
//@ private invariant this.size>0 ==>
//@ isIn(biggest) && biggest = (\max int x; isIn(x);x);
NB: RI è sempre vero ad esempio quando this è l insieme è vuoto. In tal caso,
biggest può assumere un valore qualunque.
Valgono le consuete regole di visibilità. RI di una estensione può usare:
tutti gli attributi e metodi puri (anche private) definiti nella classe stessa;
attributi e metodi puri ereditati, purche' pubblici o protected.
Quindi se els è private non si può scrivere, al posto dell'ultima linea:
(\forall int i; 0<= i && i<this.size(); els.get(i) <= x)
5
Estensioni non pure
E modificata la specifica di uno o più metodi ereditati
Es. un insieme ordinato SortedIntSet
public class SortedIntSet extends IntSet {
public SortedIntSet {super();}
@Override
//@ ensures (*restituisce un iteratore su tutti e soli gli elementi
di //@this , ciascuno una sola volta, in ordine crescente *);
}
}
NB: Le estensioni non pure di classi concrete andrebbero evitate o
quantomeno limitate (la reimplementazione di metodi sempre
«pericolosa»).
3
04/06/2014
PRINCIPIO DI SOSTITUZIONE
DI LISKOV
Richiami: Open-closed principle
Nei linguaggi a oggetti, è possibile definire astrazioni, tramite
classi (tipicamente classi astratte o interfacce), da richiamare
poi in altri moduli.
Un modulo è chiuso rispetto alla modifica perché la
definizione delle astrazioni non cambia
Ad esempio disegnaTutto() nell editor grafico era definita in base alla
classe astratta Figura. La disegnaTutto non deve più essere modificata.
Le classi però possono essere estese, tramite sottoclassi: ciò
rende un modulo aperto per l'estensione:
definire una nuova classe dalle astrazioni può modificare il
comportamento di un modulo.
Ad esempio disegnaTutto() diventa in grado di disegnare anche trapezi,
ecc.
8
4
04/06/2014
Estendibilità tramite ereditarietà è garantita solo se possiamo
usare oggetti di una classe che sono sostituibili a quelli della
sopraclasse.
Gli oggetti della sottoclasse devono rispettare il contratto della
superclasse
Ad esempio, la disegna() deve contenere codice che disegna l oggetto
this sullo schermo. Ogni sottoclasse di Figura deve definire una
disegna() che abbia lo stesso effetto!
Ma se si definisse disegna() in modo diverso? Ad esempio, se disegna()
di Trapezi fosse definita in modo che disegna this solo se l area e
maggiore di 20? Va ancora bene?
Come fare a garantire che il contratto della superclasse sia
rispettato?
9
Principio di sostituzione di Liskov
Gli oggetti della sottoclasse devono rispettare il contratto della
sovraclasse il contratto puo essere esteso per coprire ulteriori casi.
Questo garantisce che moduli che usano oggetti di un tipo devono potere
usare oggetti di un tipo derivato senza accorgersi della differenza
10
5
04/06/2014
Es: Principio di Sostituzione OK
L estensione pura MaxIntSet soddisfa il contratto di IntSet:
ha tutti i metodi di IntSet, ciascuno con la stessa specifica che in IntSet
ha in più il metodo max, ma utilizzatore di IntSet comunque non lo userà!
Es. IntSet ha metodo iterator():
//@ensures (*restituisce un iteratore su tutti e soli gli elementi di
//@ this, ciascuno visitato una sola volta, in ordine qualunque *);
public Iterator<Integer> iterator()
Codice che usa IntSet e iterator():
}
public static int traccia(IntSet x) {
int somma=0;
(for Iterator<Integer> i: x)
somma+=i.next();
return somma;
Poiché MaxIntSet ha ancora il metodo iterator() con la stessa specifica, il
codice di traccia
sarà sorpreso se gli viene passato un MaxIntSet al
posto di un IntSet: il MaxIntSet si comporta esattamente allo stesso modo.
Quindi traccia(..) continua a garantire il proprio contratto
11
Es: Principio di Sostituzione OK
public static int traccia(IntSet x) {
int somma=0;
(for Iterator<Integer> i: x)
somma+=i.next();
return somma;}
Sottoclasse SortedIntSet ridefinisce la specifica del metodo iterator() (quindi è
estensione non pura):
public class SortedIntSet extends IntSet {
@Override
//@ ensures (* \result contiene tutti e soli gli elementi di this ,
ciascuno una sola volta, in ordine crescente *);
public Iterator<Integer> iterator()
}
traccia(s ); non sarà sorpreso se s è un SortedIntSet: il metodo restituisce comunque
tutti e soli gli elementi, anche se in un ordine particolare
12
6
04/06/2014
Es: Principio di Sostituzione KO!
Se invece iterator() fosse:
//@ ensures (* restituisce un iteratore che itera su tutti gli elementi
positivi di this, ciascuno una sola volta, in ordine crescente *);
public Iterator<Integer> iterator()
}
La traccia(s) non calcola più la traccia dell insieme! il metodo
non è compatibile con quello di IntSet() non restituisce gli
elementi di valore negativo.
Traccia(s) può avere un comportamento scorretto!
Non soddisfa più il proprio contratto.
Il motivo è che il contratto di iterator() in SortedIntSet non
mantiene la promessa fatta dal contratto di iterator() in IntSet.
13
Regole per la specifica di sottoclassi
Come garantire che il contratto dell estensione sia compatibile
con quello della superclasse?
Principio di sostituzione impone vincolo sulla specifica del sottotipo:
deve essere compatibile con specifica del sopratipo (equivalenza
comportamentale (behavioral) tra esemplari della sovraclasse e della
sottoclasse).
Poi basta che implementazione sia corretta rispetto a specifica
Basta mostrare le seguenti tre proprietà della specifica dei tipi
per compatibilità:
Regola della segnatura: un sottotipo deve avere tutti i metodi del
sopratipo, e segnature dei metodi del sottotipo devono essere
compatibili.
Regola dei metodi: le chiamate ai metodi del sottotipo devono
comportarsi come le chiamate ai metodi corrispondenti del sopratipo.
Regola delle proprietà: sottotipo deve preservare tutti i public invariant
degli oggetti del sovratipo.
14
7
04/06/2014
Spiegazione
Regola della segnatura: garantisce che il contratto della
sovraclasse sia ancora applicabile, ossia che la sintassi della
sottoclasse sia compatibiile con la sintassi della sovraclasse
Es. il metodo non cambia la segnatura
Regola dei metodi: verifica che il contratto dei singoli metodi
ereditati sia compabile con il contratto dei metodi originali
Es. estensione pura non cambia specifica
Regola delle proprietà: verifica che la specifica nel suo
complesso sia compatibile con quella originale
15
Regola della segnatura
Garantisce type-safety, ossia che
ogni chiamata corretta (senza errori di tipo) per il sovratipo sia corretta
anche per il sottotipo
correttezza verificabile staticamente
Es.
chiama il metodo iterator(): questo deve essere
presente nell estensione, con un prototipo utilizzabile da
In Java (nelle versioni <1.5), la regola delle segnatura diventa:
un sottotipo deve avere tutti i metodi del sovratipo, e le segnature dei
metodi del sottotipo devono essere identiche alle corrispondenti del
sovratipo
però un metodo del sottotipo può avere meno eccezioni nella
segnatura (il codice che fa uso dell astrazione, scritto con
riferimento al metodo del sovratipo, funziona anche se alcune delle
eccezioni non vengono lanciate dai metodi del sottotipo)
In realtà la regola della segnatura in Java è inutilmente
restrittiva...
16
8
04/06/2014
Esempio: Punti e Linee Colorate
public class Line
public class ColoredLine extends Line {//una Line con un colore
public class Point
un punto (immutabile) nello spazio 2D
public class GridPoint extends
che deve stare su una griglia:
//un GridPoint ha coordinate x e y intere
public Line newLine(GridPoint p)// \result e una linea da this a p
}
public class ColoredGridPoint extends GridPoint {
private Colore col;
// \result è una ColoredLine colorata come this.col
@Override
public Line newLine(GridPoint p)
}
In Java la definizione di newLine è corretta, anche se a run time
restituisce una ColoredLine, che è sottotipo di Line.
Invarianza del risultato e dei parametri
public static void usaPunto(GridPoint punto1) {
Line lin= punto1.newLine(new GridPoint
}
usaPunto(new ColoredGridPoint
La chiamata è lecita in Java perché ColoredGridPoint è sottotipo di
GridPoint.
Ridefinizione di newLine ha la stessa signature delloriginale, ossia:
@Override // result è una ColoredLine colorata come this.col
public Line newLine(GridPoint p)
Allora usaPunto(new ColoredGridPoint
invoca la nuova
newLine, che riceve in ingresso un GridPoint e restituisce una
ColoredLine
Sappiamo che e corretto in Java! ColoredLine è sottotipo di Line e
quindi lin a run-time può riferire un oggetto di tipo ColoredLine.
9
04/06/2014
Covarianza del risultato
public static void usaPunto(GridPoint punto1) {
}
Se fosse consentito ridefinire newLine come:
@Override // \result è una ColoredLine colorata come this
public ColoredLine newLine(GridPoint p)
allora
invocherebbela la nuova
newLine, che riceve in ingresso un GridPoint e restituisce una ColoredLine
E corretto! ColoredLine è sottotipo di Line e quindi lin a run-time può
riferire un oggetto di tipo ColoredLine.
In effetti Java (da 1.5 in poi) consente di restringere il tipo del valore
ritornato (ossia consente la Covarianza del Risultato)
Covarianza dei parametri???
public static void usaPunto(GridPoint punto1) {
}
Se fosse consentito ridefinire newLine come:
@Override // \result è una linea colorata come p.col
public Line newLine(ColoredGridPoint p)
allora
invocherebbe la la nuova
newLine, che deve ricevere in ingresso un ColoredGridPoint
SCORRETTO! Il codice esistente di usaPunto passa a newLine un
GridPoint (che non ha il colore!): errore di tipo a run-time.
Quindi non si può restringere il tipo del parametro GridPoint ad un
sottotipo ColoredGridPoint. Non vale covarianza dei parametri.
10
04/06/2014
Controvarianza dei parametri
public static void usaPunto(GridPoint punto1) {
Line lin= punto1.newLine(new GridPoint
}
usaPunto(new ColoredGridPoint
Se fosse consentito ridefinire newLine come:
@Override // \result è una linea colorata come this.col
public Line newLine(Point p)
allora usaPunto(new ColoredGridPoint
deve ricevere in ingresso un Point
invocherebbe la la nuova newLine, che
CORRETTO! Il codice esistente di usaPunto passa a NewLine un GridPoint, che è
sottotipo di Point.
Quindi si può «allargare» il tipo dei parametri a una sovraclasse (si chiama
Controvarianza dei parametri).
ATTENZIONE: Questo è type-safe ma NON è consentito in Java!!!!!
DOMANDA: perché Java, definito originalmente con invarianza dei parametri e dei risultati, a un
certo punto ha consentito Covarianza dei risultati, ma ancora non consente Controvarianza dei
parametri, pur se type-safe?
Definizioni formali di covarianza e
controvarianza
Una trasformazione di tipo P e' un mapping da tipi a tipi.
Es. costrutto array associa un tipo T a un tipo array_of_T
P e' COVARIANTE se per tutti i tipi T,S quando S è sottotipo di T
allora P(S) e' sottotipo di P(T)
ossia mantiene l'ordinamento dei tipi indotto dalla relazione sottotipo
P e' CONTROVARIANTE se per tutti i tipi T,S quando S è sottotipo
di T allora P(T) e' sottotipo di P(S)
ossia inverte l'ordinamento.
P e' detta invariante negli altri casi (es. misti, ecc.)
NB: terminologia deriva da trasformazione delle coordinate negli
spazi vettoriali
enti covarianti (es. la base ortonormale, per una rotazione degli assi; il gradiente
di un campo scalare, per un cambio di scala): seguono una trasformazione uguale
a quella delle coordinate;
enti controvarianti (es. un vettore): seguono la trasformazione opposta.
25
11
04/06/2014
Regola dei metodi
le chiamate ai metodi del sottotipo devono comportarsi come le chiamate
ai metodi corrispondenti del sopratipo
Non può essere verificata dal compilatore
Perché la chiamata a un metodo del sottotipo abbia lo stesso effetto, basta
che la specifica sia la stessa (e quindi ok con estensioni pure):
se y ha tipo statico IntSet, dopo y.insert(x), sappiamo che x è nell insieme y
quando la chiamata termina (indipendentemente dal tipo effettivo di y, se un
IntSet o un MaxIntSet).
se p ha tipo statico Poly e p.coeff(3) restituisce un valore >0, sappiamo che il
grado di p è almeno 3 (indipendentemente dal tipo effettivo di p).
Ma spesso è necessario cambiare la specifica!
Es. iterator() in SortedIntSet() non restituisce gli elementi in un ordine qualsiasi
ma in ordine crescente
In questo caso, è corretto farlo, perche la ensures di IntSet.iterator() era
nondeterministica: qualunque ordine in cui si ritornano gli elementi andava
bene. Allora ritornarli in ordine crescente è accettabile ( rafforziamo la
postcondizione ).
Valgono regole della precondizione più debole e della postcondizione più
forte
26
Sulla Forza e Debolezza delle Condizioni (1)
Che significano?
FORTE: più restrittivo, meno facile da rendere vero, verificato in meno casi
DEBOLE: meno restrittivo, più facile da rendere vero, verificato in più casi
Es: x > 15 è più forte di x > 7, detto altrimenti {x | x > 15} {x | x > 7})
Come si formalizza in Logica Matematica la forza di una condizione?
le condizioni (formule logiche) sono ordinate (anche se non totalmente): dalle più
forti (vere in meno casi) alle più deboli (vere in più casi)
a un estremo FALSE, la condizione più forte di tutte
all altro estremo TRUE, la più debole che ci sia
l operatore logico per indicare che è più forte di è l implicazione ==>
l implicazione logica è una specie di operatore di relazione
che confronta il
valore di verità delle formule
l implicazione corrisponde all inclusione insiemistica dei valori che rendono
vere le formule (dei casi in cui la formula è vera, o modelli)
Es: se è x>15, è x>7, vale l implicazione ==>
in termini insiemistici {x | x > 15} {x | x > 7}
27
12
04/06/2014
Sulla Forza e Debolezza delle Condizioni (2)
Effetto degli operatori logici sulla forza delle condizioni
Disgiunzione (OR, ||) indebolisce
rispetto alla formula , la formula
in cui è vera
||
è vera in qualche caso in più: quelli
Es. (1<x<10) è più forte di (1<x<10) || (20<x<30)
Congiunzione (AND, &&) rafforza
rispetto alla formula , la formula &&
quelli in cui è vera ma non è vera
è vera in qualche caso in meno:
Es. (1<x<10) è più debole di (1<x<10) && dispari(x)
soddisfatte rispettivamente da [2 .. 9] e da {3, 5, 7, 9}
Implicazione (passare da ad ==> ) indebolisce
rispetto alla formula , la formula ==> è vera in qualche caso in più:
quelli in cui è falsa (infatti ==> equivale a ! || )
Es. (1<x<10) è più forte di dispari(x) ==> (1<x<10)
soddisfatte rispettivamente da [2 .. 9] e da [2 .. 9] {x | pari(x)}
inoltre un implicazione si indebolisce se viene rafforzata la premessa
28
Precondizione più debole
Se la precondizione del metodo ridefinito è più debole di quella
del metodo originale, allora tutti i casi in cui si chiamava il
metodo originale si può chiamare anche il metodo ridefinito.
Precondizione: ciò che chiamante deve garantire al chiamato
perché un metodo operi correttamente
se specifichiamo un metodo indebolendo la precondizione, il
chiamante la verifica a fortiori
Regola della precondizione: presuper => presub
In JML questo è sempre verificato
29
13
04/06/2014
Se la precondizione fosse più forte?
public class Stack<T> {
//OVERVIEW: una pila di elementi di tipo T
//..
//@requires true;
//@ensures (* inserisce v in cima a this *);
public void push(T v)
}
public class BoundedStack<T> {
//OVERVIEW: una pila di elementi di tipo T
// di dim. max pari a cento
...
//@requires this.size() <=100;
//@ensures (* inserisce v in cima alla pila *);
public void push(T v)
}
Non c è modo di specificare BoundedStack come erede di Stack.
Se BoundedStack estendesse Stack, allora codice che usa Stack potrebbe non
funzionare
Es. il metodo statico:
//@ensures (* inserisce 200 elementi in s*)
public static void riempiStack (Stack s) {}
riempiStack funzionerebbe con uno Stack, ma non con un BoundedStack,
violando il princ. di sostituzione.
Ovviamente Java consente di definire BoundedStack come erede di
Stack...pero la sua implementazione violera la specifica!
30
Postcondizione più forti
Postcondizione:
ciò che il chiamato deve garantire al chiamante al termine
dell esecuzione, se vale precondizione
Se rafforziamo postcondizione, allora la postcondizione attesa
dal chiamante sarà comunque verificata
più forte nel senso di implica logicamente :
postsub ==> postsuper
(se postsub è vera, allora anche postsuper è vera)
poiché al termine dell esecuzione del metodo, post sub deve valere,
anche postsuper vale, e l utilizzatore del sopratipo non è sorpreso
In JML questo è sempre verificato
31
14
04/06/2014
Esempio dal calcolo numerico
public class Num {
//requires r > 0
//ensures abs(\result * \result - r) < 0.1
}
public class NumPreciso {
//requires r > 0
///ensures abs(\result * \result - r) < 0.01
}
NumPreciso può essere definita come erede di Num: la sua post garantisce la
post originaria
32
Esempio di violazione della regola dei metodi
public class CharBuffer {
//OVERVIEW: Un buffer di caratteri.
public class LowerCaseCharBuffer {
//OVERVIEW: Un buffer di caratteri minuscoli.
...
}
// se x e minuscolo inserisce x in this, se non
e
// minuscolo non assicura nulla
}
Se LowerCaseCharBuffer venisse definito come sottoclasse di
CharBuffer, il principio di sostituzione verrebbe violato.
La regola della segnatura è verificata, ma la regola dei metodi
NO:
La post di insert non inserisce caratteri minuscoli: è più debole.
33
15
04/06/2014
JML ed estensioni
Come si ridefinisce in JML la specifica di un metodo?
Una sottoclasse eredita pre e postcondizioni dei metodi pubblici e protetti della
superclasse e i sui invarianti pubblici
Sintatticamente: tutto come al solito, basta aggiungere //@also
//@also
Ci dice che la specifica non è completa,
ma serve quanto definito nella superclasse
Vanno considerate insieme a
quanto definito nella superclasse
Semanticamente: clausole aggiuntive interpretate in modo da rispettare la
regola dei metodi
la nuova postcondizione si applica solo nel caso in cui valga la nuova
precondizione
In JML è impossibile specificare sottoclassi che non siano sottotipi perchè
non seguono la regola dei metodi
e perciò tutti i nostri (contro)esempi di violazione della regola dei metodi non
sarebbero accettati da JML
34
Semantica in JML
la parte //@requires della classe erede va in OR (||, disgiunzione)
con quella della classe antenato: requires risulta indebolita
la parte //@ensures della classe erede è essenzialmente messa in
AND con quella della classe antenato: ensures risulta rafforzata
Specifica Superclasse
Requires Presup
Ensures Postsup
Specifica Sottoclasse: //@also
Requires Presotto
Ensures Postsotto
Requires Presup || Presotto
Ensures (\old(Presup ) ==> Postsup)) && (\old(Presotto) ==> Postsotto )
35
16
04/06/2014
Esempio (1)
Contratto di un metodo della classe Baratto: se mi dai almeno dieci patate allora ti
do almeno due fragole
int fragole(int patate)
//@requires patate>=10
//@ensures \result >= 2
Esempio di ridefinizione del metodo in una estensione di Baratto:
tutto come prima, ma inoltre se mi dai almeno cinque patate ti do
fragola
//@also
//@requires patate >=5
//@ensures \result>=1
almeno una
La ridefinizione ha la seguente specifica:
//@requires patate>=10 || patate >=5
//@ensures (patate>=10 ==> \result >= 2) &&
(patate>=5 ==> \result >=1)
se mi dai almeno dieci patate ti do almeno due fragole, se mi dai almeno cinque
36
patate ti do almeno una fragola
Esempio (2)
Se volessi ridefinire fragole indebolendo la postcondizione, cioe dicendo
che voglio restuire meno fragole?
//@also
//@requires patate >=10
//@ensures \result>=1
La ridefinizione ha la seguente specifica:
//@requires patate>=10 || patate >=10
//@ensures (patate>=10 ==> \result >= 2) &&
(patate>=10 ==> \result >=1)
cioe :
cioe :
//@requires patate>=10
//@ensures (patate>=10 ==> \result >= 2)
Infatti non e possibile indebolire la postcondizione in JML.
37
17
04/06/2014
Esempio (3)
Se volessi ridefinire fragole rafforzando la postcondizione, cioe dicendo
che voglio restituire più fragole?
//@also
//@requires patate >=10
//@ensures \result>=4
La ridefinizione ha la seguente specifica:
//@requires patate>=10 || patate >=10
//@ensures (patate>=10 ==> \result >= 2) &&
(patate>=10 ==> \result >=4)
cioè:
//@requires patate>=10
//@ensures (patate>=10 ==> \result >= 4)
Quindi per rafforzare la postcondizione, a parita di precondizione, basta
ripetere la precondizione originale e aggiungere la nuova postcondizione
38
piu forte.
Regola completa della postcondizione
Dagli esempi si vede come, dove non vale presuper, può valere
una postsub diversa o incompatibile con postsuper.
(presuper && postsub) => postsuper
La regola della postcondizione più forte vale quindo solo
dove è verificata la precondizione originale:
il codice che usa il metodo della superclasse non chiama il metodo nei
casi aggiunti dalla precondizione più debole nella versione della
sottoclasse
La regola completa della postcondizione è sempre verificata in
JML, per il modo in cui sono unite le specifiche del metodo
nella superclasse e nella sottoclasse.
39
18
04/06/2014
Esempio di Regola completa post.
Esempio: metodo addZero() di IntSet aggiunge 0 solo negli insiemi
non vuoti
//@requires this.size() > 0
public void addZero()
Definiamo sottotipo di IntSet in cui AddZero() ridefinito in modo che
se l insieme è vuoto viene aggiunto 1
//@also
//@requires this.size() = 0
//@ensures this.isIn(1)
public void addZero()
ensures è diversa solo per quei valori (this = vuoto) che non erano legali
per addZero() della superclasse: il codice che usa addZero non è sorpreso
dalla modifica (non chiamera mai addZero con this == vuoto)
In JML, la postcondizione corrisponde a:
(\old(size()>0) ==> isIn(0) ) && (\old(size()=0) ==> isIn(1) )
40
Esempio con tipi primitivi
int (Integer) è sottotipo di long (Long)? O viceversa
Osservazione:
la somma di due int a 32 bit in caso di overflow lancia eccezione,
mentre la somma degli stessi numeri rappresentati come long a 64 bit
calcola il risultato!
Non possono essere uno sottotipo dell altro perche le
rispettive postcondizioni della somma non si implicano.
41
19
04/06/2014
Riassumendo
1. Regola dei metodi può essere enunciata come:
require no more, promise no less:
NON RICHIEDERE DI PIU' del metodo della classe base
NON PROMETTERE DI MENO del metodo della classe base
Entrambe le richieste devono essere soddisfatte
JML impedisce di specificare astrazioni sui dati che violino
la regola dei metodi
NB: Ci sono altri metodi di specifica che lo consentono
in JML, al massimo si può costruire una specifica non soddisfacibile
(pre e/o post condizioni sono false per tutti i valori)
Tuttavia, una classe in Java può essere implementata in modo
scorretto e violare la regola.
42
Attenzione quando si eliminano eccezioni!
IntSet con insert che non ignora i duplicati, ma
lancia un eccezione:
sottoclasse elimina eccezione:
public class IntSet extends ExIntSet {...
public class ExIntSet {
//@ensures !\
//@ signals (DuplicateException e) \old(isIn(x));
public void insert (int x) throws DuplicateException
//NON LANCIA ECCEZIONE
public void insert (int x)
}
}
Regola della segnatura: OK, ma Regola dei metodi: KO!
Utilizzatore di IntSet potrebbe usare l eccezione Duplicate anche per stabilire se
l elemento era gia presente e sara sorpreso...
Dim: Sia x in this. Postsub è vera. Ma PostSuper è falsa (eccezione non è lanciata).
Quindi si può eliminare un eccezione dalla segnatura solo se non è effettivamente
usata o se il suo lancio è opzionale
Ad esempio se era prevista non per trattare violazioni di precondizioni sui dati, ma per il verificarsi di
altre condizioni che non possono più verificarsi. Notare la differenza con le precondizioni, che
possono invece essere indebolite.
Es. SportelloApertoException usata per metodo read() di una classe che descrive un floppy e poi
in una sottoclasse che descrive una memoria SD rimovibile.
43
20
04/06/2014
Es. eliminazione eccezioni
Nell es. BoundedStack non può essere definita come erede di Stack.
La Stack può essere definita come erede di BoundedStack?
Stack.push: //@ ensures (* inserisce v in cima *);
BoundedStack.push:
//@ requires this.size() <=100;
//@ ensures (* inserisce v in cima *);
SI, la regola dei metodi è soddisfatta.
Se però contratto di BoundedStack.push fosse:
//@ signals (OverflowExcepion e) this.size() >100;
//@ ensures \old(size())<100 ==> (* inserisce v in cima *);
allora violata regola della postcondizione.
44
Regola delle proprietà
Sottotipo deve conservare le proprietà degli oggetti del sovratipo
Sono proprietà generali incluse nel contratto della classe, e deducibili dal
contratto dei metodi. Non compaiono pero esplicitamente nei metodi
proprietà definite tipicamente nella OVERVIEW o come public invariant
del sopratipo.
Occorre mostrare che tutti metodi nuovi o ridefiniti, inclusi i costruttori, del
sottotipo conservano le proprietà invarianti e le proprietà evolutive del
sovratipo, osservabili con i metodi pubblici observer della sopraclasse
Si usa solo specifica dei metodi, non implementazione. Fra un osservazione e
l altra, e possibile che vengano chiamati anche i metodi nuovi dell estensioe.
proprietà invariante: proprietà degli stati astratti osservabili dalla
sopraclasse ().
es: size() di un IntSet è sempre >= 0. Grado di un Poly non è negativo.
proprietà evolutiva: relazione fra uno stato astratto osservabile e lo stato
astratto osservabile successivo
Es. Mutabilità; grado di un Poly non cambia
45
21
04/06/2014
Esempio violazione proprietà invariante
tipo FatSet: come IntSet ma non è mai vuoto:
public class FatSet {
//OVERVIEW Un SimpleSet è insieme mutabile di interi con
// almeno un elemento
//@ public invariant this.size()>=1; //sempre almeno un elemento
//@ ensures (*costruisce insieme {i}*)
public FatSet(int i)
//@ ensures \old(this.size()>=2) ==> !this.isIn(x); //elimina x da this
public void removeNonEmpty(int x)
}
public class IntSet {
...
//@ensures !this.isIn(x);
}
IntSet non può essere definita come sottoclasse di FatSet (IntSet extends FatSet)
perché ne viola la proprietà (anche solo aggiungendo costruttore IntSet() per
l insieme vuoto). Notare che IntSet sembra estensioen pura di SimpleSet (ma non
lo e perche ne viola le proprieta )
46
Esempio violazione proprietà evolutiva
Tipo SimpleSet come IntSet ma senza remove:
public class SimpleSet
//OVERVIEW Un SimpleSet è insieme mutabile di
// interi che non può ridursi nel tempo
IntSet non può essere definito come estensione pura di
SimpleSet aggiungendo remove, perché ne viola proprietà
Utilizzatori di SimpleSet sarebbero sorpresi non ritrovando più gli
elementi che avevano inserito in precedenza in un SimpleSet!
47
22
04/06/2014
Altro esempio di violazione proprietà
evolutiva
Sottoclasse di Stack che aggiunge un metodo riflesso
public class MirrorStack extends Stack {
//@ensures (*this diventa il riflesso di \old(this) *);
}
Pur essendo un'estensione pura di Stack, viola la proprietà
evolutiva delle Pile (ossia la politica LIFO)
Codice utilizzatore sarebbe sorpreso di non trovare i dati in
ordine LIFO.
NB: Quindi anche un'estensione pura può violare il principio di
sostituzione
Spesso si preferisce allora definire le estensioni pure come quelle che non
modificano la specifica dei metodi e mantengono le proprietà della
classe.
48
Ereditarietà e collezioni
class Automobile {...}
class AutomobileElettrica extends Automobile {...}
class ParcheggioAutomobili {
public void aggiungi(Automobile a) {
//@ensures (*inserisce auto a in parcheggio*)
....
}
La classe ParcheggioElettriche rappresenta parcheggi speciali dedicati
esclusivamente alle AutomobiliElettriche.
ParcheggioElettriche può essere sottotipo di ParcheggioAutomobili?
public class ParcheggioElettriche extends Parcheggio {
}
50
23
04/06/2014
Parcheggio
Automobile a=new Automobile();
Parcheggio_di_Automobili p=new ParcheggioElettriche(100);
p.aggiungi(a);
Sorpresa: inserisce Automobile non elettrica in Parcheggio_di_Elettriche
(violata regola delle proprietà)!
Se per impedirlo si ridefinisse ParcheggioElettriche.aggiungi(Automobile) in
modo da inserire solo Auto elettriche, si violerebbe invece la postcondizione di
Parcheggio.aggiungi(Automobile)!
quindi un ParcheggioElettriche non può essere definito come sottotipo di
ParcheggioAutomobili
Questo perche' ParcheggioAutomobili e' un tipo Mutabile. Se fosse immutabile,
non ci sarebbe metodo aggiungi(): un ParcheggioElettriche potrebbe essere
definito come estensione di ParcheggioAutomobili (il costruttore non e'
ereditato...)
E' lo stesso problema già visto con i generici: Collection<String> non e'
sottotipo di Collection<Object>
51
Riassumendo: il principio di sostituzione
Tre proprietà della specifica dei tipi per compatibilità:
Regola della segnatura: un sottotipo deve avere tutti i metodi del
sopratipo, e segnature dei metodi del sottotipo devono essere
compatibili.
Garantito dal compilatore Java
Regola dei metodi: le chiamate ai metodi del sottotipo devono
comportarsi come le chiamate ai metodi corrispondenti del sopratipo.
Garantito da JML ma non da Java
Regola delle proprietà: sottotipo deve preservare tutti i public invariant
e le proprieta evolutive degli oggetti del sovratipo.
Garantito in parte da JML
L implementazione Java deve essere aderente alla propria
specifica.
52
24
04/06/2014
Consigli per usare ereditarietà
Un approccio errato
Ereditarietà = riuso diretto codice.
Es: classe Persona eredita da classe Data perché ha una data di
nascita
Approccio corretto: principio di sostituzione
Un oggetto della classe derivata deve potere essere sostituito
ovunque ci sia un oggetto della classe base.
Persona non può essere usato dove c'è una Data! Vi siete iscritti in
data "Mario Rossi"?
Ereditarietà quindi non è un meccanismo di condivisione/riuso del
codice: la sottoclasse deve essere simile alla sopraclasse
53
Esempio: Quadrato vs Rettangolo
Un Quadrato può essere erede di Rettangolo?
(es: costruttore public Quadrato(float l){ lato1 = lato2 = l})
Dipende (di solito no): Se Rettangolo ha metodo stretch() per allungare o
public void stretch() //@ensures (*cambia rapporto tra i lati*)
Tipica soluzione corretta:
classe astratta Figura, e poi Rettangolo e Quadrato definiti come eredi di
Figura. Se si vuole riusare in Quadrato il codice scritto per Rettangolo
basta p.es. che Quadrato includa un attributo privato di tipo Rettangolo.
54
25