Astrazioni sui dati :
Ragionare sui Tipi di Dato
Astratti
1
Correttezza dell’implementazione
L’implementazione del tipo di dato soddisfa la
specifica
Rispetto al caso delle procedure stand-alone piu’
complicato
1.
2.
Specifica: descrizione astratta degli oggetti +
metodi pubblici e le loro proprieta’
Implementazione: rappresentazione +
implementazione dei metodi
2
Correttezza dell’implementazione
Bisogna provare due cose:
1. La rappresentazione (implementazione) dei dati
soddisfa le proprieta’ richieste nella
OVERVIEW
2. I metodi soddisfano le loro specifiche (simile al
caso delle procedure stand-alone)
3
Correttezza dell’implementazione
Le due cose sono pero’ strettamente correlate
1. La correttezza dei metodi puo’ dipendere da
proprieta’ della rappresentazione
1.
La correttezza della rappresentazione dipende
dai metodi, dato che i metodi creano e
modificano gli oggetti
4
Servono altre informazioni
Funzione di astrazione: collega la
rappresentazione degli oggetti con la loro
descrizione astratta
Invariante di rappresentazione: specifica
proprieta’ della rappresentazione degli
oggetti
5
Primo problema
se vogliamo dimostrare che le implementazioni
dei metodi soddisfano le rispettive specifiche
l’implementazione utilizza la rappresentazione
– nel caso di IntSet
– private Vector els;
le specifiche esprimono proprietà dei dati astratti,
ovvero degli insiemi
– nel caso di IntSet
public boolean isIn (int x)
// EFFECTS: se x appartiene a this ritorna
// true, altrimenti false
Per sapere se l’implementazione la soddisfa devo
sapere come è rappresentato un insieme
6
Cosa manca
Un modo per mettere in relazione tra loro i due
“insiemi di valori” concreti ed astratti
valori concreti: stati degli oggetti della classe
(espressi da un insieme di var d’istanza)
valori astratti: il tipo di dato specificato
nel caso di IntSet: devo collegare il vettore els
all’insieme di interi che rappresenta
7
La funzione di astrazione 1
la funzione di astrazione cattura l’intenzione del
progettista nello scegliere una particolare
rappresentazione
la funzione di astrazione
a : C --> A
porta da uno stato concreto
– lo stato di un oggetto della classe C
a uno stato astratto
– lo stato dell’oggetto astratto
8
Esempio
public class IntSet {
// OVERVIEW: un IntSet è un insieme modificabile
// di interi di dimensione qualunque
private Vector els; // la rappresentazione
a deve mappare vettori di interi in insiemi
a ([1,2]) = {1,2}
a ([3,1]) = {1,3}
a ([1,3,2]) = {1,2,3}
9
La funzione di astrazione 2
la funzione di astrazione è generalmente una funzione
molti-a-uno
public class IntSet {
// OVERVIEW: un IntSet è un insieme modificabile
// di interi di dimensione qualunque
private Vector els; // la rappresentazione
– piú stati concreti (vettori di interi) vengono portati nello stesso
stato astratto (insieme )
– a ([1,2]) = {1,2}
–
a ([2,1]) = {1,2}
10
Come definire la funzione di
astrazione?
il problema è che non abbiamo una rappresentazione
esplicita dei valori astratti (solo specifica informale)
l’oggetto da rappresentare può essere descritto da un
oggetto astratto tipico della classe dato nella OVERVIEW
della specifica.
per dare la funzione possiamo usare la notazione
matematica e la notazione del linguaggio di
programmazione con opportune abbreviazioni.
11
La funzione di astrazione di
IntSet
public class IntSet {
// OVERVIEW: un IntSet è un insieme modificabile
// di interi di dimensione qualunque
// un tipico IntSet è {x1, ,xn }
private Vector els; // la rappresentazione
a(c) = { c.els.get(i).intValue() |
0 <= i < c.els.size() }
C metavariabile che rappresenta un oggetto
generico di IntSet
12
La funzione di astrazione di
Poly
public class Poly {
// OVERVIEW: un Poly è un polinomio a
// cofficienti interi non modificabile
// un tipico Poly: c0 + c1*x + c2*x2 + ...
private int[] termini; // la rappresentazione
private int deg; // la rappresentazione
rappresentiamo il polinomio con
– array che ha la dimensione del grado piu’
uno
– l’elemento in posizione n contiene il coefficiente del termine che
ha esponente n (eventualmente 0)
– deg contiene il grado del polinomio
La funzione di astrazione collega l’array al
polinomio
13
La funzione di astrazione di
Poly
public class Poly {
// OVERVIEW: un Poly è un polinomio a
// cofficienti interi non modificabile
// un tipico Poly: c0+ c1 x + c2 x2 + ...
private int[] termini; // la rappresentazione
private int deg; // la rappresentazione
a(c) = c0+ c1 x + c2 x2 + ... tale che
ci = c.termini[i] se 0 <= i < c.termini.length
= 0 altrimenti
notare che il valore di deg non ha nessuna influenza sulla
funzione di astrazione
– è una informazione derivabile dall’array termini che utilizziamo nello
stato concreto per questioni di efficienza
14
Un altro problema
non tutti gli stati concreti “rappresentano”
correttamente uno stato astratto
infatti gli oggetti concreti soddisfano delle
proprieta’, sono costruiti in modo opportuno
queste proprieta’ servono tipicamente sia per la
correttezza della rappresentazione sia per quella
dei metodi
Da cosa derivarle
15
Esempio
public class IntSet {
// OVERVIEW: un IntSet è un insieme modificabile
// di interi di dimensione qualunque
// un tipico IntSet è {x1, ,xn }
private Vector els; // la rappresentazione
// la funzione di astrazione
// a(c) = { c.els.get(i).intValue() |
0 <= i < c.els.size() }
il vettore els potrebbe contenere più occorrenze dello
stesso elemento o contenere valori non di tipo Integer
– questo sarebbe coerente con la funzione di astrazione
– ma non rispecchierebbe la nostra scelta di progetto (riflessa
nell’implementazione dei metodi , vedi per esempio size, remove)
16
L’invariante di rappresentazione
l’invariante di rappresentazione (rep invariant) è
un predicato
I : C --> boolean
che è vero per gli stati concreti che sono
rappresentazioni legittime di uno stato astratto
esprime le proprieta’ fondamentali della
rappresentazione (implementazione) degli oggetti
in base alle quali abbiamo definito i metodi
17
L’invariante di rappresentazione
l’invariante di rappresentazione, insieme alla funzione di
astrazione, riflette le scelte relative alla rappresentazione
– deve essere inserito nella documentazione della implementazione
come commento
la funzione di astrazione è definita solo per stati concreti
che soddisfano l’invariante
18
L’invariante di rappresentazione
di IntSet
I(c) = c.els != null e
per ogni intero i, c.els.get(i) è un Integer
e per tutti gli interi i,j, tali che
0 <= i < j < c. els.size(),
c.els.get(i).intValue() !=
c.els.get(j).intValue()
il vettore non deve essere null
gli elementi del vettore sono tutti distinti
gli elementi del vettore devono essere Integer
– assunti soddisfatti in a
19
L’invariante di rappresentazione
di Poly
public class Poly {
private int[] termini; // la rappresentazione
private int deg; // la rappresentazione
// la funzione di astrazione
// a(c) = c0 + c1*x + c2*x2 + ... tale che
// ci = c.termini[i] se 0 <= i < c.termini.size()
//
= 0 altrimenti
// l’invariante di rappresentazione:
// I(c) = c.termini != null e
// c.termini.length >= 1 e
// c.deg = c.termini.length-1 e
// c.deg > 0 ==> c.termini[deg] != 0
Il grado e’ l’esponente massimo con coefficiente non zero
Il grado (deg) deve essere uguale alla lunghezza dell’array -1
Il polinomio vuoto e’ rappresentato da un array di un elemento
20
Validita’ dell’invariante
Come facciamo a dimostrare formalmente
che tutti gli oggetti del nuovo tipo di dato
soddisfano l’invariante?
Notiamo che, dato che la rappresentazione e’
invisibile, nuovi oggetti possono essere creati solo
coi costruttori e manipolati solo attraverso metodi
pubblici della classe
Questo suggerisce che per provare la validita’
dell’invariante si puo’ procedere per induzione sul
tipo di dato
21
Induzione sui dati
induzione sul numero di invocazioni di metodi
usati per produrre il valore corrente dell’oggetto
Caso Base: dimostriamo che l’invariante vale per
gli oggetti restituiti dai costruttori
22
Caso induttivo
per tutti i metodi produttori o modificatori separatamente
facciamo vedere che
assumendo che l’invariante valga (IPOTESI INDUTTIVA)
• per this
• per tutti gli argomenti del tipo
• per gli oggetti del tipo ritornati o modificati da altre chiamate di
metodi interne
allora quando il metodo termina l’invariante vale
• per this
• per tutti gli argomenti del tipo
• per gli oggetti del tipo ritornati
23
Commenti
Notate che questo ragionamento induttivo non
sarebbe possibile se la rappresentazione fosse
visibile
Chi ci garantirebbe che qualcuno non faccia
qualche pasticcio nella rappresentazione
dall’esterno?
Altro vantaggio di questo stile di
programmazione, piu’ sicuro, piu’ facile ragionare
sulla correttezza
24
IntSet
Caso Base: il costruttore
Caso induttivo: per i modificatori insert e
remove assumiamo che this soddisfi
l’invariante e facciamo vedere che la
soiddisfano anche dopo
Notate che gli altri metodi non modificatori
preservano banalmente l’invariante (non
modificano this)
25
IntSet 1
public class IntSet {
private Vector els; // la rappresentazione
// I(c) = c.els != null e
// per ogni intero i, c.els.get(i) è un Integer
// e per tutti gli interi i,j, tali che
//
0 <= i < j < c. els.size(),
// c.els.get(i).intValue() !=
//
c.els.get(j).intValue()
public IntSet ()
{els = new Vector();}
il costruttore soddisfa l’invariante perché
restituisce un Vector vuoto (non null)
26
IntSet 2
// I(c) = c.els != null e
// per ogni intero i, c.els.get(i) è un Integer
// e per tutti gli interi i,j, tali che
//
0 <= i < j < c. els.size(),
// c.els.get(i).intValue() !=
//
c.els.get(j).intValue()
public void insert (int x)
{Integer y = new Integer(x);
if (getIndex(y) < 0) els.add(y); }
private int getIndex (Integer x)
// EFFECTS: se x occorre in this ritorna la
// posizione in cui si trova, altrimenti -1
il metodo insert preserva l’invariante
perché aggiunge x a this solo se x non è
già in this, e lo aggiunge come Integer
27
IntSet 3
// I(c) = c.els != null e
// per ogni intero i, c.els.get(i) è un Integer
// e per tutti gli interi i,j, tali che
//
0 <= i < j < c. els.size(),
// c.els.get(i).intValue() !=
//
c.els.get(j).intValue()
public void remove (int x)
{int i = getIndex(new Integer(x));
if (i < 0) return;
els.set(i, els.lastElement());
els.remove(els.size() - 1);}
il metodo remove preserva l’invariante
perché rimuove solo
28
Correttezza
dopo avere dimostrato l’invariante possiamo
provare la correttezza dei metodi
per ogni metodo separatamente, facciamo vedere
che l’implementazione soddisfa la specifica
– usando il fatto che gli oggetti concreti soddisfano
l’invariante
– usando la funzione di astrazione per collegare dati
concreti ed astratti
– assumendo che le chiamate di altri metodi della classe
interne siano corrette
29
Correttezza di IntSet 1
public class IntSet {
private Vector els; // la rappresentazione
// la funzione di astrazione
// a(c) = { c.els.get(i).intValue() |
0 <= i < c.els.size() }
public IntSet ()
// EFFECTS: inizializza this a vuoto
{els = new Vector();}
l’astrazione di un vettore vuoto è proprio l’insieme vuoto
30
Correttezza di IntSet 2
public class IntSet {
private Vector els; // la rappresentazione
// la funzione di astrazione
// a(c) = { c.els.get(i).intValue() |
0 <= i < c.els.size() }
public int size ()
// EFFECTS: ritorna la cardinalità di this
{return els.size(); }
il numero di elementi del vettore è la cardinalità
dell’insieme perché
– la funzione di astrazione mappa gli elementi del vettore in quelli
dell’insieme
– il rep invariant garantisce che non ci sono elementi duplicati in
els
• Possiamo usare size() di Vector
31
Correttezza di IntSet 3
public void remove (int x)
// MODIFIES: this
// EFFECTS: toglie x da this
{int i = getIndex(new Integer(x));
if (i < 0) return;
els.set(i, els.lastElement());
els.remove(els.size() - 1);}
se x non occorre nel vettore non fa niente
– corretto perché in base alla funzione di astrazione x non
appartiene all’insieme
se x occorre nel vettore lo rimuove
– e quindi in base alla funzione di astrazione x non
appartiene all’insieme modificato
32
Correttezza di IntSet 4
public void remove (int x)
// MODIFIES: this
// EFFECTS: toglie x da this
{int i = getIndex(new Integer(x));
if (i < 0) return;
els.remove(i);}
Il rep invariant garantisce che non ci sono elementi
duplicati (getIndex ritorna l’unico indice in cui occorre
l’elemento se compare)
33
Eccezioni
L’invariante garantisce anche che il vettore
non sia null
Di conseguenza nei metodi, quando si
accede al vettore, non viene sollevata una
eccezione (che sarebbe sbagliata, perche’
non prevista nella specifica)
34
Come scegliere l’invariante?
L’invariante riflette le scelte fatte nella rappresentazione
Deve valere per tutti gli oggetti del tipo (ragionando in
modo induttivo)
Deve contenere abbastanza informazione per derivare le
proprieta’ richieste nella specifica, sia dei dati che dei metodi
Per esempio: per la correttezza di remove e size e’
necessario sapere che nel vettore non ci sono occorrenze
multiple (queste condizioni sono esattamente catturate dal
rep invariant)
35
Invariante e f di astrazione
Devono sempre essere inserite come commento
all’implementazione
Sono fondamentali per ragionare sulla correttezza
dell’implementazione del tipo di dato astratto
Possono anche essere implementate ed utilizzate da
programmi test per verificare le proprieta’ a run-time
36
Funzione di astrazione
non avendo una rappresentazione esplicita dei valori
astratti, possiamo rappresentarli come stringhe
a questo punto, possiamo implementare la funzione di
astrazione, che è esattamente il metodo toString
– utile per stampare valori astratti
// a(c) = { c.els.get(i).intValue() |
0 <= i < c.els.size() }
37
toString per IntSet
// a(c) = { c.els.get(i).intValue() |
0 <= i < c.els.size() }
public String toString ()
{String s = "{";
for (int i = 0; i < els.size() - 1; i++) {
s = s + els.get(i).toString() + ","; }
if (els.size() > 0) {
s = s + els.get(els.size() - 1).toString(); }
s = s + "}";
return (s);}
38
Invariante di rappresentazione
Usiamo un metodo repOk che verifica l’invariante pubblico perché
deve poter essere chiamato da fuori della sua classe
ha sempre la seguente specifica
public boolean rep0k ()
// EFFECTS: ritorna true se il rep invariant
// vale per this, altrimenti ritorna false
39
repOK
può essere usato da programmi di test per
verificare se una implementazione preserva
l’invariante
può essere usato dentro le implementazioni di
costruttori e metodi
– creatori, modificatori e produttori dovrebbero chiamarlo
prima di ritornare per assicurarsi che per l’oggetto
costruito o modificato vale l’invariante
• per esempio, dovrebbero chiamarlo insert e remove di
IntSet, add, mul, minus di Poly
se l’invariante non vale si dovrebbe sollevare
FailureException
40
repOK per IntSet
public class IntSet {
private Vector els;
// la
rappresentazione
// I(c) = c.els != null e
// per ogni intero i, c.els.get(i) è un
Integer
// e per tutti gli interi i,j, tali che
//
0 <= i < j < c. els.size(),
// c.els.get(i).intValue() !=
//
c.els.get(j).intValue()
41
repOK per IntSet
public boolean repOk() {
if (els == null) return false;
for (int i = 0; i < els.size(); i++) {
Object x = els.get(i);
if (! (x instanceof Integer)) return false;
for (int j = i + 1; j < els.size(); j++)
if (x.equals (els.get(j))) return false; }
return true; }
42