Abstract Data Type Pag. 1/10 Abstract Data Type (ADT) Iniziamo la nostra trattazione presentando una nozione che ci accompagnerà lungo l intero corso di Laboratorio Algoritmi e Strutture Dati: il Tipo di Dato Astratto (ADT). Useremo gli ADT come strumento di descrizione di una collezione di oggetti quando è nostro interesse specificare cosa una operazione deve fare, tralasciando aspetti implementativi quali la realizzazione di un operatore o l organizzazione delle informazioni. Introdurremo la nozione di ADT facendo riferimento a problematiche ricorrenti nella pratica, evidenziandone quindi le potenzialità. Richiameremo infine, sulla base di quanto espresso in precedenza, i concetti principali del paradigma Object Oriented sottolineando come tale tecnica di programmazione rappresenti uno strumento efficace per dar luogo ai concetti propri del Tipo di Dato Astratto. 1. Tipi di dati e nuovi operatori Ogni linguaggio di programmazione offre un insieme di tipi di dati predefiniti, dati built-in o primitivi, e per ognuno di essi definisce un insieme di operatori applicabili. Esempi di tipi di dati primitivi sono gli interi, i reali ed i caratteri, così come esempi di operatori sono l operatore somma e l operatore di moltiplicazione applicabili ai tipi di dati interi. L applicazione di un operatore ad un tipo di dato è dettato da un insieme di regole. Esempio 1.1 Siano m ed n due interi. Un insieme di regole alle quali gli operatori di divisione e moltiplicazione devono attenersi è dato dalla seguente lista: 1. n/m è NON APPLICABILE se m=0; 2. n*m = 0 se m=0 3. n*1 = n
Abstract Data Type Pag. 2/10 In considerazione di un tipo di dato, D e sulla base degli operatori per esso applicabili possiamo implementare un nuovo operatore per D. Ad esempio, mediante l utilizzo dell operatore di moltiplicazione possiamo implementare l operazione di elevamento a potenza di un numero. Esempio 1.2 Consideriamo il seguente tipo di dato, Ndemo, costituito dall insieme dei numeri naturali e da un insieme ristretto di operatori: iszero, succ, add, molt. Tipo di dato: Ndemo = n tale che n è numero naturale Operatori: iszero( n ) bool succ( n ) m add( n 1, n 2 ) m molt( n 1, n 2 ) m Regole: iszero( zero ) = true iszero( succ(n)) = false add(zero, n ) = n add( succ( n ), m ) = succ( add( n, m )) molt( n, zero ) = zero molt( n, uno ) = n dove: - iszero ritorna true se il valore assunto da n è uguale a zero, altrimenti ritorna false. - add esegue la somma dei valori n 1 e n 2 ; - molt esegue il prodotto dei valori n 1 e n 2 ; - succ ritorna il valore successivo a quello assunto da n. Estendiamo l insieme degli operatori definendo un nuovo operatore: il fattoriale. Sulla base degli operatori applicabili al nostro tipo di dato, non possiamo esprimere il nuovo operatore mediante la classica formula fatt(n)=n*fatt(n-1), poiché non disponiamo dell operatore di sottrazione. Questo però sembra non essere un problema insormontabile, i soli operatori Succ e Molt bastano per implementare l operatore fattoriale come di seguito riportato: Operatore: Fatt( n ) m Regole: Fatt( zero ) = 1; Fatt( succ( n ) ) = succ(n) Molt( Fatt( n ) ); cioè: Fatt( 10 ) = Fatt( succ( 9 ) ) = succ( 9 ) Molt( Fatt( 9 ) ) = 10 Molt( Fatt( 9 ) ) Sulla base dei soli operatori applicabili al tipo di dato Ndemo abbiamo pertanto definito un nuovo operatore. Definizione: l operazione di estensione dell insieme degli operatori definiti per un tipo di dato è definita come Astrazione Funzionale.
Abstract Data Type Pag. 3/10 2. Tipo di dato astratto Similmente all operazione di astrazione funzionale è possibile operare l Astrazione Dati cioè definire un nuovo tipo di dato ed un nuovo insieme di operatori ad esso applicabili e similmente all astrazione funzionale, per la quale il vincolo imposto è l utilizzo degli operatori già definiti, anche l astrazione dati è sottoposta al rispetto di due requisiti: requisito di astrazione e requisito di protezione. Proseguiamo per ordine e proviamo ad operare una astrazione dati definendo quindi un nuovo tipo di dato e l insieme degli operatori ad esso applicabili. Nuovo tipo di dato Consideriamo l insieme dei numeri complessi C= a+ib a R, b R. A partire dal tipo di dato numero reale possiamo definire un nuovo tipo di dato che rappresenti un numero complesso. Procediamo in tal senso e rappresentiamo quindi un numero complesso attraverso una coppia di valori formata da due numeri reali: c = (a,b) a R, b R Nuovi operatori Scelta la rappresentazione del nuovo tipo di dato definiamo gli operatori applicabili. Specifichiamo quindi due soli operatori somma e sottrazione di due numeri complessi che operano nel seguente modo: ComplexSomma(( a 1, b 1 ), ( a 2, b 2 )) ( a, b ) = (a 1 + a 2, b 1 + b 2 ) ComplexSottrazione(( a 1, b 1 ), ( a 2, b 2 )) ( a, b ) = = (a 1 - a 2, b 1 - b 2 ) Date due coppie rappresentanti due numeri complessi, gli operatori di somma e sottrazione restituiscono una coppia rappresentante anch essa un numero complesso. Gli elementi di tale coppia sono nel primo caso la somma dei singoli elementi mentre nel secondo caso la sottrazione di singoli elementi.
Abstract Data Type Pag. 4/10 L utente finale In base alla nostra rappresentazione, l utente finale 1 quando opererà con il nostro nuovo tipo di dato eseguirà sostanzialmente i seguenti passi: 1. definisce tre coppie di numeri ( a 1, b 1 ), ( a 2, b 2 ), ( a 3, b 3 ) 2. inizializza ( a 1, b 1 ), ( a 2, b 2 ) 3. poni ( a 3, b 3 ) = Csomma(( a 1, b 1 ), ( a 2, b 2 )) Le lacune della rappresentazione La rappresentazione da noi scelta, anche se funzionale, possiede però alcuni difetti. Il primo su tutti: l utente finale nell utilizzare il nuovo tipo di dati ed i relativi operatori dovrà necessariamente conoscere la rappresentazione adottata, coppia di due valori reali. Pertanto, qualora decidessimo di modificare la rappresentazione del tipo di dato o di proporre una rappresentazione alternativa alla coppia di valori, l utente finale dovrà necessariamente apportare modifiche all intero lavoro svolto. Altro difetto: essendo la coppia di valori reali definita dall utente (si veda il passo 1), nulla vieta ad egli di procedere in una operazione di somma delle singole componenti o perfino in una operazione di somma della parte immaginaria e della parte complessa di un numero complesso eseguendo in tal caso una operazione non corrispondente alla realtà 2. Insomma nulla vieta all utente di modificare il valore rappresentato dal nostro tipo di dato senza accedere ai soli operatori definiti per esso (ComplexSomma, ComplexSottrazione). La nostra rappresentazione manca in tal caso di due requisiti fondamentali dell astrazione dati: - il Requisito di Astrazione (oppure Incapsulamento), una astrazione dati rispetta questo requisito quando all utente finale vengono nascoste le scelte di realizzazione, rappresentazione del tipo di dato ed implementazione degli operatori. 1 Dove per utente finale si intende una qualsiasi entità che praticamente oppure in maniera del tutto teorica fa uso della nostra rappresentazione. 2 La parte immaginaria e la parte reale di un numero complesso non possono essere sommate.
Abstract Data Type Pag. 5/10 - il Requisito di Protezione (oppure Data Hiding), una astrazione dati rispetta questo requisito quando l utente finale può apportare modifiche e quindi elaborare il nuovo tipo di dati solo attraverso l utilizzo dei soli operatori dichiarati all atto della specifica. Come abbiamo già visto, il rispetto del requisito di astrazione permette la modifica della rappresentazione del tipo di dato o l implementazione di una rappresentazione alternativa senza dover necessariamente obbligare l utente finale ad apportare modifiche al proprio lavoro. Mentre il rispetto del requisito di protezione aiuta l utente nel non eseguire una operazione non corretta o qualsivoglia non corrispondente alla realtà. Un Tipo di Dato Astratto è la rappresentazione di un tipo di dato in cui è presente sia il requisito di astrazione che il requisito di protezione. Formalmente: Definizione: un tipo di dati astratto (ADT) è un modello matematico (astratto) descrivente un tipo di dati. Un ADT specifica: 1. il tipo di dato; 2. l insieme dei soli operatori applicabili al tipo di dato; 3. i vincoli di correttezza ai quali gli operatori si attengono. Esempio 2.1 L ADT Coda Di seguito la rappresentazione mediante ADT di una coda di elementi. La politica di gestione di una coda è la usuale politica applicata ad esempio all interno degli esercizi pubblici: il primo che arriva è servito! Tale politica è identificata attraverso la sigla FIFO (First In First Out): il primo ad entrare è il primo ad uscire. Segue la definizione: Tipo di dato: Coda (sequenza di n elementi estratti da un insieme S) Operatori: isempty() risultato Restituisce true se la coda è vuota, false altrimenti Aggiungi( elemento e ) Aggiunge l elemento e al termine della coda Preleva() elemento Preleva il primo elemento in coda
Abstract Data Type Pag. 6/10 Sulla base dell esempio 2.1, approfondiamo il significato della definizione di ADT. Un ADT non deve specificare né come un tipo di dato è rappresentato né come un operatore è implementato (Incapsulamento), esso deve indicare esclusivamente la tipologia ( sequenza di n elementi estratti da un insieme S ), cioè cosa il tipo di dato rappresenta. Un ADT deve specificare l insieme degli operatori applicabili ad un tipo di dati, essi devono rappresentare gli unici strumenti adottabili dall utente finale per l elaborazione dei valori rappresentati dal tipo di dati (Data Hiding). Nella pratica quotidiana, i concetti espressi dall ADT tornano utili durante l utilizzo della rappresentazione: sono separati gli interessi di chi vuol semplicemente utilizzare il nuovo tipo di dati da chi è interessato alla effettiva realizzazione e/o implementazione degli operatori. Utilità riscontrabile anche in fase di progettazione durante la quale l analista potrà concentrarsi piuttosto su cosa il nuovo tipo di dati dovrà fare e non su come dovrebbe farlo. Nota Approccio simile è utilizzato nella fase di editing di un programma scritto in linguaggio C o C++. E uso comune, infatti, creare due file distinti: il file di include (estensione.h), contenente i prototipi delle funzioni o l interfaccia delle classi, ed il file sorgente (estensione.c o.cpp) contenente le implementazioni.
Abstract Data Type Pag. 7/10 3. L approccio OO Sulla base dei concetti introdotti nel Capitolo 2 possiamo affermare che un ADT è implementabile solo mediante l utilizzo di un linguaggio di programmazione che: 1. permetta la definizione di nuovi tipi di dati; 2. offra costrutti di linguaggio che permettano di esprimere la diretta dipendenza esistente tra il tipo di dato e l operatore; 3. vieti ogni tentativo di applicazione al nuovo tipo di dato di un qualsiasi operatore non dichiarato nella specifica; I linguaggi Object Oriented (OO) rappresentano al momento una famiglia di linguaggi di programmazione che esprimono al meglio i concetti intrinseci di un ADT. Un linguaggio di programmazione OO ( C++, Java, etc. ) garantisce la presenza di tutti gli strumenti utili per la corretta definizione di un tipo di dato astratto dove la Classe ne rappresenta la naturale realizzazione. Esempio 3.1 Tipo di dato Ndemo Consideriamo nuovamente il nostro primo tipo di dato trattato precedentemente: Ndemo. Attraverso l utilizzo di un linguaggio OO, nello specifico il C++, possiamo dar luogo a questo tipo di dato implementando la seguente classe: Definizione class Ndemo private: int ValoreNumerico; public: bool iszero(); Ndemo succ(); Ndemo add( Ndemo n); Ndemo molt( Ndemo n ); ; Implementazione bool Ndemo::iszero() if ( ValoreNumerico == 0 ) return true; else return false;
Abstract Data Type Pag. 8/10 Ndemo Ndemo::succ() Ndemo ValoreRitorno; ValoreRitorno.ValoreNumerico = ValoreNumerico+1; return ValoreRitorno; Ndemo Ndemo::add( Ndemo n) Ndemo ValoreRitorno; ValoreRitorno.ValoreNumerico = ValoreNumerico+ n.valorenumerico; return ValoreRitorno; Ndemo Ndemo::molt( Ndemo n ) Ndemo ValoreRitorno; ValoreRitorno.ValoreNumerico = ValoreNumerico* n.valorenumerico; return ValoreRitorno; In tal modo l utente che utilizzerà il nostro nuovo tipo di dato definirà dapprima le variabili di tipo Ndemo attraverso dichiarazione del tipo Ndemo var1, var2, var3; e dopodichè elaborerà i dati attraverso istruzioni del tipo: var3 = var1.somma( var2 ); var3 = var1.prodotto( var2 ); Il compilatore vieterà l applicazione di operatori non dichiarati in fase di specifica o definizione, ad esempio var1 + var2 così come vieterà l applicazione di assegnamenti del tipo var1.valorenumerico = 10 poichè essendo ValoreNumerico un attributo privato non risulterà accessibile dall esterno.
Abstract Data Type Pag. 9/10 Esempio 3.2 - I numeri complessi Realizziamo il tipo di dato numero complesso. Al fine di fornire una rappresentazione più naturale degli operatori di somma e prodotto abbiamo utilizzato in questo caso la possibilità offerta dal linguaggio di ridefinire l operatore + e - Definizione class Complex private: float reale; // parte reale float immaginaria; // parte immaginaria public: Complex operator+( const Complex B ); Complex operator-( const Complex B ); ; Implementazione Complex Complex::operator +( const Complex B ) Complex ValoreRitorno; ValoreRitorno.reale = reale + B.reale; ValoreRitorno.immaginaria = immaginaria + B.immaginaria; return ValoreRitorno; Complex Complex::operator -( const Complex B ) Complex ValoreRitorno; ValoreRitorno.reale = reale - B.reale; ValoreRitorno.immaginaria = immaginaria - B.immaginaria; return ValoreRitorno; Il codice utente int main( ) Complex A, B, C, D; C = A + B; D = A - B; D = A * B; // Errore!!! L operatore * non è definito per i Complex D.reale = A.reale + A.immaginaria // Errore!!! Reale ed immaginaria non sono // accessibili direttamente dall utente
Abstract Data Type Pag. 10/10 Nota Il codice utente non fa alcun riferimento ai due attributi float utilizzati per rappresentare un numero complesso. Il compilatore del linguaggio blocca ogni tentativo di applicazione al tipo di dato Complex di un operatore non dichiarato nella specifica. Infine, qualsiasi modifica alla rappresentazione dei numeri complessi, ad esempio utilizzo di un vettore di due float, non comporta alcuna modifica al codice scritto dall utente e l utente finale non ha modo di applicare operazione considerate non lecita per l insieme dei numeri complessi.