Interfacce come Tipi di Dati Astratti

Ricordiamo dalla lezione sulle interfacce che:

Le interfacce in Java hanno almeno due ruoli fondamentali:

  • una interfaccia consente di fattorizzare informazioni comuni a più classi in modo indipendente dalla relazione di ereditarietà: compensa la mancanza di ereditarietà multipla;

  •  
  • una interfaccia può essere vista come una specifica astratta del comportamento di una collezione di oggetti (Tipo di dati astratto): una classe che implementa l'interfaccia fornisce una possibile realizzazione della specifica.
Abbiamo introdotto le interfacce sottolineandone il primo ruolo, ma ora le useremo principalmente come specifica di tipi di dati astratti (come code, pile, liste, insiemi, alberi binari).



Tipi di Dati...

Un "Tipo di Dati" definisce:
    • un insieme di elementi, enumerandoli o descrivendone la struttura;
    • un insieme di operazioni su tali elementi.
Esempi:
  • Numeri interi in rappresentazione decimale
    • Gli elementi sono 0, 1, -1, 2, -2, ...
    • Le operazioni sono somma, sottrazione, moltiplicazione, ... come imparate alle elementari.

    •  
  • Una classe Java (esempio: BankAcconunt)
    • Gli elementi sono le istanze.
    • Le operazioni sono i metodi (e.g. withdraw, deposit,...) e modifiche/letture di variabili d'istanza.



...e Tipi di Dati Astratti

Un "Tipo di Dati Astratto" (Abstract Data Type) definisce 
    • un insieme di elementi;
    • un insieme di operazioni su tali elementi, di cui viene fornita una specifica (che può essere formale o informale)
    Gli elementi del TDA non sono descritti esplicitamente, ma implicitamente tramite le operazioni.
Esempi:
  • Gli insiemi, come imparati alla scuola
    • Gli elementi sono gli insiemi, ovviamente...
    • Le operazioni sono unione, intersezione, ... Ad esempio, la specifica della 'differenza tra insiemi' è: 
    "A\B" contiene tutti gli elementi di A che non appartengono a B

     
  • I numeri naturali secondo l'assiomatizzazione di Peano.

  •  
  • Un'interfaccia Java (esempio: Comparable)
    • Gli elementi saranno istanze di classi che implementano l'interfaccia: non sono descritti direttamente;
    • Le operazioni sono i metodi astratti dell'interfaccia.



Realizzazione di un TDA

Un Tipo di Dati è una realizzazione di un Tipo di Dati Astratto se definisce gli elementi e le operazioni del TDA, in modo tale che le operazioni "rispettino" la corrispondente specifica.

Esempi:

  • I numeri naturali in base 10 con le operazioni di addizione e moltiplicazione come imparate alle elementari sono una realizzazione dei naturali di Peano;

  •  
  • Le sequenze di elementi, con opportune operazioni, forniscono una realizzazione degli insiemi.
      
  • Una classe che implementa un'interfaccia è precisamente un tipo di dati che realizza il corrispondente tipo di dati astratto.



Interfaccia (TDA) come 'contratto'

Le interfacce forniscono un supporto linguistico alla nozione di contratto tra chi usa gli oggetti di una classe, e chi realizza la classe stessa.
  • Chi scrive la porzione di programma che usa gli oggetti della classe ne ha una visione astratta: conosce solo le firme dei metodi da usare e una descrizione (informale) delle operazioni, indipendentemente dalla realizzazione.

  •  
  • Chi scrive la classe deve fornire una realizzazione dei metodi dell'interfaccia, fornendo una rappresentazione concreta degli oggetti.
Questo permette di procedere in parallelo alla scrittura delle due parti, e di integrarle alla fine.


Inoltre la classe che implementa l'interfaccia può essere sostituita con un'altra più efficiente (o più conveniente...), senza modificare il programma che la utilizza.

Esempio: Possiamo avere due realizzazione di un'interfaccia Insieme:

  • nella prima la verifica dell'appartenenza all'insieme ha complessità costante, ma l'occupazione di memoria è fissa e proporzionale al massimo numero di elementi previsto;

  •  
  • nella seconda l'appartenenza ha complessità lineare, e l'occupazione di memoria è lineare con la dimensione dell'insieme.



Un semplice esempio: PuntoNelPiano (1)

Definiamo una semplice interfaccia: PuntoNelPiano. Concettualmente, il nostro TDA definisce una collezione di oggetti su cui sono definite queste due semplici operazioni:
 
interface PuntoNelPiano {
   double distanza() ;
   void traslaOrizzont(double deltaX) ;
}
 

Possiamo pensare a due realizzazioni, la prima basata su coordinate cartesiane (PuntoCartesiano):

 
class PuntoCartesiano implements PuntoNelPiano {
    private double asc;
    private double ord;
    public PuntoCartesiano (double x, double y) {
        asc = x;
        ord = y ;
    }
    public double distanza( ) {
     return (double) Math.sqrt(asc * asc + ord * ord) ;
    }
    public void traslaOrizzont(double deltaX) {
        asc = asc + deltaX ;
    }
}

 




PuntoNelPiano (2)

La seconda realizzazione si basa su coordinate polari (PuntoPolare) [si usano i radianti come unità di misura per gli angoli: novanta gradi misurano Math.PI/2, ossia circa 1.57]
 
class PuntoPolare implements PuntoNelPiano {
    private double ro;
    private double theta;
    public PuntoPolare (double d, double a) {
        ro = d;            // distanza dall'origine
        theta = a ; // angolo con l'asse delle ascisse 
    } 
    public double distanza() {
     return ro;
    }
    public void traslaOrizzont(double deltaX) {
        // un semplice aggiornamento di ro e theta
        // con formule di trigonometria...
     }
}





PuntoNelPiano (3)

Un utente dell'interfaccia PuntoNelPiano può usare il nome dell'interfaccia come tipo delle variabili che dichiara, e può assegnare loro come valori, degli oggetti PuntoCartesiano o PuntoPolare [UsaPuntoNelPiano]:
 
 
PuntoNelPiano p1 = new PuntoCartesiano(3.0, 3.0) ;
PuntoNelPiano p2 = new PuntoPolare(4.24, 0.785) ;

System.out.println("Distanza dei due punti dall'origine:");
System.out.println(p1.distanza( ));
System.out.println(p2.distanza( ));

p1.traslaOrizzont(5.7);
p2.traslaOrizzont(5.7);

System.out.println("Distanza dall'origine dopo traslazione:") ;
System.out.println(p1.distanza( )) ;
System.out.println(p2.distanza( )) ;