1 Astrazioni sui dati : Ragionare sui Tipi di Dato Astratti
2 Correttezza dellimplementazione 4 Limplementazione del tipo di dato soddisfa la specifica 4 Rispetto al caso delle procedure stand-alone piu complicato 1. Specifica: descrizione astratta degli oggetti + metodi pubblici e le loro proprieta 2. Implementazione: rappresentazione + implementazione dei metodi
3 Correttezza dellimplementazione 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)
4 Correttezza dellimplementazione 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
5 Servono altre informazioni 4 Funzione di astrazione: collega la rappresentazione degli oggetti con la loro descrizione astratta 4 Invariante di rappresentazione: specifica proprieta della rappresentazione degli oggetti
6 Primo problema 4 se vogliamo dimostrare che le implementazioni dei metodi soddisfano le rispettive specifiche 4 limplementazione utilizza la rappresentazione –nel caso di IntSet –private Vector els; 4 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 4 Per sapere se limplementazione la soddisfa devo sapere come è rappresentato un insieme
7 Cosa manca 4 Un modo per mettere in relazione tra loro i due insiemi di valori concreti ed astratti 4 valori concreti: stati degli oggetti della classe (espressi da un insieme di var distanza) 4 valori astratti: il tipo di dato specificato nel caso di IntSet: devo collegare il vettore els allinsieme di interi che rappresenta
8 La funzione di astrazione 1 4 la funzione di astrazione cattura lintenzione del progettista nello scegliere una particolare rappresentazione 4 la funzione di astrazione : C --> A porta da uno stato concreto –lo stato di un oggetto della classe C a uno stato astratto –lo stato delloggetto astratto
9 Esempio public class IntSet { // OVERVIEW: un IntSet è un insieme modificabile // di interi di dimensione qualunque private Vector els; // la rappresentazione 4 deve mappare vettori di interi in insiemi 4 ([1,2]) = {1,2} 4 ([3,1]) = {1,3} 4 ([1,3,2]) = {1,2,3}
10 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 ) – ([1,2]) = {1,2} – ([2,1]) = {1,2}
11 Come definire la funzione di astrazione? 4 il problema è che non abbiamo una rappresentazione esplicita dei valori astratti (solo specifica informale) 4 loggetto da rappresentare può essere descritto da un oggetto astratto tipico della classe dato nella OVERVIEW della specifica. 4 per dare la funzione possiamo usare la notazione matematica e la notazione del linguaggio di programmazione con opportune abbreviazioni.
12 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 (c) = { c.els.get(i).intValue() | 0 <= i < c.els.size() } C metavariabile che rappresenta un oggetto generico di IntSet
13 La funzione di astrazione di Poly public class Poly { // OVERVIEW: un Poly è un polinomio a // cofficienti interi non modificabile // un tipico Poly: c 0 + c 1 *x + c 2 *x private int[] termini; // la rappresentazione private int deg; // la rappresentazione rappresentiamo il polinomio con –array che ha la dimensione del grado piu uno –lelemento in posizione n contiene il coefficiente del termine che ha esponente n (eventualmente 0) –deg contiene il grado del polinomio 4 La funzione di astrazione collega larray al polinomio
14 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 x private int[] termini; // la rappresentazione private int deg; // la rappresentazione (c) = c0+ c1 x + c2 x tale che c i = c.termini[i] se 0 <= i < c.termini.length = 0 altrimenti 4 notare che il valore di deg non ha nessuna influenza sulla funzione di astrazione –è una informazione derivabile dallarray termini che utilizziamo nello stato concreto per questioni di efficienza
15 Un altro problema 4 non tutti gli stati concreti rappresentano correttamente uno stato astratto 4 infatti gli oggetti concreti soddisfano delle proprieta, sono costruiti in modo opportuno 4 queste proprieta servono tipicamente sia per la correttezza della rappresentazione sia per quella dei metodi 4 Da cosa derivarle
16 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 // (c) = { c.els.get(i).intValue() | 0 <= i < c.els.size() } 4 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 nellimplementazione dei metodi, vedi per esempio size, remove)
17 Linvariante di rappresentazione 4 linvariante di rappresentazione (rep invariant) è un predicato I : C --> boolean che è vero per gli stati concreti che sono rappresentazioni legittime di uno stato astratto 4 esprime le proprieta fondamentali della rappresentazione (implementazione) degli oggetti in base alle quali abbiamo definito i metodi
18 Linvariante di rappresentazione 4 linvariante di rappresentazione, insieme alla funzione di astrazione, riflette le scelte relative alla rappresentazione –deve essere inserito nella documentazione della implementazione come commento 4 la funzione di astrazione è definita solo per stati concreti che soddisfano linvariante
19 Linvariante 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() 4 il vettore non deve essere null 4 gli elementi del vettore sono tutti distinti 4 gli elementi del vettore devono essere Integer –assunti soddisfatti in 4 Al solito usiamo i metodi per esprimere le proprieta (vedi intValue())
20 Linvariante di rappresentazione di Poly public class Poly { private int[] termini; // la rappresentazione private int deg; // la rappresentazione // la funzione di astrazione // (c) = c 0 + c 1 *x + c 2 *x tale che // c i = c.termini[i] se 0 <= i < c.termini.size() // = 0 altrimenti // linvariante 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 4 Il grado e lesponente massimo con coefficiente non zero 4 Il grado (deg) deve essere uguale alla lunghezza dellarray -1 4 Il polinomio vuoto e rappresentato da un array di un elemento
21 Validita dellinvariante 4 Come facciamo a dimostrare formalmente che tutti gli oggetti del nuovo tipo di dato soddisfano linvariante? 4 Notiamo che, dato che la rappresentazione e invisibile, nuovi oggetti possono essere creati solo coi costruttori e manipolati solo attraverso metodi pubblici della classe 4 Questo suggerisce che per provare la validita dellinvariante si puo procedere per induzione sul tipo di dato
22 Induzione sui dati 4 induzione sul numero di invocazioni di metodi usati per produrre il valore corrente delloggetto 4 Caso Base: dimostriamo che linvariante vale per gli oggetti restituiti dai costruttori
23 Caso induttivo 4 per tutti i metodi produttori o modificatori separatamente facciamo vedere che 4 assumendo che linvariante 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 4 allora quando il metodo termina linvariante vale per this per tutti gli argomenti del tipo per gli oggetti del tipo ritornati
24 Commenti 4 Notate che questo ragionamento induttivo non sarebbe possibile se la rappresentazione fosse visibile 4 Chi ci garantirebbe che qualcuno non faccia qualche pasticcio nella rappresentazione dallesterno? 4 Altro vantaggio di questo stile di programmazione, piu sicuro, piu facile ragionare sulla correttezza
25 4 Caso Base: il costruttore 4 Caso induttivo: per i modificatori insert e remove assumiamo che this soddisfi linvariante e facciamo vedere che la soiddisfano anche dopo 4 Notate che gli altri metodi non modificatori preservano banalmente linvariante (non modificano this) IntSet
26 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 linvariante perché restituisce un Vector vuoto (non null)
27 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 linvariante perché aggiunge x a this solo se x non è già in this, e lo aggiunge come Integer
28 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 linvariante perché rimuove solo
29 Correttezza di Poly 1 public class Poly { private int[] termini; // la rappresentazione private int deg; // la 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 public Poly () {termini = new int[1]; deg = 0; } il primo costruttore soddisfa linvariante perché restituisce un Array di un elemento e deg = 0
30 Correttezza di Poly 2 public class Poly { private int[] termini; // la rappresentazione private int deg; // la 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 public Poly (int c, int n) throws NegativeExponentExc if (n < 0) throw new NegativeExponentExc (Poly(int,int) constructor); if (c == 0) {termini = new int[1]; deg = 0; return; } termini = new int[n+1]; for (int i = 0; i < n; i++) termini[i] = 0; termini[n] = c; deg = n; } il secondo costruttore soddisfa linvariante perché testa esplicitamente il caso c=0
31 Correttezza di Poly 3 public class Poly { private int[] termini; // la rappresentazione private int deg; // la 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 public Poly sub (Poly q) throws NullPointerException {return add(q.minus()); } il metodo produttore sub soddisfa linvariante perché (per ipotesi induttiva) –lo soddisfano q e this –lo soddisfano add e minus
32 Correttezza 4 dopo avere dimostrato linvariante dobbiamo provare la correttezza dei metodi 4 per ogni metodo separatamente, limplementazione soddisfa la specifica (assumendo che le chiamate di altri metodi della classe interne siano corrette) –usando il fatto che gli oggetti concreti soddisfano linvariante –usando la funzione di astrazione per collegare dati concreti ed astratti
33 Correttezza di IntSet 1 public class IntSet { private Vector els; // la rappresentazione // la funzione di astrazione // (c) = { c.els.get(i).intValue() | 0 <= i < c.els.size() } public IntSet () // EFFECTS: inizializza this a vuoto {els = new Vector();} 4 lastrazione di un vettore vuoto è proprio linsieme vuoto
34 Correttezza di IntSet 2 public class IntSet { private Vector els; // la rappresentazione // la funzione di astrazione // (c) = { c.els.get(i).intValue() | 0 <= i < c.els.size() } public int size () // EFFECTS: ritorna la cardinalità di this {return els.size(); } 4 il numero di elementi del vettore è la cardinalità dellinsieme perché –la funzione di astrazione mappa gli elementi del vettore in quelli dellinsieme –il rep invariant garantisce che non ci sono elementi duplicati in els Possiamo usare size() di Vector
35 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 allinsieme se x occorre nel vettore lo rimuove –e quindi in base alla funzione di astrazione x non appartiene allinsieme modificato
36 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);} 4 Il rep invariant garantisce che non ci sono elementi duplicati (getIndex ritorna lunico indice in cui occorre lelemento se compare)
37 Eccezioni 4 Linvariante garantisce anche che il vettore non sia null 4 Di conseguenza nei metodi, quando si accede al vettore, non viene sollevata una eccezione (che sarebbe sbagliata, perche non prevista nella specifica)
38 Come scegliere linvariante? 4 Linvariante riflette le scelte fatte nella rappresentazione 4 Deve valere per tutti gli oggetti del tipo (ragionando in modo induttivo) 4 Deve contenere abbastanza informazione per derivare le proprieta richieste nella specifica, sia dei dati che dei metodi 4 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)
39 Esempio: lambiente dei metodi public class MethodEnv { // OVERVIEW: UN METHODENV È UNA // FUNZIONE PARZIALE DA IDENTIFICATORI // DI METODI (STRINGHE) A DESCRIZIONI DI // METODI. NON È MODIFICABILE. private PairIdeVal[] associazioni;
40 Rappresentazione Array di coppie, PairIdeVal e un tipo record che modella le coppie identificatore (String) e descrizione di metodo (Mdescr) Due variabili nome e valore (Object) PairIdeVal
41 Funzione di astrazione (c) = f:String -----> Mdescr dove 0 <= i < c.associazioni.length() f(c.associazioni[i].nome)= c.associazioni[i].valore ed f e indefinita per tutti gli altri valori (funzione parziale) Mappa linsieme di coppie, contenuto nellarray, nella corrispondente funzione (relazione >funzione)
42 Invariante di rappresentazione Esprime le proprieta della rappresentazione Linsieme di coppie contenuto nellarray deve rappresentare una funzione (non tutte le relazioni corrispondono a funzioni) I(c) = c.associazioni!= null e per ogni intero i, 0 <= i < c. associazioni.length(), c.associazioni[i].valore è un Mdescr e per tutti gli interi i,j, tali che 0 <= i < j < c. associazioni.length(), c.associazioni[i].nome != c.associazioni[j].nome
43 Validita dellinvariante 4 Linvariante e soddisfatta (per induzione sui dati, un metodo alla volta) 4 Metodi Produttori (bind ed instantiate): attenzione se esiste gia la descrizione di un metodo con quel nome non possiamo riinserirlo
44 Commenti 4 Linvariante esprime una proprieta necessaria per la correttezza della rappresentazione dei dati e dei metodi, rispetto alle loro specifiche 4 Ogni dato concreto che soddisfa linvariante e mappato tramite la funzione di astrazione in un ambiente dei metodi (come richiesto dallOVERVIEW) 4 La funzione di astrazione non ritorna una funzione se applicata ad un oggetto concreto che non soddisfa linvariante
45 Per i metodi 4 Bisogna ragionare sulle specifiche, uno ad uno usando invariante e funzione di astrazione
46 Invariante e f di astrazione 4 Devono sempre essere inserite come commento allimplementazione 4 Sono fondamentali per ragionare sulla correttezza dellimplementazione del tipo di dato astratto 4 Possono anche essere implementate ed utilizzate da programmi test per verificare le proprieta a run-time
47 Funzione di astrazione 4 non avendo una rappresentazione esplicita dei valori astratti, possiamo rappresentarli come stringhe 4 a questo punto, possiamo implementare la funzione di astrazione, che è esattamente il metodo toString –utile per stampare valori astratti // (c) = { c.els.get(i).intValue() | 0 <= i < c.els.size() }
48 toString per IntSet // (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);}
49 Invariante di rappresentazione 4 Usiamo un metodo repOk che verifica linvariante pubblico perché deve poter essere chiamato da fuori della sua classe 4 ha sempre la seguente specifica public boolean rep0k () // EFFECTS: ritorna true se il rep invariant // vale per this, altrimenti ritorna false
50 repOK 4 può essere usato da programmi di test per verificare se una implementazione preserva linvariante 4 può essere usato dentro le implementazioni di costruttori e metodi –creatori, modificatori e produttori dovrebbero chiamarlo prima di ritornare per assicurarsi che per loggetto costruito o modificato vale linvariante per esempio, dovrebbero chiamarlo insert e remove di IntSet, add, mul, minus di Poly 4 se linvariante non vale si dovrebbe sollevare FailureException
51 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()
52 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; }