Issuu on Google+

Tipus abstractes de dades Jordi Àlvarez Canal P06/05001/00575 Mòdul 1


© FUOC • P06/05001/00575 • Mòdul 1

Tipus abstractes de dades

Índex

Introducció ..............................................................................................

5

Objectius ...................................................................................................

7

1. Contenidors ........................................................................................

9

1.1. Dualitat entre especificació/implementació ..................................

9

1.2. Implicacions matemàtiques dels TAD ............................................ 11 1.2.1. TAD com a conjunt de valors ............................................. 11 1.2.2. Semàntica d’un TAD ........................................................... 12 1.2.3. Especificació d’un TAD ....................................................... 13 1.3. Tipus abstractes de dades en Java ................................................... 14 1.3.1. Biblioteques en Java ............................................................ 15 1.3.2. Dualitat entre especificació/implementació en Java .......... 16 2. Disseny per contracte ....................................................................... 23 2.1. Programació defensiva .................................................................... 24 2.2. Contractes ....................................................................................... 25 2.2.1. Incompliment de contracte ................................................ 26 2.3. Invariant de la representació .......................................................... 28 2.4. Contractes en Java .......................................................................... 29 2.4.1. Eines de disseny per contracte ............................................ 33 3. Desenvolupament d’una col·lecció d’exemple .......................... 35 3.1. Operacions ...................................................................................... 35 3.2. Implementació mitjançant un vector ............................................ 36 3.2.1. Definició de la representació ............................................... 36 3.2.2. Contenidors afitats .............................................................. 37 3.2.3. Implementació de les operacions ....................................... 38 3.2.4. Ús de la col·lecció ................................................................ 40 4. Tipus genèrics o paramètrics ......................................................... 43 5. Biblioteca de col·leccions de l’assignatura ................................. 48 5.1. Jerarquia de col·leccions ................................................................. 48 5.2. Tipus auxiliars ................................................................................. 53 6. Presentació de la resta de mòduls ................................................ 55 Resum ........................................................................................................ 57 Exercicis d’autoavaluació .................................................................... 59


© FUOC • P06/05001/00575 • Mòdul 1

Solucionari ............................................................................................... 61 Glossari ..................................................................................................... 61 Bibliografia .............................................................................................. 62 Annex ........................................................................................................ 63

Tipus abstractes de dades


© FUOC • P06/05001/00575 • Mòdul 1

5

Tipus abstractes de dades

Introducció

En aquest mòdul presentem les nocions bàsiques que ens proporcionaran l’entorn en el qual anirem presentant al llarg del curs els diferents tipus de contenidors. En primer lloc, desenvoluparem les nocions de contenidor i de tipus abstracte de dades (TAD). La primera és una abstracció que ens permet emmagatzemar un

Al llarg del text farem servir indistintament els termes contenidor i col·lecció.

grup d’elements i realitzar les operacions necessàries per gestionar aquest grup: inserir nous elements, esborrar-ne d’existents o consultar l’estat del grup mateix d’elements (quins elements hi ha...). Aquesta noció té una importància pràctica cabdal en el món del desenvolupament de programari, ja que la possibilitat de definir grups d’elements de diferents tipus i la seva gestió és necessària en qualsevol programa d’una complexitat mínima. La segona noció, de tipus abstracte de dades o TAD, ens permetrà desvincular la interfície d’un contenidor de la seva implementació. Aquesta desvinculació ens permetrà definir dos nivells i aspectes diferents: la implementació dels contenidors, i el seu ús, totalment independent del punt anterior. I el millor de tot: no haurem de barrejar aquests dos aspectes. La noció de TAD es remunta al principi dels anys setanta, molt abans de l’aparició dels llenguatges de programació més populars de l’actualitat. De fet, la noció de TAD és completament independent del llenguatge de programació utilitzat. Cada llenguatge disposa d’un conjunt de formalismes i construccions que permeten adaptar-se millor o pitjor per a representar la noció de TAD. Com que en aquesta assignatura s’utilitza Java, veurem com podem definir TAD amb les eines ofertes per aquest llenguatge. Com hem comentat, els TAD ofereixen dos aspectes diferents: la implementació i l’ús. En aquesta dualitat, ¿com definim la frontera entre el que fa el TAD i el que ha de fer el seu usuari (típicament un programador d’aplicacions)? L’especificació d’un TAD és precisament això: la definició del seu comportament de manera que l’usuari pugui programar l’aplicació de forma coherent amb el comportament del TAD. Ara bé, hi ha una tècnica que, fent ús de l’especificació d’un TAD, permet raonar de manera molt més profunda sobre el comportament d’un TAD i la relació que s’estableix entre ell i els seus usuaris. Per a definir bé aquesta relació s’estableix un contracte en què cadascuna de les parts té les seves responsabilitats i els seus drets. Aquest plantejament ofereix dos grans avantatges: d’una banda ajuda els usuaris d’un TAD a tenir clar quines són les seves responsabilitats i què poden esperar d’un TAD si les assumeixen; i de l’altra, aju-

TAD és la sigla de tipus abstracte de dades.


© FUOC • P06/05001/00575 • Mòdul 1

6

den els dissenyadors de TAD a definir clarament el contracte mitjançant l’especificació. Amb tot això, haurem presentat el marc teòric amb què treballarem a l’assignatura. A continuació, posarem en comú tots aquests elements mitjançant un exemple de col·lecció, que presentarem en el mateix format estàndard que utilitzarem per a les col·leccions dels mòduls següents. Aquesta col·lecció d’exemple es presentarà en dues variants diferents: una en què no es fa ús de tipus paramètrics i l’altra en què sí se’n fa ús. Aquesta segona variant ens servirà per a introduir els tipus paramètrics en Java, una novetat de la versió 1.5 de Java (també anomenada Java 5). Al llarg de tota l’assignatura, es continuaran fent servir tipus paramètrics per a presentar les diferents col·leccions en el llenguatge Java. Per acabar, en el mòdul es fa una presentació de la biblioteca de col·leccions de l’assignatura, que conté totes les col·leccions desenvolupades en els diferents mòduls. Si bé els detalls es deixen per als mòduls següents, es fa una descripció general de la biblioteca i una presentació del disseny orientat a objectes de la seva estructura. Aquest punt, a part de donar-vos una impressió general de la biblioteca de col·leccions que utilitzareu durant curs, us servirà de referència bàsica quan l’hagueu de fer servir en les diferents activitats.

Tipus abstractes de dades


© FUOC • P06/05001/00575 • Mòdul 1

7

Objectius

Els materials didàctics d’aquest mòdul proporcionen els coneixements fonamentals perquè assoliu els objectius següents: 1. Entendre les nocions de contenidor i tipus abstracte de dades o TAD; i com a conseqüència d’aquesta segona, la diferència entre especificació i implementació. 2. Saber identificar les responsabilitats del TAD i les del seu usuari a partir de l’especificació d’un TAD. 3. Ser capaç d’assignar –en la tasca de disseny d’un TAD– responsabilitats a un TAD i als seus usuaris a partir dels arguments principals presentats pel disseny per contracte. A partir d’aquestes responsabilitats, poder donar l’especificació del TAD en llenguatge natural. 4. Conèixer i poder aplicar tots els mecanismes oferts pel llenguatge Java per representar TAD (interfícies i classes) i col·leccions on els elements són un tipus qualsevol (tipus paramètrics). 5. Entendre l’organització i l’estructura de la biblioteca de col·leccions de l’assignatura.

Tipus abstractes de dades


© FUOC • P06/05001/00575 • Mòdul 1

9

Tipus abstractes de dades

1. Contenidors

Aquesta assignatura versa especialment sobre com representar i gestionar col·leccions de dades. Qualsevol aplicació mínimament complexa necessita emmagatzemar col·leccions d’elements. Per tant, és molt útil disposar d’una

En els llenguatges d’orientació a objectes, representarem un contenidor amb una classe. En l’assignatura Programació orientada a objectes ja heu fet servir les classes java.util.Vector i java.util.ArrayList per a emmagatzemar col·leccions.

biblioteca de contenidors que pugui ser utilitzada per qualsevol aplicació que haguem de desenvolupar. Però, si es tracta únicament de guardar col·leccions d’objectes, per què parlem de biblioteca de contenidors o de contenidors en plural? Per què no tenim una única classe de contenidor, que faríem servir per a emmagatzemar qualsevol col·lecció? La raó és que hi ha diverses estratègies per a emmagatzemar una col·lecció d’objectes. Cada estratègia té unes característiques concretes que determinaran l’ús que en podrem fer: • Depenent de l’estratègia que triem, necessitarem més o menys espai per a representar els objectes. • Les operacions que podrem fer variaran lleugerament si fem servir una es-

Espai Per espai entenem el nombre de bits en la memòria de l’ordinador que ocuparà una col·lecció.

tratègia o si en fem servir una altra. Així doncs, una estratègia ens pot oferir una operació determinada que una altra estratègia no ens ofereix. • Les mateixes operacions, de vegades, es poden realitzar més eficaçment amb una estratègia que amb una altra. • Algunes estratègies únicament es poden utilitzar si els objectes emmagatzemats compleixen certes condicions, com per exemple que es puguin ordenar. Normalment, cada aplicació tindrà una sèrie de restriccions tant d’eficiència (temporal) com d’espai, i en cada una caldrà emmagatzemar uns tipus d’objectes determinats, als quals necessitarem accedir també d’unes maneres determinades. Aquestes restriccions faran que no totes les estratègies siguin aplicables als mateixos casos. A més, entre les estratègies aplicables en una situació determinada, n’hi haurà de més adequades que altres.

1.1. Dualitat entre especificació/implementació Un cop l’aplicació ja està funcionant, és usual que els requeriments evolucionin amb el temps. Llavors, l’aplicació s’ha d’adaptar als nous requeriments.

Eficiència D’entre els recursos emprats per una aplicació ens podem referir a recursos temporals i a recursos espacials. Conseqüentment, parlarem d’eficiència temporal i d’eficiència espacial. Molts cops, però, trobarem el terme eficiència de manera aïllada. En aquest cas, normalment, el terme farà referència a l’eficiència temporal.


© FUOC • P06/05001/00575 • Mòdul 1

10

Tipus abstractes de dades

Aquests nous requeriments poden fer que les estratègies que es feien servir fins aquell moment per a emmagatzemar col·leccions d’objectes deixin de ser òptimes, i sigui convenient substituir-les per unes altres. Aquest canvi s’hauria de poder fer fàcilment, i hauria de tenir el menor impacte possible en l’aplicació.

Per aquest motiu, i per d’altres que ja anirem desgranant, quan desenvolupem una aplicació, i aquesta ha de fer ús de col·leccions, és important que el codi de l’aplicació estigui tan desvinculat com sigui possible de les estratègies concretes que hem triat per emmagatzemar col·leccions d’objectes.

Evidentment, quan en una aplicació decidim utilitzar un contenidor concret, és perquè necessitarem fer servir algunes (o moltes) de les funcions que ofereix aquest contenidor. Per tant, l’aplicació necessitarà com a mínim estar vinculada a les funcions ofertes pel contenidor. En canvi, no serà necessari que l’aplicació conegui com estan implementades. És a dir, l’aplicació (o resolució del problema) és en realitat independent de la implementació concreta que

Llenguatge Java El llenguatge de programació Java proporciona eines per desvincular les funcions ofertes per un contenidor (interfície) de la seva implementació, tal com veurem en el subapartat 1.2 d’aquesta assignatura.

fem servir. Per tant, serà desitjable desvincular l’aplicació de la implementació o estratègia concreta que estem fent servir. Depenent del llenguatge de programació utilitzat, aquesta desvinculació serà factible o no.

Per a dur a terme aquesta desvinculació, cada col·lecció disposarà de dues parts ben diferenciades:

• L’especificació, que està composta per les signatures dels mètodes (els serveis o funcionalitats que s’ofereixen) i la descripció de cada un d’aquests

Recordeu de Fonaments de programació que la precondició (o postcondició) s’expressa amb un predicat booleà.

mètodes, en forma de precondició i postcondició. • La implementació, que consisteix a associar un algorisme a cada una de les funcionalitats o mètodes oferts en l’especificació. Cada un dels algorismes associats ha de garantir que si a l’inici de la seva execució es compleix la precondició corresponent, un cop executat l’algorisme, es complirà la postcondició.

Des del punt de vista conceptual, l’especificació d’una col·lecció ens diu “què fa la col·lecció” (quin comportament té); mentre que la seva implementació ens diu “com ho fa” (com està implementat aquest comportament).

De la mateixa manera que podem tenir diversos algorismes que compleixin una mateixa especificació, aquí podrem tenir diferents implementacions que proporcionin una mateixa especificació.

Aquesta dualitat entre especificació i implementació és equivalent a la d’especificació-algorisme que es mostra a l’assignatura Fonaments de programació.


© FUOC • P06/05001/00575 • Mòdul 1

11

Tipus abstractes de dades

1.2. Implicacions matemàtiques dels TAD La dualitat entre especificació i implementació no és aplicable únicament a les col·leccions. De fet, podem fer servir aquesta dualitat en un àmbit totalment general, per a qualsevol classe (o tipus), i no únicament restringit a aquelles classes especialitzades a guardar col·leccions d’elements. Per tant, en aquest subapartat parlarem d’especificació (o, respectivament, implementació) d’una classe en el mateix sentit que en el subapartat anterior fèiem servir especificació (implementació) d’una col·lecció. Per a treballar de manera conceptual amb aquesta dualitat entre especificació i implementació, l’any 1974, John Guttag i altres investigadors van introduir el concepte tipus abstracte de dades o, abreujat, TAD (en anglès: abstract data type, ADT).

Terminologia La paraula abstracte denota aquí independència respecte a la implementació concreta que es faci servir.

1.2.1. TAD com a conjunt de valors Un TAD és una abstracció matemàtica que consisteix en un conjunt de valors i operacions definits a partir del que fins ara hem anomenat especificació. Per a entendre què significa això, plantejarem una analogia amb un tipus que ens és ben conegut: el dels nombres naturals (a partir d’ara farem servir Nat per a referir-nos al tipus dels nombres naturals). Un objecte que és de tipus Nat pot tenir el valor 0, o bé el valor 1, o bé el valor 2, etc. En canvi, aquest objecte mai podrà tenir el valor 33,1, ni el valor ‘a’, ni el valor ‘casa’. És a dir, el conjunt de valors associat al tipus Nat és {0, 1, 2, 3, 4, ...}. De la mateixa manera, un objecte que és del tipus corresponent a un TAD T qualsevol, podrà prendre un conjunt de valors determinat pel TAD T mateix: T1, T2, T3... Sobre un objecte de tipus Nat podem realitzar una sèrie d’operacions. Així doncs, podem incrementar-lo o decrementar-lo en una unitat, sumar-hi o restar-ne una quantitat, etc. L’aplicació de cada una d’aquestes operacions produirà una variació en el valor de l’objecte. És a dir, aplicar una operació sobre un objecte de tipus Nat que té un valor determinat pot fer (depenent de la definició de l’operació) que el valor d’aquest objecte es transformi en un altre, que necessàriament ha de estar associat a Nat. Així, per exemple, si incrementem un objecte que té el valor 56, aquest objecte passarà a tenir el valor 57. De la mateixa manera, si a l’especificació de T hi ha definida una operació determinada, quan apliquem aquesta operació sobre un objecte de tipus T, aquest objecte també podrà variar el seu valor (en funció probablement del seu valor anterior).

El tipus Nat En el cas del tipus Nat, sabem que el conjunt de valors associats és infinit. No ha de ser necessàriament així sempre. Podem definir TAD amb un conjunt de valors finit. Imagineu, per exemple, un TAD per a valors booleans.

El formalisme que utilitzarem per a definir un TAD i les seves operacions es mostra en el subapartat 1.2.3 d’aquesta assignatura.


© FUOC • P06/05001/00575 • Mòdul 1

12

Ha de quedar clar que la manera conceptual més adequada de visualitzar un TAD és per referència al següent: 1) El conjunt de valors que poden tenir les instàncies del TAD. 2) L’especificació de les operacions que es poden aplicar sobre les instàncies del TAD.

Tipus abstractes de dades

Domini d’un TAD Observeu que els valors que poden prendre les instàncies d’un TAD no han de fer referència necessàriament a tipus bàsics del llenguatge de programació usat. Es pot tractar de valors d’un domini totalment a part.

D’aquesta manera, aconseguim definir el comportament del TAD de manera totalment independent a la implementació (o implementacions) que s’hi proporcioni.

Amb tot això, podem definir un TAD com un conjunt de valors determinat per un conjunt d’operacions i l’especificació de cada una d’aquestes.

1.2.2. Semàntica d’un TAD El conjunt de valors que pot prendre una instància d’un TAD són únicament símbols, però, de fet, no tenen cap significat. Necessitem alguna manera de donar un significat o semàntica a cada un d’aquests valors. Amb el significat podem, per exemple en el cas dels naturals, saber que el nombre 2 és 1 + 1, o el doble d’1. En canvi, sense significat, únicament tindríem els símbols 2 i 1. Per a dotar de significat el conjunt de valors que pot prendre una instància d’un TAD, la teoria dels tipus abstractes de dades usa l’especificació algebraica. En aquest text no tractarem a fons aquest tema, però sí que creiem interessant exposar-lo breument de manera intuïtiva, cosa que fem a continuació.

Us heu parat mai a pensar de quina manera es defineix la semàntica dels nombres enters? Si heu estudiat àlgebra, probablement ho hàgiu hagut de fer. Els nombres enters, juntament amb les operacions de suma i producte, són un anell commutatiu i unitari. Això vol dir que compleixen el conjunt de propietats d’un anell que sigui commutatiu i unitari. Aquestes propietats estan definides mitjançant un conjunt d’equacions en què intervenen les operacions (en aquest cas suma i producte) i variables que representen instàncies d’enters.

Així, per exemple, una de les propietats d’un anell és la propietat associativa respecte del producte, expressada per l’equació: x · (y · z) = (x · y) · z. En el cas dels TAD, es fa una cosa equivalent mitjançant l’especificació algebraica: es defineixen un conjunt d’equacions que relacionen els valors d’un TAD mitjançant les seves operacions. Aquestes equacions permeten donar significat tant a operacions com a valors. De fet, la semàntica dels uns no és pos-

Si teniu curiositat per la resta de propietats o voleu aprofundir en el tema, podeu recórrer a qualsevol llibre d’àlgebra que tingueu a mà.


© FUOC • P06/05001/00575 • Mòdul 1

13

Tipus abstractes de dades

sible sense els altres. Així, en el cas dels enters no podem “definir” el 2 sense l’operació de suma, i en relació amb algun altre valor (1 + 1). D’altra banda, el conjunt d’equacions que constitueix l’especificació algebraica d’un TAD, a la vegada que dóna significat a valors i operacions, defineix un conjunt de classes d’equivalència entre els termes que podem construir utilitzant valors i operacions. Així, per exemple, en el cas dels enters, l’equació que ens diu que els enters compleixen la propietat associativa respecte del producte ens diu també que el terme (3 · 2) · 7 és equivalent al terme 3 · (2 · 7). Tot això permet definir completament la semàntica d’un TAD. Ara bé, per a fer-ho, cal utilitzar un formalisme matemàtic que no forma part de l’objectiu d’aquesta assignatura. Així que aquí ens limitarem a utilitzar especificacions informals, tal com es veu en el subapartat següent.

1.2.3. Especificació d’un TAD

Per a caracteritzar o definir un TAD, cal proporcionar-ne l’especificació.

Noteu que en el subapartat anterior es diu explícitament que l’especificació d’un TAD en determina el conjunt de valors. Per tant, a partir de l’especificació seríem capaços d’obtenir aquest conjunt de valors. 1) Vegem a continuació com definim un dels exemples de TAD més senzills de tots i que ja hem introduït abans: el TAD Nat. El definirem amb un constructor i tres operacions. El constructor generarà el 0 i, com és l’únic constructor que definirem, l’haurem de fer servir per a construir totes les instàncies de Nat. Les tres operacions definides modificaran la instància del tipus Nat sobre la qual s’executaran, i la primera operació i la segona, la convertiran respectivament en el successor i el predecessor; i la tercera operació hi sumarà una quantitat.

TAD Nat { @pre cert @post El natural construït correspon al nombre natural 0 Nat(); @pre El valor del nombre natural representat és X @post El valor del nombre natural representat és X + 1 void succ(); @pre El valor del nombre natural representat és X > 0

Lectures complementàries Si voleu aprofundir en el tema de l’especificació algebraica i la semàntica d’un TAD podeu revisar les obres següents: X. Franch (2001). Estructures de dades. Especificació, disseny i implementació (4a. ed.). Barcelona: Edicions UPC. Disponible en línia a: www.edicionsupc.es. R. Peña Marí (2000). Diseño de programas. Formalismo y abstracción (2a. ed.). Prentice Hall.


© FUOC • P06/05001/00575 • Mòdul 1

14

Tipus abstractes de dades

@post El valor del nombre natural representat és X – 1 void pred(); @pre El valor del nombre natural representat és X; y = Y @post El valor del nombre natural representat és X + Y void sumarQuantitat(Nat y); }

La sintaxi de l’exemple és la que farem servir en aquesta assignatura per a especificar el TAD, i és semblant a la que utilitzarem més endavant per a representar els TAD en Java. D’una banda, hem aproximat la sintaxi en què habitualment es presenten els TAD en el món de la l’orientació a objectes (OO) i, més concretament, en el món Java, que serà el llenguatge que hem triat per a aquesta assignatura. D’altra banda, hem fet servir llenguatge natural per a especificar la precondició i la postcondició de les operacions del TAD. Cal remarcar diversos punts: a) De la mateixa manera que passa amb les classes en Java, les operacions creadores o constructores tenen el mateix nom del TAD. En l’exemple anterior, disposem d’una operació de creació, Nat(), que crea un objecte de tipus natural amb valor 0. b) Cada operació del TAD va acompanyada per dues etiquetes o tags (@pre i @post) semblants als que fa servir el Javadoc i a les anotacions del Java 5. Mitjançant aquestes etiquetes dotem cada mètode d’especificació. c) La precondició i la postcondició són predicats. Tant en aquest exemple com en la resta de mòduls, es fa servir el llenguatge natural per a expressar-les. Hem optat per prescindir d’un llenguatge formal en l’especificació dels TAD amb l’objectiu d’evitar tota la maquinària matemàtica que seria necessària si haguéssim de treballar amb l’especificació algebraica de TAD. Com ja hem comentat, no és l’objectiu d’aquest curs aprofundir en les implicacions matemàtiques dels TAD ni treballar-hi. El nostre objectiu és traslladar al llenguatge de programació que fem servir (Java) algunes propietats dels TAD que ens seran molt útils: la dualitat entre especificació i implementació, i l’ocultació de la implementació; i, per tant, poder utilitzar un TAD únicament fent referència a la seva especificació.

1.3. Tipus abstractes de dades en Java En aquest subapartat tractarem amb una mica de detall dos aspectes molt útils en relació amb els tipus abstractes de dades en programació Java: les bibliote-

Sobre la sintaxi dels TAD La sintaxi habitual dels TAD presenta força diferències respecte de la que utilitzem aquí, més enfocada cap a la familiarització amb una sintaxi molt semblant a la que farem servir a la resta de l’assignatura quan barrejarem els TAD amb el món Java. Podeu trobar la sintaxi original i un aprofundiment en les nocions matemàtiques dels TAD en les obres de X. Franch (1999) i R. Peña Marí (2000).


© FUOC • P06/05001/00575 • Mòdul 1

15

Tipus abstractes de dades

ques de classes de Java, i la dualitat entre especificació i implementació que presenta el llenguatge Java.

1.3.1. Biblioteques en Java La majoria de llenguatges de programació actuals ens permeten encapsular conjunts d’algorismes i estructures reutilitzables en el que s’acostuma a anomenar biblioteques. En llenguatges OO parlàrem de biblioteques de classes. No cal dir que Java no n’és una excepció. D’una banda, ens permet utilitzar bibli-

OO es acrònim d’orientació a objectes.

oteques des dels nostres programes. De l’altra, el JDK incorpora eines que ens permeten construir-ne de noves. El llenguatge mateix incorpora ja una biblioteca amb un extens conjunt de classes definides. Per exemple, hi podem trobar des de classes que ens permeten treballar amb hores i calendaris (java.util.Date, java.util.GregorianCalendar), fins a classes que ens possibiliten interaccionar amb fitxers (java.io.FileInputStream, java.io.File i d’altres), passant per classes que serveixen per a emmagatzemar col·leccions d’objectes (totes les que implementen la interfície java.util.Collection, com la probablement coneguda java.util.Vector).

Així, per exemple, utilitzant la biblioteca de classes que ens proporciona el llenguatge Java, podem fer fàcilment un programa que ens mostri per la sortida estàndard l’hora i el dia en curs:

HoraActual.java package uoc.ei.exemples.modul1; import java.util.Date; public class HoraActual { public static void main(String[] args) { Date ara = new Date(); System.out.println(ara); } }

L’exemple anterior defineix una classe anomenada HoraActual en el paquet uoc.ei.exemples.modul1. En els exemples de codi que apareixen en aquest text, es fa ús extensiu dels paquets Java. Tots els exemples estan localitzats en subpaquets del paquet uoc.ei.exemples, i tots ells els trobareu també en format electrònic a l’aula. El codi de l’exemple es redueix a dues línies: en la primera es crea un objecte de la classe java.util.Date per a obtenir l’hora en curs (aquesta classe està continguda a la biblioteca proporcionada pel mateix Java); en la segona s’escriu la

Paquets de programari És força recomanable l’ús de paquets packages, que ens permetin ordenar les classes que desenvolupem d’una manera coherent, sobretot quan el nombre de classes amb què treballem comença a ser elevat.


© FUOC • P06/05001/00575 • Mòdul 1

16

Tipus abstractes de dades

representació en text d’aquest objecte per la sortida estàndard (es crida de manera implícita el mètode ara.toString(), definit a la classe java.util.Date. A part de la biblioteca integrada en el mateix entorn Java, és possible fer servir altres biblioteques, o construir-ne de noves. Totes les biblioteques en Java estan representades per fitxers amb extensió .jar. Aquesta mena de fitxers ens permeten agrupar conjunts de classes en un únic fitxer de la mateixa forma que un fitxer .zip ens permet agrupar altres fitxers. La variable CLASSPATH Per a poder fer servir una biblioteca (que no sigui la pròpia de Java) heu d’incloure el fitxer .jar corresponent a la variable d’entorn del sistema operatiu anomenada CLASSPATH. Així doncs, si la variable CLASSPATH és igual a .;c:\ei\tad.jar voldrà dir que quan compilem un programa en Java (i també quan l’executem), tenim accés primer a les classes que estan situades en el subarbre de directoris que penja del directori actual (la part corresponent al ‘.’); i, en segon lloc, a totes les classes que conté el fitxer c:\ei\tad.jar.

Assignació del valor de CLASSPATH Per a donar un valor a la variable CLASSPATH, s’ha de fer des del sistema operatiu. Cada versió de sistema operatiu té la seva pròpia manera de fer-ho.

1.3.2. Dualitat entre especificació/implementació en Java

Per a poder representar adequadament un TAD en un llenguatge de programació, cal que el llenguatge en permeti l’especificació i la implementació de manera separada. En Java utilitzarem una interfície (construcció interface) per a representar l’especificació d’un TAD, i classes per a representar les seves implementacions.

Especificació Com sabeu, la construcció interface ens permet especificar un conjunt de mètodes i descriure per a cada un d’ells la signatura del mètode. El llenguatge Java no disposa, però, de cap mecanisme per a dotar els mètodes d’una interfície de precondició o postcondició. El concepte d’especificació d’un TAD va, doncs, bastant més enllà que el que Java ens ofereix per representar-la. Com que un dels objectius principals de l’especificació és documentar el comportament dels mètodes corresponents, habitualment veurem que l’especificació d’un mètode en forma de precondició i postcondició s’ha substituït per una documentació detallada en format Javadoc del comportament del mètode. Podem comprovar això en la mateixa documentació de l’API de Java. Tanmateix, tal com veurem, en aquesta assignatura seguirem utilitzant precondicions i postcondicions. Això, però, no sempre serà possible per a les interfícies. En aquests casos, optarem per proporcionar una bona documentació en llenguatge natural que informi d’una manera clara de què fa cada un dels mètodes.

Recordeu, de Programació orientada a objectes, que el terme Javadoc fa referència al format estàndard per a comentar classes Java.


© FUOC • P06/05001/00575 • Mòdul 1

17

Tipus abstractes de dades

Vegem a continuació com traduir l’especificació del TAD Nat a Java en forma d’interfície. Com podeu comprovar, la traducció és ben directa. Noteu que hi hem afegit una operació addicional anomenada consultar() que ens permet accedir al nombre natural representat (aquesta operació no té gaire sentit en el TAD Nat original, ja que el valor representat és de fet la mateixa instància del TAD). Nat.java package uoc.ei.exemples.modul1; public interface Nat { /** Aquest mètode incrementa el valor del natural. */ void succ(); /** Aquest mètode decrementa el valor del natural. */ void pred(); /** Aquest mètode suma un natural a l'objecte. * @param y El natural a sumar. */ void sumarQuantitat(Nat y); /** Mètode consultor que retorna el valor del * natural com un enter. * @return el valor de l'objecte. */ int consultar(); }

La interfície anterior també es diferencia de l’especificació del TAD Nat pel fet que no proporciona cap operació de creació. En traduir els TAD a Java, les operacions de creació únicament estaran associades amb la implementació. Això és així perquè, en Java, en el moment de crear una instància d’un TAD, hem d’especificar a partir de quina de les seves implementacions la volem crear. Per tant, no té gaire utilitat associar les operacions creadores amb la interfície si després hem de fer referència a la implementació triada cada cop que les fem servir.

Així doncs, associarem les operacions de creació d’un TAD, exclusivament, amb les implementacions.

Implementació

Una vegada definida la interfície del TAD, podem proporcionar-li una implementació definint una classe que proporcioni la definició de cada un dels mètodes definits a la interfície. A continuació teniu una implementació del TAD Nat.

En la definició de la interfície Nat, s’ha omès expressament qualsevol referència a l’especificació del TAD, que es tractarà en l’apartat 2 d’aquest mòdul.


© FUOC • P06/05001/00575 • Mòdul 1

18

NatiImpl.java package uoc.ei.exemples.modul1; public class NatiImpl implements Nat { /** Aquesta classe representa el natural * mitjançant un atribut enter. */ private int nat; /** Crea una instància de Nat amb valor 0. */ public NatiImpl() { nat = 0; } public void succ() { nat++; } public void pred() { nat--; } public void sumarQuantitat(Nat y) { nat+= y.consultar(); } public int consultar() { return nat; } /** Retorna una representació del natural * en forma de text. */ public String toString() { return Integer.toString(consultar()); } }

TAD Nat en Java Com podeu comprovar, la implementació de Nat és totalment directa. Això és així perquè el tipus int de Java ja disposa d’operacions equivalents a les que estem intentant definir en el TAD Nat. Per tant, únicament es tractarà de fer servir les operacions homòlogues del tipus int. De fet, això ens indica que la definició del TAD Nat en Java no ens resultarà en realitat gaire útil. L’especificació i la implementació aquí del TAD Nat està únicament justificada per motius didàctics i té l’objectiu d’introduir, mitjançant un exemple senzill, tots els conceptes que farem servir per a treballar amb TAD a partir d’ara.

Les implementacions d’un TAD sempre contindran un conjunt d’atributs privats o protegits amb els quals haurem de poder representar els diferents valors que pugui prendre una instància del TAD. En aquest cas, representem un nombre natural mitjançant un atribut enter (Nat).

En la capçalera de la classe, ha de constar que la classe implementa la interfície corresponent a l’especificació del TAD. I igual com quan una classe implementa qualsevol interfície, s’ha de proporcionar una implementació per a cada un dels mètodes que hi apareixen. Cada una d’aquestes implementacions accedirà i modificarà adequadament el conjunt d’atributs que es corresponen a la representació (en el cas de la classe NatiImpl, l’atribut Nat).

La implementació proporcionada per la classe NatiImpl és probablement la més senzilla que es pot fer del TAD Nat. Però no és l’única. Per exemple, és pos-

Tipus abstractes de dades


© FUOC • P06/05001/00575 • Mòdul 1

19

sible representar un natural de manera alternativa mitjançant una taula de booleans. En aquesta representació, un natural N estaria representat per una taula de booleans en què les primeres N posicions de la taula (de 0 a N – 1) tenen el valor ‘cert’ (true), i la posició N-èsima té valor ‘fals’ (false).

Únicament amb fins didàctics disposeu a continuació d’una implementació de Nat que fa servir aquesta representació.

NatbImpl.java package uoc.ei.exemples.modul1; public class NatbImpl implements Nat { /** Aquesta classe representa el natural * mitjançant una taula de booleans. La representació * d'un natural N es correspon amb una taula * on les primeres posicions de la taula tenen * el valor true i la posició N + 1 té * el valor false. */ private boolean[] nat; /** Crea una instància de natural amb valor 0. */ public NatbImpl() { nat = new boolean[10]; } public void succ() { int i = cercarDarreraPosicio(); marcarPosicio(i + 1,true); } public void pred() { int i = cercarDarreraPosicio(); marcarPosicio(i,false); } public void sumarQuantitat(Nat y) { int i = cercarDarreraPosicio(); int j = y.consultar(); while (j-->0) marcarPosicio(++i,true); } public int consultar() { return cercarDarreraPosicio() + 1; }

Tipus abstractes de dades


© FUOC • P06/05001/00575 • Mòdul 1

20

Tipus abstractes de dades

/** Cerca la darrera posició amb valor true * del vector i la retorna */ private int cercarDarreraPosicio() { int i = 0; while (i<nat.length && nat[i]) i++; return i-1; } /** Marca una posició del vector de booleans * amb valor. En cas que la posició ultrapassi * el final del vector (i>=nat.length), *

duplica la mida del vector nat. */

private void marcarPosicio(int i,boolean valor) { if (i>=nat.length) duplicarVector(); nat[i] = valor; } /** Duplica la mida del vector nat. */ private void duplicarVector() { boolean[] nou = new boolean[nat.length*2]; for(int i = 0;i<nat.length;i++) nou[i] = nat[i]; nat = nou; } }

La representació dels naturals que usàvem a NatiImpl (un enter) és força més propera al model original dels naturals que la representació feta servir aquí (un vector de booleans). Per aquest motiu, en aquesta implementació alternativa, la implementació dels mètodes que ofereix el TAD resulta bastant més complexa, i ens cal definir mètodes privats addicionals que s’encarreguin de manejar el vector de booleans.

Ara ja disposem d’un TAD –Nat– i de dues implementacions diferents –NatiImpl i NatbImpl. És important que en els algorismes que desenvolupem en què fem ús del TAD Nat, fem referència a la implementació el mínim nombre de vegades. En principi, això hauria de ser únicament en el moment de crear instàncies del TAD. En la resta de l’algorisme, farem referència únicament a la interfície. Aquesta és una bona pràctica de programació que ens permetrà obtenir un codi més clar i entenedor; i a més incrementarà la mantenibilitat del codi i facilitarà possibles canvis d’implementació en el futur si és necessari.

La utilitat del disseny descendent Manejar directament el vector de booleans dins els mètodes públics del TAD seria una tasca massa complexa. Per això, hem de tenir sempre ben present la tècnica de disseny descendent que vau aprendre a Fonaments de programació.


21

© FUOC • P06/05001/00575 • Mòdul 1

Tipus abstractes de dades

No cal fer una anàlisi exhaustiva per a adonar-se que la implementació proporcionada per NatiImpl és clarament millor en tots els aspectes: • El codi és més compacte i clar. Per tant, el manteniment de la implementació molt més fàcil. • La implementació de les operacions proporcionada a NatiImpl és més eficient que la proporcionada per NatbImpl. Noteu que mentre que les implementacions proporcionades a NatiImpl es limiten a fer servir els operadors del tipus int de Java, les operacions de NatbImpl contenen totes una crida al mètode privat cercarDarreraPosicio, en què es fa una cerca en el vector de booleans. • L’espai ocupat per una instància de NatiImpl (un atribut de tipus int) és més petit que l’espai ocupat per una instància de NatbImpl (un vector de booleans). Tots aquests factors són força importants a l’hora d’escollir una implementació o una altra. Si ens posem només en el paper d’usuari d’un TAD, ens interessaran sobretot els dos darrers punts: l’eficiència temporal i l’eficiència espacial. En aquest cas, la implementació proporcionada per NatiImpl és clarament millor en tots els aspectes. En altres casos, trobarem situacions en què unes operacions del TAD són més eficients en una implementació, mentre que unes altres ho són en una altra. Llavors, per a triar entre l’una i l’altra, haurem d’avaluar l’ús que fem del TAD en l’aplicació desenvolupada, i a partir d’això, determinar quins mètodes interessarà que s’executin de manera més eficient. Com a exemple d’aplicació usuària del TAD Nat mostrem a continuació una aplicació que calcula els N primers nombres de Fibonacci utilitzant el TAD Nat. Noteu que podeu intercanviar les dues implementacions de Nat modificant una sola línia. El resultat serà el mateix en tots els casos. Fibonacci.java package uoc.ei.exemples.modul1; public class Fibonacci { protected static Nat crearNat() { return new NatiImpl();

// o bé NatbImpl()

} public static void mostraNombres(int n) { Nat fibn = crearNat();

// f[0] = 0

Nat fibn2 = crearNat(); // f[1] = 0 Nat fibn3 = crearNat(); fibn3.succ();

// f[2] = 1

Cost temporal del mètode En el mòdul “Complexitat algorísmica”, aprendrem a calcular de forma rigorosa el que triga un mètode a executar-se. Ho farem únicament a partir de les instruccions que apareixen a la implementació del mètode. Del resultat, n’anomenarem cost temporal del mètode, i ens permetrà triar d’una manera objectiva, entre diversos algorismes o mètodes, aquell que sigui més eficient.


22

© FUOC • P06/05001/00575 • Mòdul 1

int i = 0; while (i<n) { fibn = fibn2; fibn2 = fibn3; fibn3 = crearNat(); fibn3.sumarQuantitat(fibn); fibn3.sumarQuantitat(fibn2); i++; } System.out.println("El nombre és: "+fibn); } public static void main(String[] args) { if (args.length! = 1) System.out.println("Has de proporcionar un sol paràmetre!"); else { try { int n = Integer.parseInt(args[0]); mostraNombres(n); } catch (NumberFormatException e) { System.out.println("El paràmetre ha de ser un enter!"); } } } }

Tipus abstractes de dades


© FUOC • P06/05001/00575 • Mòdul 1

23

Tipus abstractes de dades

2. Disseny per contracte

Al final de l’apartat anterior, hem deixat de banda tot el referent a l’especificació de manera expressa. Què passa si s’executa un dels mètodes oferts per la interfície del TAD Nat en una situació que no està prevista en el TAD? Aquesta situació podria esdevenir-se per diversos motius. Per exemple, l’usuari de l’aplicació final (que és usuària del nostre TAD) pot introduir-hi una dada errònia. O bé, una situació també comuna, l’aplicació final té algun defecte que fa que es cridi algun mètode d’un TAD en una situació anormal. El programa següent genera un nombre natural –que inicialment tindrà el valor 0– i executa el mètode pred(). El predecessor de 0 no existeix, i cap de

Observeu que la precondició de pred (vegeu el subapartat 1.2) no es compleix si el natural és 0.

les implementacions que hem vist està preparada per a tractar aquesta situació. ProvaErrorNat.java package uoc.ei.exemples.modul1.defensiu; import uoc.ei.exemples.modul1.*; public class ProvaErrorNat { public static void main(String[] args) { Nat n = new NatiImpl(); // o bé NatbImpl n.pred(); System.out.println("El valor del natural és: "+n); } }

Segons l’especificació que hem donat del TAD Nat tal com apareix a l’apartat 1.2, el comportament de l’operació pred del TAD Nat en el cas que el natural sigui 0 no està definit. És a dir, l’operació no està preparada per a rebre un 0 com a paràmetre i el resultat retornat no està definit, ja que la postcondició no s’ha de complir necessàriament en aquesta situació. Si provem el programa anterior amb NatiImpl, veurem com la sortida estàndard mostra el resultat –1, que no és un nombre natural. Si, en canvi, usem NatbImpl, l’execució s’aturarà i es generarà una ArrayIndexOutOfBoundsException, perquè l’operació pred estarà intentant accedir a la posició –1 del vector de booleans. En aquest cas, la precondició de l’operació pred del TAD Nat ens informa que ProvaErrorNat és responsable de garantir que n no és el natural 0 quan es fa la crida a pred. Però la precondició és un predicat que, de moment, no comprovem enlloc en fer la crida. Té sentit, per tant, mantenir la precondició com està? No farà això que el TAD sigui poc robust?

Precondició d’un algorisme Recordeu que, tal com vau estudiar a Fonaments de programació, en un algorisme (en el nostre cas el corresponent a una operació d’un TAD), la seva precondició determinarà si una crida és correcta o no. L’algorisme únicament és responsable de tractar aquelles situacions en què la precondició es compleix.


© FUOC • P06/05001/00575 • Mòdul 1

24

Tipus abstractes de dades

Per a arribar al fons del problema, deixem de banda momentàniament la precondició actual de l’operació pred i analitzem la situació des d’un punt de vista d’assignació de responsabilitats, és a dir: quines coses s’han de garantir des de cada una de les parts, i qui ha de gestionar els errors. Posteriorment, veurem com tot això està completament lligat amb l’especificació del TAD.

Pel que fa a l’assignació de responsabilitats, s’entreveuen dues possibilitats:

1) Assignar la responsabilitat de comprovar les situacions anòmales al mateix TAD. Això vol dir que el codi del mètode del TAD serà responsable de comprovar si el valor actual del TAD i els paràmetres rebuts pel mètode són els adequats per a executar el mètode.

2) Assignar la responsabilitat als usuaris del TAD de garantir que sempre que cridin un mètode del TAD, ho faran en les condicions adequades perquè es pugui executar.

Triar entre una o altra opció és una decisió que haurem de prendre sempre que dissenyem un nou TAD, o, de fet, qualsevol classe.

2.1. Programació defensiva

Assignar la responsabilitat al TAD comporta fer servir el que comunament s’anomena programació defensiva. En aquest cas, haurem d’incorporar als nostres TAD línies de codi dedicades exclusivament a comprovar condicions d’error i a tractar-les adequadament. El següent fragment de codi mostra com podem modificar la implementació del mètode pred() de NatiImpl afegint-hi la comprovació necessària.

Disposeu del conjunt complet de classes Java per a aquest exemple en format electrònic (paquet uoc.ei.exemples. modul1.defensiu).

NatiDefensiuImpl.java package uoc.ei.exemples.modul1.defensiu; ... public void pred() throws ExcepcioNat { if (nat==0) throw new ExcepcioNat(“No existeix el predecessor de 0”); nat--; } ...

En aquest cas la comprovació és senzilla. Però per a altres situacions, el codi corresponent a la comprovació és, de vegades, bastant més complex. Addicio-


© FUOC • P06/05001/00575 • Mòdul 1

25

Tipus abstractes de dades

nalment, en el mateix TAD, normalment no sabrem com hem de tractar la situació anòmala. En general, el tractament de l’error es realitzarà en l’aplicació usuària del TAD. Per aquest motiu, la programació defensiva s’ha de limitar,

Recordeu el mecanisme de llançament i captura d’excepcions del llenguatge Java presentat en l’assignatura Programació orientada a objectes.

també en general, a comprovar la condició d’error i en cas de detectar una situació anòmala, llançar una excepció que posteriorment algú altre tractarà. En aquest cas, hem introduït l’ús d’una excepció nova: ExcepcioNat. Si fem responsables els usuaris del TAD de garantir que els paràmetres rebuts són correctes, ens estalviarem la programació defensiva. En canvi, aquesta opció és menys robusta enfront de possibles usos incorrectes. Per aquest motiu, l’ús de la programació defensiva podria semblar en un primer moment més adequada. Però això no és exactament així: l’ús de la programació defensiva té avantatges i desavantatges importants. Si bé és cert que pot fer el codi més ro-

Responsabilitat i verificació Fer responsables els usuaris d’un TAD de garantir-ne l’ús adequat no significa que aquests n’hagin de fer les comprovacions defensives.

bust, la seva presència fa també el codi més complex i difícil de llegir, en definitiva, menys mantenible. Per a aplicacions i TAD de mida petita, podem assumir sense problemes aquesta complexitat; però per a aplicacions de mida considerable és molt més desitjable no arrossegar aquest cost. Això no treu, però, que en algun cas concret ens resulti més interessant que el TAD mateix s’encarregui de les comprovacions pertinents. Les dues opcions també es poden combinar. Això pot passar quan en un mateix mètode es donen diverses situacions d’error. Per a algunes, ens pot interessar que el TAD mateix tingui la responsabilitat de comprovar-les, mentre que per a altres ens pot interessar que sigui el client qui garanteixi al TAD que la situació és la correcta per a realitzar l’operació. En qualsevol cas, sempre que decidim utilitzar la programació defensiva és desitjable que el codi corresponent a l’algorisme que volem “protegir” quedi com més aïllat possible del codi que el “protegeix”.

2.2. Contractes Amb l’objectiu d’assignar d’una manera clara les responsabilitats de cada una de les classes/TAD que intervenen en una aplicació, farem servir el que s’anomena

DBC és la sigla de l’expressió anglesa design by contract.

disseny per contracte, sovint conegut per la seva sigla anglesa DBC. El disseny per contracte (a partir d’ara, DBC) introdueix un nou element en el disseny d’aplicacions OO: el contracte entre una classe i els seus usuaris.

El contracte entre una classe i els seus usuaris no és res més que l’especificació d’aquesta classe, que representa l’acord formal entre la classe i els seus usuaris, en què queden definits els drets i les obligacions de cada part.

Parlarem de contracte tant en l’àmbit de classe/interfície com en l’àmbit de TAD.


© FUOC • P06/05001/00575 • Mòdul 1

26

Tipus abstractes de dades

Així doncs, la precondició d’un mètode d’una classe (o TAD): 1) “Compromet” l’usuari de la classe a garantir que la precondició es compleixi quan executi el mètode. 2) Com a conseqüència, “garanteix” que quan el mètode en qüestió sigui cridat, la precondició de la classe serà certa. Per tant, el desenvolupador d’un TAD o una classe pot assumir que, sempre que un mètode sigui executat, la seva precondició serà certa. D’altra banda, la postcondició d’un mètode d’una classe: 1) “Compromet” la classe a què, un cop executat el mètode, es complirà la postcondició (i sempre que al principi de l’execució es compleixi la precondició, cosa que la classe assumeix). 2) Com a conseqüència, “garanteix” als usuaris de la classe que, un cop executat el mètode, es complirà la postcondició. De la mateixa manera que els contractes legals que podem signar (per exemple, per a comprar un pis o obtenir un crèdit), el contracte que ens ocupa vincula dues parts: una classe, d’una banda, i els seus usuaris, de l’altra. I també, com els contractes legals, defineix una sèrie de compromisos i garanties per a cada una de les parts. Evidentment, els compromisos d’una part són les garanties de l’altra.

En definitiva, el disseny per contracte (DBC) no és res més que englobar sota el mantell conceptual de contracte els diferents elements que apareixen en l’especificació. La noció de contracte ens ajuda, mitjançant l’especificació, a establir una relació clara entre una classe i els seus usuaris.

L’ús de contractes en el desenvolupament de programari orientat a objectes (OO) permet definir d’una manera clara les responsabilitats en cada una de les interaccions entre les classes que conformen l’aplicació OO. Això fa que el programari

Abreugem les expressions orientació a objectes o orientat a objectes amb la sigla OO.

desenvolupat a partir d’aquesta tècnica sigui en general més fiable, ja que proporciona una eina que ens permet descompondre la responsabilitat global de fer funcionar l’aplicació entre el conjunt de classes (i TAD) que participen en l’aplicació, i en separa de manera clara les responsabilitats de cadascuna.

2.2.1. Incompliment de contracte Què passa si l’usuari d’una classe no compleix la seva part de contracte? Doncs, simplement, que el mètode no està obligat a complir la seva. Ni tan

La precondició d’un mètode Comprovar la precondició d’un mètode dins la implementació d’aquest no és una bona pràctica en situacions normals, ja que un dels objectius principals en establir contractes és eliminar el codi defensiu corresponent a les situacions errònies capturades per la precondició.


© FUOC • P06/05001/00575 • Mòdul 1

27

Tipus abstractes de dades

sols està obligat a informar de la situació anòmala. En aquesta situació, és lliure de fer “el que li doni la gana”. Per a aquestes situacions, direm que el comportament del mètode no està definit. El DBC és compatible amb la programació defensiva, ja que no hi ha cap problema per a incloure en el contracte les comprovacions que se’n deriven. És a dir, el DBC ens ofereix la possibilitat de definir contractes més o menys defensius, segons ho creguem convenient en cada situació. L’ús de contractes poc (o gens) defensius serà el més habitual, i ens permetrà centrar-nos exclusivament en el desenvolupament de l’algorisme o TAD. Normalment, això serà preferible i més beneficiós que “sobreprotegir” els nostres mètodes amb programació defensiva que faci més complex el codi. Això no treu, com ja hem comentat abans, que vulguem fer servir la programació defensiva per a situacions concretes. Aquesta estratègia té, però, un inconvenient important. La programació defensiva permet detectar situacions d’error de manera fiable. En canvi, l’ús de contractes poc defensius farà que aquestes situacions d’error no es detectin fins que es produeixi un error detectat directament pel llenguatge de programació o el sistema operatiu. Així, per exemple, si mai heu fet desenvolupament en llenguatge Java, de ben segur que us haurà aparegut alguna vegada un NullPointerException, o un ArrayIndexOutOfBoundsException. Aquest error difícilment ens dirà alguna cosa sobre la situació conceptual d’error que s’ha produït. En canvi, l’ús de programació defensiva hauria detectat aquesta situació amb anterioritat, reportant que, per exemple, un contenidor no té un element determinat; o un enter hauria d’estar dins un cert rang. Aquesta informació, sens dubte, serà més útil al desenvolupador per a trobar l’error de programació més ràpidament. Hi ha solucions que permeten mantenir tant la fiabilitat proporcionada per la programació defensiva, com la major mantenibilitat de l’ús de contractes que responsabilitzin a la part client d’un ús correcte d’un TAD o classe. Aquestes solucions consisteixen a disposar d’algun sistema per a –un cop definit el contracte d’una manera rigorosa– comprovar-lo automàticament. Aquests sistemes no proliferen gaire, i són específics per a cada plataforma de desenvolupament concreta. Per al cas del Java, existeixen diversos sistemes d’aquesta mena; i per a aquesta assignatura se n’ha desenvolupat un que es comenta més endavant, i en més profunditat en els recursos electrònics de l’assignatura. Exemple En l’especificació del TAD Nat vist en el subapartat 1.2, s’estableix que la precondició de l’operació pred és que el valor del nombre natural representat sigui més gran que X > 0”; i la seva postcondició, que el valor del nombre natural representat sigui X – 1”. El programa ProvaErrorNat que hem vist al principi de l’apartat 2 crida el mètode pred amb un natural que té valor 0. Per tant, ProvaErrorNat està incomplint la seva part del contracte. Aquest incompliment fa que cap de les implementacions de Nat estigui obligada a garantir que la postcondició de pred es compleixi.

Vegeu el subapartat 2.4.1 d’aquest mòdul.


© FUOC • P06/05001/00575 • Mòdul 1

28

Tipus abstractes de dades

Per tant, el resultat –1 obtingut si s’utilitza NatiImpl com a implementació de Nat, i el llançament de l’excepció ArrayIndexOutOfBoundsException si es fa servir NatbImpl es deuen a un incompliment del contracte per part de ProvaErrorNat.

2.3. Invariant de la representació Una implementació d’un TAD farà servir un conjunt d’atributs per a representar el valor concret que pren la instància del TAD. A aquest conjunt d’atributs,

Vegeu NatbImpl en el subapartat 1.3.2 d'aquest mòdul.

l’anomenarem representació del TAD. Així, en el cas de NatbImpl, la representació consta d’un únic atribut, que és un vector de boolean. Com és lògic, per a cada un dels valors possibles del TAD, tindrem (com a mínim) un valor de la representació. En el cas de NatbImpl, tenim que els valors del TAD Nat –que són cadascun dels nombres naturals (0, 1, 2, 3, ...)– són els

Observació Per comoditat en l’explicació, farem servir el terme valor de la representació per a referirnos a una combinació de valors per a tots els atributs de la representació.

de la representació de NatbImpl que els representa. Així, per exemple, el natural 5 es pot representat de la següent manera:

Ara bé, sovint ens trobarem amb casos en què representacions diferents representen el mateix valor del TAD. Observeu, per exemple, els dos vectors següents. Tots dos són representacions diferents. En canvi, tots dos representen el natural 5.

Addicionalment, també hi ha situacions en què les representacions no equivalen a cap valor del TAD. Per exemple, fixem-nos ara en l’altra implementació del TAD Nat que hem vist: NatiImpl. En aquesta implementació, la representació està constituïda per l’atribut enter nat. La representació nat = –3 és una representació possible, ja que nat és un int i, com a tal, pot prendre valors negatius. En canvi, l’esmentada representació no es correspon amb cap valor del TAD Nat. Així doncs, en general, únicament un subconjunt de totes les representacions possibles tindran un equivalent com a valor del TAD. Lògicament, aquestes seran les úniques representacions del TAD que considerarem vàlides. L’invariant de la representació és una propietat que definim amb cada implementació d’un TAD, i que és certa per a tots els valors vàlids de la represen-

Vegeu el TAD Nat en el subapartat 1.2.3 d'aquest mòdul.


© FUOC • P06/05001/00575 • Mòdul 1

29

Tipus abstractes de dades

tació, i falsa per a aquells valors de la representació que no ho són. Aquesta propietat defineix tot allò que tenen en comú els estats vàlids de la representació. Per exemple, en el cas de NatiImpl, l’invariant de la representació es correspon amb la propietat nat ≥ 0. En canvi, en el cas de NatbImpl, totes les representacions es corresponen amb valors del TAD i, per tant, l’invariant de la representació és ‘cert’. Depenent de la implementació i del TAD, l’invariant de la representació pot arribar a ser realment complex. L’invariant de la representació és una noció especialment útil per a proporcionar l’especificació (o contracte) d’un TAD per a una implementació concreta. Per a cada una de les operacions del TAD, l’invariant de la representació s’ha de

En aquests materials, no treballarem més aquest tema. Sí que us el trobareu, però de manera implícita, en les especificacions de les implementacions de la biblioteca de col·leccions de l’assignatura.

complir tant al principi com al final de l’execució de l’operació. És a dir, les operacions sempre parteixen d’una representació vàlida (representen un valor del TAD), que converteixen en una altra representació vàlida. Per tant, un cop definit l’invariant de la representació, aquest formarà part de la precondició i de la postcondició de totes les operacions.

2.4. Contractes en Java La majoria de cursos i llibres de text existents en l’àrea dels TAD i les estructures de dades no estableixen una vinculació directa entre el marc conceptual que acabem de definir i la part més pràctica, corresponent al desenvolupament i ús d’implementacions de TAD. És a dir, no acostuma a haver-hi cap vincle entre l’especificació formal del comportament d’un TAD i la seva implementació. En aquesta assignatura, adoptem un punt de vista lleugerament diferent. Havent presentat el marc conceptual sense fer servir cap formalisme matemàtic, no ens interessa treballar l’especificació formal dels TAD; sí, en canvi, pretenem establir una vinculació directa entre la implementació i l’especificació d’un TAD en el llenguatge de programació. Així doncs, totes les implementacions de TAD que introduïm en aquesta assignatura aniran acompanyades del seu contracte. Definirem el contracte d’una implementació de la manera següent: • Expressarem la precondició i la postcondició de cada un dels mètodes de la classe, i l’invariant de la seva representació mitjançant expressions booleanes, fent servir una extensió de la mateixa sintaxi proporcionada pel llen-

Precondicions i postcondicions en Java Encara que en Java representem les precondicions i postcondicions com a expressions booleanes, heu de tenir present que no formen part de l’algorisme i que no hi tenen cap efecte.

guatge Java (més endavant veurem en què consisteix aquesta extensió). • Definirem la precondició i la postcondició d’un mètode en la seva documentació Javadoc mitjançant unes etiquetes especials @pre i @post.

Aquesta manera de definir el contracte no és estàndard de Java. És una extensió especialment definida per a aquesta assignatura.


© FUOC • P06/05001/00575 • Mòdul 1

30

• De la mateixa manera, l’invariant de la representació estarà inclòs al Javadoc de la classe mitjançant una etiqueta especial anomenada @inv. Observeu que en els paràgrafs anteriors hem parlat de contractes per a les implementacions dels TAD, i no per als TAD en si mateixos, representats en Java mitjançant interfícies. Sempre que puguem, intentarem proporcionar el contracte de manera genèrica per a la interfície que representa al TAD. Això, però, no sempre serà possible, i a vegades estarem limitats a proporcionar únicament contractes per a les implementacions. El motiu pel qual no sempre podrem proporcionar un contracte per a les interfícies és que en Java no podem proporcionar una traducció directa de l’especificació algebraica. Llavors, molts cops serà necessari accedir a l’estat (contingut) de la instància del TAD per a construir la precondició o la postcondició. No sempre serà possible accedir a aquest estat des de la mateixa interfície; dependrà de si aquesta ofereix les operacions adequades de consulta (cosa que no sempre serà així). En canvi, fer-ho des de la implementació concreta mateix d’un TAD sempre serà possible, ja que els atributs que defineixen aquest estat estan definits en la mateixa implementació (i clarament hi tenim accés). Vegem com podríem proporcionar un contracte en el cas del TAD Nat. En aquest cas, sí que podem proporcionar el contracte pel que fa a la interfície. Així doncs, veiem com queda la interfície Nat: Nat.java package uoc.ei.exemples.introduccio; public interface Nat { /** Aquest mètode incrementa el valor del natural. * @pre true * @post consultar() == $old(consultar())+1 */ void succ(); /** Aquest mètode decrementa el valor del natural. * @pre consultar()>0 * @post consultar() == $old(consultar())-1 */ void pred(); /** Aquest mètode suma un natural a l'objecte. * @pre true * @post consultar() == $old(consultar())+y.consultar() * @param y El natural a sumar. */ void sumarQuantitat(Nat y);

Tipus abstractes de dades


31

© FUOC • P06/05001/00575 • Mòdul 1

Tipus abstractes de dades

/** Mètode consultor que retorna el valor del natural com un enter. * @post $return == consultar() && consultar() == $old(consultar()) * @return el valor de l'objecte. */ int consultar(); }

Com podem veure, per a cada mètode de la interfície Nat definim la precondició i postcondició mitjançant les etiquetes @pre i @post. Els valors associats a aquestes etiquetes són expressions booleanes de Java amb algun element addicional. Vegem quins són aquests elements addicionals: • $old(expressió). Permet fer referència al valor de l’expressió just abans d’executar el mètode. L’expressió es pot correspondre amb tota la representació del TAD (en aquest cas, trobaríem $old(this)), amb un atribut del TAD

El mètode consultar Observeu que per al cas de la interfície Nat és possible proporcionar un contracte per a la interfície, ja que disposem del mètode consultar, que ens permet consultar l’”estat” del natural (de fet el seu valor). Si el TAD no disposés d’aquest mètode només podríem definir els contractes a cadascuna de les seves implementacions.

(només quan es tracta d’una implementació, en l’exemple anterior aquest ús no és possible perquè una interfície no té atributs) o, fins i tot, amb qualsevol expressió avaluable abans d’executar el mètode (per exemple, $old(consultar()). • $return. Permet fer referència al valor de retorn del mètode. • $all(i:taula,expr-bool). És una expressió booleana amb tres paràmetres: – i: qualsevol nom, es farà servir dins expr-bool. – taula: fa referència a un atribut de tipus taula; és a dir, definit en el TAD com a tipus[]. – expr-bool: és una expressió booleana que es verificarà per a cada element de taula. Dins expr-bool podem fer servir tant taula com i per a fer referència a cadascun dels elements. El resultat de l’expressió $all serà ‘cert’ si expr-bool es compleix per a tots els

Els elements $all i $exists... ... són la traducció directa dels quantificadors universal i existencial de la lògica de primer ordre. Són útils per a expressar condicions sobre conjunts d’elements. Per això, els necessitarem per a expressar els contractes. En trobareu exemples d’ús en els recursos electrònics de l’assignatura.

elements de taula i ‘fals’ en cas contrari. • $exists(i:taula,expr-bool). És una expressió booleana amb els tres mateixos paràmetres que $all. El resultat de l’expressió $exists serà ‘cert’ si expr-bool es compleix com a mínim per a un dels elements de la taula, i ‘fals’ en cas contrari.

La precondició i la postcondició

Repassem algunes de les precondicions i postcondicions per a entendre bé com funciona la definició de contracte. Fixem-nos, per exemple, en el mètode pred. La precondició és consultar() > 0. Això vol dir que la precondició es complirà quan la crida al mètode consultar retorni un valor més gran que 0. És a dir, si un algorisme crida el mètode pred per a un Nat que té valor 0, estarà incomplint la seva part del contracte.

a

Normalment, en la precondició, es defineixen un conjunt de condicions que cal que es compleixin per al correcte funcionament del mètode; i, en la postcondició, es descriu quin és el resultat de l’execució del mètode en funció de l’estat de la instància del TAD.


32

© FUOC • P06/05001/00575 • Mòdul 1

Quant a la postcondició de pred, ens diu quin és l’estat de la instància de Nat (el seu valor en aquest cas) segons el seu estat anterior. Ens diu que el valor del natural (denotat per consultar()) és igual al valor del natural abans d’executar el mètode menys 1. Fixeu-vos com per a fer referència al valor del natural abans d’executar el mètode es fa servir $old de la manera següent: $old(consultar()). Revisem ara la precondició i la postcondició del mètode succ. La postcondició d’aquest mètode és força semblant a la postcondició de pred, i ens diu que el nou valor del natural després d’executar el mètode succ és igual al valor antic més 1. En canvi, la seva precondició és una expressió força simple: true. Què vol dir això? Simplement, que sigui quin sigui l’estat de la instància de Nat i siguin quins siguin els paràmetres de succ (en aquest cas no tenim paràmetres), la precondició sempre es complirà. És a dir, el contracte de Nat no imposa cap condició als usuaris del TAD Nat, que sempre podran cridar el mètode succ sense necessitat de garantir res.

a

Com hem vist, la sintaxi de les precondicions i postcondicions es correspon amb expressions booleanes Java on podem fer servir alguns elements addicionals. Com en qualsevol expressió booleana Java, hi podem fer crides a mètodes. Ara bé, com l’avaluació de precondició i postcondició no forma part, de fet, de l’algorisme o programa que s’està definint, no té sentit cridar mètodes que modifiquin l’estat del TAD.

Així doncs, en les precondicions i postcondicions és permès cridar mètodes, però només aquells que únicament calculin o retornin un valor, i que en cap cas no modifiquin els objectes o atributs que hi accedeixin.

A continuació, teniu un programa que exemplifica una execució i on veureu en quin moment es comproven la precondició i la postcondició.

public void static main(String[] args) { (1) Nat n = new Natilmpl(); (2)comprovem post del constructor (3)comprovem pre de succ (4) n.succ(); (5)post de succ (6)pre de pred (7) n.pred(); (8)post de pred (9) System.out.println(n); }

Tipus abstractes de dades


© FUOC • P06/05001/00575 • Mòdul 1

33

Tipus abstractes de dades

2.4.1. Eines de disseny per contracte Ha quedat clar que –com en qualsevol altre contracte– l’incompliment del contracte per part dels usuaris d’un TAD eximeix el TAD de complir la seva

Vegeu les repercussions de l’incompliment del contracte en el subapartat 2.2.1 d’aquest mòdul.

part de contracte. Tot i això, en el procés de desenvolupament d’una aplicació, és normal que es cometin errades. Per tant, pot passar que l’aplicació usuària d’un TAD incompleixi la seva part de contracte en alguna de les crides a mètodes d’aquest TAD. Per motius obvis, ens interessarà detectar aquesta situació i localitzar l’error com més aviat millor. La programació per contracte eximeix el TAD de detectar l’incompliment de contracte per part dels usuaris. Per aquest motiu, en no detectar la situació anòmala d’entrada, es pot produir un error a partir del qual serà més difícil detectar el codi font responsable d’aquesta situació. Tal com també hem comentat, en una situació ideal voldríem tenir la capacitat de detectar els incompliments de contracte sense haver d’“embrutar” el

En els recursos electrònics de l’assignatura, trobareu documentació sobre el sistema de DBC que farem servir en aquest curs.

codi amb comprovacions de programació defensiva que no formen realment part de l’algorisme, sinó del contracte mateix. Per a aconseguir això, farem servir un sistema de DBC per a Java elaborat especialment per a aquesta assignatura. Amb aquesta eina podem: • Especificar el contracte d’una classe fent servir les etiquetes @pre, @post i @inv. És aconsellable fer-ho per a qualsevol classe que desenvolupem, sobretot si forma part d’una biblioteca que serà utilitzada per a altres mòduls o aplicacions. • A partir d’un conjunt de classes per a les quals s’ha especificat el contracte, generar una biblioteca (en format .jar) que contingui aquest conjunt de classes i que comprovi de manera automàtica els contractes definits. La biblioteca jar generada amb l’eina de DBC, la podem fer servir com qualsevol altre jar. D’aquesta manera, quan estem desenvolupant una aplicació que fa ús d’una biblioteca que té contractes definits, podem fer servir el jar generat amb l’eina de DBC. Això farà que quan provem les funcionalitats que anem desenvolupant, es comprovin els contractes definits en la biblioteca i es pugui detectar ràpidament si n’hi ha algun que no es compleix. Un cop s’hagi acabat la fase de desenvolupament, si volem, podem substituir el fitxer jar generat per l’eina de DBC per un altre jar que podem generar amb les eines estàndard proporcionades per l’entorn Java i que no comprovarà cap contracte. Heu de tenir en compte, però, que no serà necessari que feu servir aquesta eina en aquest curs; ja que, en principi, vosaltres no haureu de desenvolupar cap

Trobareu una explicació més detallada sobre el funcionament de l’eina de DBC en els recursos electrònics de l’aula.


© FUOC • P06/05001/00575 • Mòdul 1

34

biblioteca. Al llarg de tot el curs, treballareu amb la biblioteca de TAD de l’assignatura, de la qual en tindreu dues versions diferents (és a dir, dos fitxers jar): una generada amb l’eina de DBC, que farà les comprovacions dels contractes, i una altra generada amb les eines estàndard de l’entorn Java, que no comprova cap contracte.

Tipus abstractes de dades


© FUOC • P06/05001/00575 • Mòdul 1

35

Tipus abstractes de dades

3. Desenvolupament d’una col·lecció d’exemple

Vegem un primer exemple de col·lecció. L’objectiu principal d’aquest apartat no és l’estudi d’aquesta col·lecció concreta d’exemple, sinó la presentació mitjançant un exemple pràctic dels diferents elements que apareixen en el desenvolupament i ús d’una col·lecció. La col·lecció que definirem i farem servir d’exemple en aquest apartat és la dels conjunts. Un conjunt és un grup d’elements no ordenats i sense repetició. Representarem un conjunt enumerant els seus elements, separant-los per comes i posant l’esmentada enumeració entre claus. Així, per exemple, representarem un conjunt amb els nombres naturals 1, 5 i 8 com: {1, 5, 8}. Diem que els elements del conjunt no estan ordenats perquè no ens importa l’ordre en què s’hi han afegit, ni tampoc qualsevol altre ordre que puguem definir entre els elements del conjunt (per exemple, en el cas dels nombres naturals, l’ordre ‘<’ dins del mateixos nombres naturals). Amb això volem dir, per exemple, que {1, 5, 8} i {8, 1, 5} són en realitat el mateix conjunt. D’altra banda, diem que un conjunt és un grup d’elements sense repetició, ja que els elements només hi apareixen un cop. És a dir, a partir d’un element e i d’un conjunt C, només hi ha dues situacions possibles: o bé e pertany a C o bé no hi pertany.

3.1. Operacions

Tria de col·leccions Quan conegueu més tipus de col·leccions i estigueu en procés de resolució d’un problema, un pas força important d’aquest procés serà triar les col·leccions que ens permetin representar de manera adequada el domini del problema. Com que en un conjunt no s’estableix cap ordre entre els elements que el formen, la col·lecció Conjunt no ens servirà per a representar, per exemple, una cua de persones que estan esperant per a ser ateses en una gestoria. En canvi, sí que ens servirà per a representar el conjunt de gestions que es poden realitzar a la gestoria.

Un cop ja tenim conceptualment clar en què consisteixen i quines són les propietats de la col·lecció que hem agafat d’exemple, veurem quines són les operacions que ens interessarà proporcionar sobre la col·lecció Conjunt: Representació de col·leccions

col·lecció Conjunt és constructor() Crea un conjunt buit. @pre cert. @post Retorna un conjunt sense cap element ($this és el conjunt buit).

void afegir(Object elem) Afegeix un element al conjunt. Si l’element ja hi és, no fa res. @pre cert. @post elem pertany al conjunt ($this.hiEs(elem)).

Aquesta és la forma com presentarem habitualment cada una de les col·leccions que vagin apareixent al llarg dels mòduls. Per a cada operació que es pugui efectuar sobre la col·lecció tenim: la seva signatura en negreta, una breu explicació, i la precondició i postcondició, que estaran precedides de les etiquetes @pre i @post. En el cas de les col·leccions (que es correspondran a interfícies Java), la precondició i la postcondició s’expressaran habitualment de manera informal.


© FUOC • P06/05001/00575 • Mòdul 1

36

Tipus abstractes de dades

boolean hiEs(Object elem) Comprova si un objecte pertany al conjunt. @pre ‘cert’. @post El valor retornat ($return) és ‘cert’ si elem pertany al conjunt ($this). I ‘fals’ en cas contrari. Object esborrar(Object elem) Esborra un element del conjunt. Si l'element no hi és, no fa res. @pre ‘cert’. @post elem no pertany al conjunt (!$this.hiEs(elem)). El valor retornat ($return) és l’element esborrat, o ‘null’ si l’element no hi era. Les operacions prèvies corresponen a les operacions més elementals que podem fer sobre els conjunts. Tot i que les col·leccions com a tals no tenen constructor, normalment hi afegirem operacions anomenades constructor, de

Trobareu la interfície Java corresponent a aquesta col·lecció en els exemples disponibles en els recursos electrònics de l’assignatura (interfície Conjunt en el paquet uoc.ei.exemples.modul1. Conjunt).

manera que les diferents maneres de construir la col·lecció s’identifiquin fàcilment. Tingueu en compte, però, que aquestes operacions no procedeixen de cap mètode de la interfície Java corresponent, sinó directament dels mètodes constructors de les implementacions de la interfície. La versió dels conjunts que proporcionem en aquest apartat ens permet crear un conjunt buit, afegir-hi i esborrar-ne elements, i consultar si un objecte concret hi pertany. Seria interessant disposar d’operacions addicionals. Per exem-

En l’apartat “Exercicis d’autoavaluació” del mòdul, trobareu material addicional relacionat amb l’ampliació de les funcionalitats dels conjunts presentats en aquest apartat.

ple, poder unir dos conjunts, saber quants elements té, o poder-ne recórrer els elements. D’altra banda, en un altre mòdul, s’estudia una versió molt més completa dels conjunts, també amb implementacions més eficients de les que veurem a continuació.

a

En el mòdul “El TAD Taula”, d’aquesta assignatura, s’estudia una versió molt més completa dels conjunts.

3.2. Implementació mitjançant un vector Un cop vistes les operacions, i definit de manera clara el seu comportament, passem a veure com podem proporcionar una implementació per a aquesta col·lecció. Per a proporcionar una implementació, hem de definir una classe amb els elements següents: 1) Un conjunt d’atributs que ens permeti representar qualsevol conjunt. Anomenarem aquest conjunt d’atributs representació de la col·lecció o TAD. 2) Un cop tinguem definida la representació, hi hem de proporcionar implementacions per a les operacions de la col·lecció que satisfacin el contracte imposat per la col·lecció mateixa (etiquetes @pre i @post).

3.2.1. Definició de la representació Anem doncs al primer pas: triar una representació que ens permeti representar els conjunts. Normalment, triarem la representació en la qual la realització de

L’eficiència s’estudia en el mòdul “Complexitat algorísmica” d’aquesta assignatura.


© FUOC • P06/05001/00575 • Mòdul 1

37

Tipus abstractes de dades

cadascuna de les operaciones sigui la més eficient. De moment, deixarem de banda el tema de l’eficiència, i en aquest subapartat proporcionarem una representació en què dominarà la senzillesa.

Una manera senzilla de representar un conjunt d’elements és mitjançant dos atributs: un vector de tipus Object on guardem els elements i un atribut enter (int) que farem servir per a guardar el nombre d’elements del conjunt. Així, per exemple, podrem representar el conjunt {1, 5, 8, 2, 7} mitjançant els atributs especificats de la manera següent:

Mida dels vectors en Java

En les representacions en què apareguin vectors, normalment, trobarem un tros del vector que contindrà els elements que guardem (per a abreujar, l’anomenarem tros ple), i un tros del vector lliure que farem servir quan necessitem

Recordeu que en el llenguatge Java, la mida dels vectors és accessible a partir del mateix vector (en l’exemple caldria accedir a elements.length). Per tant, en el cas concret de Java, no caldrà tenir un atribut especial per a guardar la mida del vector. Altres llenguatges com C requeririen un atribut addicional.

emmagatzemar un nombre superior d’elements. En l’exemple, el tros ple consta de les cinc primeres posicions del vector, i la resta està lliure. La variable nombreElementsActual indica la mida del tros ple.

3.2.2. Contenidors afitats

Les representacions que fa servir un vector per a emmagatzemar els elements que constitueixen una col·lecció tenen una particularitat: en el moment de la creació de la col·lecció, cal establir-ne el nombre màxim d’elements. El motiu és força clar: necessitem definir la mida del vector en el moment de la creació de la col·lecció, i aquesta mida és la que determinarà, en principi, el nombre d’elements que podrem guardar com a màxim en aquesta col·lecció.

Això té diverses implicacions:

• En primer lloc, serà necessari modificar lleugerament el contracte de la col·lecció per a tenir en compte aquest màxim. • Com a conseqüència d’això, només podrem fer servir una representació que utilitzi vectors per a aquells casos en què coneguem el nombre màxim d’elements. En un altre mòdul, estudiarem alternatives a la representació amb vectors que esquiven aquestes implicacions. Aquestes representacions alternatives tenen sovint un cost addicional tant en la programació com en l’espai de memòria

En el mòdul “Contenidors seqüencials” d’aquesta assignatura, s’estudien algunes alternatives a la representació amb vectors.


© FUOC • P06/05001/00575 • Mòdul 1

38

Tipus abstractes de dades

ocupat. Per això, a vegades interessarà fer servir una representació amb vectors. Hi estudiarem també una tècnica que permet redefinir el nombre màxim d’elements d’una representació amb vectors amb posterioritat al moment de la creació. De moment, però, treballarem amb aquest màxim. A fi d’establir d’una manera clara i neta el nou contracte, que té en compte el màxim d’elements, definirem una nova col·lecció que adaptarà aquest contracte per a l’operació afegir i hi afegirà una operació nova anomenada estaPle. Farem això de manera específica per a la col·lecció Conjunt: col·lecció ConjuntAfitat esten Conjunt és void afegir(Object elem) @pre o bé ‘elem’ ja està en el conjunt o bé hi ha espai per a afegir un nou element. boolean estaPle() Comprova si el conjunt està ple.

Una sola col·lecció general Per a una biblioteca de col·leccions com la de la nostra assignatura (vegeu l’apartat 5), serà més convenient definir una col·lecció general única, que anomenarem ContenidorAfitat. Aquesta col·lecció general serà implementada per totes les implementacions que proporcionen una representació afitada.

@pre ‘cert’. @post el valor retornat ($return) és ‘cert’ si el nombre d’elements del conjunt és el màxim possible, i ‘fals’ en el cas contrari. Per convenció, marquem la signatura de l’operació afegir en cursiva per indicar que n’estem redefinint el contracte. Si redefinim el contracte, únicament especificarem els elements que canvien (en aquest cas, la precondició).

3.2.3. Implementació de les operacions Un cop aclarides les implicacions de fer servir una representació amb vectors, veurem com es du a terme la implementació de les operacions a partir de la representació triada. En primer lloc, mostrem un esquema algorísmic de cada una de les operacions: col·lecció ConjuntVectorImpl implementa ConjuntAfitat • constructor() – Crea el vector d’elements. – Assigna el nombre d’elements actual a 0. • void afegir(Object elem) – Assigna ‘elem’ a la primera posició lliure del vector d’elements. – Incrementa el nombre d’elements actual. • boolean hiEs(Object elem) – Cerca ‘elem’ en el tros del vector d’elements ple. – Retorna ‘cert’ si ‘elem’ s’ha trobat i, ‘fals’, en cas contrari.

Operació d’afegir Observeu que en l’operació d’afegir, és l’usuari qui ha de garantir que el conjunt té espai suficient per a afegir l’element. Per tant, la implementació no necessita realitzar cap comprovació.


© FUOC • P06/05001/00575 • Mòdul 1

39

• Object esborrar(Object elem) – Cerca ‘elem’ en el tros del vector d’elements ple. – Si s’ha trobat i.

Si ‘elem’ no és el darrer element: agafa el darrer element del tros ple del vector i el posa en la posició d’‘elem’.

ii. Assigna ‘null’ a la darrera posició plena del vector. iii. Decrementa el nombre d’elements actual. iv. Retorna l’‘elem’ trobat al vector.

Tipus abstractes de dades

Reciclatge de la memòria Amb vista al correcte reciclatge de la memòria (garbage collection) és molt important desassignar els objectes que ja no necessitem guardar, i sobreescriure’n la referència amb ‘null’, tal com es fa en el pas (ii). En el mòdul “Contenidors seqüencials”, s’explica el reciclatge de memòria en Java, i allà s’entendrà la importància d’aquesta acció i el motiu.

– Si no s’ha trobat, retorna ‘null’. Un cop vist l’esquema d’implementació, veurem com es tradueix això al llenguatge Java. A continuació teniu les parts més representatives de la classe ConjuntVectorImpl: ConjuntVectorImpl.java package uoc.ei.exemples.modul1; public class ConjuntVectorImpl implements ConjuntAfitat { /** Taula que guarda els elements del conjunt. */ private Object[] elements; /** Nombre d'elements que el conjunt conté actualment.*/ private int nombreElementsActual; public ConjuntVectorImpl(int n) { elements = new Object[n]; nombreElementsActual = 0; } ... public Object esborrar(Object elem) { Object elementEsborrat = null; int posicio = cercarPosicioElement(elem); if (posicio!= -1) { elementEsborrat = elements[posicio]; nombreElementsActual--; if (posicio<nombreElementsActual) elements[posicio] = elements[nombreElementsActual]; // important per a la recollida d’escombraries elements[nombreElementsActual] = null; } return elementEsborrat; }

A l’aula de l’assignatura disposeu dels fitxers Java corresponents als exemples d’aquest apartat. En el paquet edu.uoc.ei.exemples.modul1 trobareu les interfícies Conjunt i ConjuntAfitat, la classe ConjuntVectorImpl, i un programa d’exemple (ProvaConjunt).


40

© FUOC • P06/05001/00575 • Mòdul 1

Tipus abstractes de dades

/** Mètode privat que cerca la posició d'un element dins de la taula * on es guarden tots els elements que s'han afegit * al conjunt. */ protected int cercarPosicioElement(Object elem) { boolean trobat = false; int i = 0; while (i<nombreElementsActual && !trobat) { trobat = elements[i].equals(elem); if (!trobat) i++; } return trobat ? i : -1; } ... }

En el fragment de codi inmediatament anterior es poden apreciar els aspectes següents: • La definició dels dos atributs que conformen la representació de la col·lecció (el vector elements i l’enter nombreElementsActual). • El constructor. • El mètode esborrar, que correspon al pseudocodi que hem presentat anteriorment. • Un mètode privat de la classe anomenat cercarPosicioElem que cerca la posició d’un element al tros ple del vector elements, i retorna la seva posició o –1 si l’element no hi és. Aquest mètode és utilitzat per diversos mètodes públics de

Observeu com cercarPosicioElement prové d’una acurada aplicació de la tècnica del disseny descendent, apresa en l’assignatura Fonaments de programació. Cal no oblidar les tècniques apreses en aquesta assignatura, ni en Programació orientada a objectes.

la col·lecció (hiEs, afegir i esborrar), que necessiten buscar elements dins el tros ple del vector d’elements.

L’explicació que donarem al llarg dels diversos mòduls d’aquesta assignatura sobre la implementació de les operacions d’una col·lecció anirà sempre acompanyada d’una anàlisi de l’eficiència de les operacions. De moment, en aquest mòdul introductori no parlarem d’aquest tema.

a

3.2.4. Ús de la col·lecció A continuació teniu un fragment d’un programa que usa ConjuntVectorImpl. Aquest programa mostra l’ús de la col·lecció. Permet crear conjunts de fins a 10 elements afegint-los un a un. Un cop tenim el conjunt, el mostra per la sortida estàndard.

Els conceptes bàsics per a analitzar l’eficiència d’un algorisme es presenten en el mòdul “Complexitat algorísmica” d’aquesta assignatura.


© FUOC • P06/05001/00575 • Mòdul 1

41

Tipus abstractes de dades

ProvaConjunt.java package uoc.ei.exemples.modul1; ... public class ProvaConjunt { ... public static void main(String[] args) { boolean sortir = false; ConjuntAfitat cjt = new ConjuntVectorImpl(10); while (!sortir) { try { String elem = readLine("Introdueix un num (o \"fi\"): ",System.in); sortir = elem.equalsIgnoreCase("fi"); cjt.afegir(new Integer(Integer.parseInt(elem))); if (cjt.estaPle()) { System.out.println("El conjunt és ple. No s'hi poden afegir més elements."); sortir = true; } } catch (NumberFormatException e) { System.out.println("El text introduït no és un nombre torna-ho a provar."); } catch (IOException e) { System.out.println("Problema d’E/S, sortint"); sortir = true; } } System.out.println("El conjunt resultant és: "+cjt); } }

Com podeu apreciar, en primer lloc es crea la col·lecció, i després s’hi afegeixen elements un a un fins que o bé el conjunt està ple o bé l’usuari decideix

Com sempre, trobareu el programa complet com a recurs electrònic a l’aula.

no omplir-lo més (i entra el valor especial ‘fi’). Es tracta d’un programa senzill que no té més interès que mostrar l’ús de la col·lecció que ens ha ocupat en aquest apartat. Cal remarcar alguns aspectes de la programació que és important respectar quan utilitzem una col·lecció o TAD: • Per a la creació de la col·lecció és imprescindible fer referència a la implementació de la col·lecció (ConjuntVectorImpl). Ara bé, un cop creada, és una bona pràctica de programació fer referència únicament a la interfície, sempre que això sigui possible. En el programa d’exemple, la variable cjt és de tipus ConjuntAfitat. Per tant, en el programa sempre fem referència a aquesta interfície.

La majoria d’aquests aspectes ja han estat comentats amb anterioritat. Atesa la seva importància, però, no serà sobrer recordar-los un cop ja tenim una visió més clara del que és un TAD i del seu ús.


© FUOC • P06/05001/00575 • Mòdul 1

42

Tipus abstractes de dades

• Recordeu que el programa usuari d’un TAD és responsable de garantir que, quan en crida una operació, se’n compleixi la precondició. En aquest cas, ProvaConjunt és responsable de garantir les precondicions dels mètodes que

Vegeu les eines de disseny per contracte en el subapartat 2.4.1 d'aquest mòdul.

ha cridat. Per tant, el nostre programa de prova és responsable de garantir que, quan es crida el mètode afegir, els elements afegits hi càpiguen. Això, en aquest cas, es fa amb la instrucció if posterior a la crida a afegir que comprova si el conjunt està ple i, si ho està, ja no deixa seguir afegint elements. Fixeu-vos també que és necessari fer servir la interfície ConjuntAfitat, perquè la interfície Conjunt no disposa del mètode estaPle i, en conseqüència, no ens permetria realitzar les comprovacions necessàries per a garantir la precondició del mètode afegir. • Per acabar –i això no fa referència a l’ús d’un TAD en concret, sinó al llenguatge Java en general–, recordeu que mentre un mètode s’està executant es pot produir una situació d’error que normalment provoca el llançament d’una excepció. És important capturar aquestes excepcions en el context adequat, en el qual sabem com tractar la situació excepcional. Veieu com a ProvaConjunt es capturen dues situacions excepcionals. Una d’elles, corresponent a l’excepció IOException, es pot produir en llegir una línia de l’entrada estàndard. En aquest cas, si detectem un error d’aquest tipus, decidim acabar el programa. L’altra situació excepcional es produeix en convertir una cadena de text (String) en un nombre enter, en la crida al mètode Integer.parseInt. En aquest cas, es deu a un error produït per l’usuari quan tecleja. Per tant, el tractament proposat és escriure un missatge explicatiu i tornar a demanar l’enter.

Raonament per al lector Queda com a raonament per al lector deduir com, amb aquesta instrucció if posterior a la crida a afegir, es garanteix que el mètode afegir no es cridarà mai quan el conjunt estigui ple.


© FUOC • P06/05001/00575 • Mòdul 1

43

Tipus abstractes de dades

4. Tipus genèrics o paramètrics

En l’apartat anterior hem desenvolupat un exemple de col·lecció. Fixeu-vos que hi ha un tema que hem obviat fins a aquest moment: quin tipus d’elements guarda la col·lecció? En l’apartat anterior hem assumit de manera implícita que els elements de les col·leccions eren de tipus Object, tal com es pot comprovar revisant les signatures dels mètodes dels TAD vistos com a exemple fins ara. Això vol dir que podrem fer servir com a elements dels conjunts de l’apartat anterior qualsevol objecte que sigui instància d’Object o d’una subclasse d’aquesta. És a dir, podrem fer servir com a element qualsevol objecte, deixant

Com ja sabeu de Programació orientada a l’objecte, el tipus Object és el tipus més general en Java. Tota la resta de tipus de Java hereten directament o indirecta del tipus Object (excepte els primitius, que són int, char, boolean, float i double).

de banda els tipus Java primitius. Per tant, els conjunts de l’apartat anterior poden contenir instàncies d’Integer, tal com es mostra en l’exemple al final de l’apartat; però també podríem posar-hi instàncies de String, Boolean, o qualsevol classe definida per nosaltres mateixos. Fins i tot, ningú ens impedeix afegir a un conjunt elements que siguin instància de Conjunt (i, per tant, podríem tenir fàcilment conjunts de conjunts). Tampoc existeix cap impediment perquè en un mateix conjunt hi pugui haver elements de diverses classes. L’única restricció imposada per la signatura de Conjunt, i que comprovarà el compilador de Java, és que tots els elements siguin instància d’Object. A primera vista, aquesta pot semblar la situació ideal (perquè és la més general). Però moltes vegades –de fet és el més habitual– ens interessarà definir conjunts en què tots els elements siguin del mateix tipus. En aquestes situacions, seria força útil estar segurs que tots els elements afegits a un conjunt són instàncies d’aquest tipus concret. Això no es pot fer amb les interfícies i la implementació vistes en l’apartat anterior, que accepten elements del tipus més general que existeix a Java (Object). Per a fer-ho, caldria definir de nou les interfícies corresponents, amb una nova signatura per a cada un dels mètodes; i també caldria proporcionar una nova implementació. En aquestes interfícies, caldria modificar les signatures dels mètodes, en les quals substituiríem el tipus Object dels elements pel tipus concret que necessitéssim. Quant a la implementació, puix que les signatures dels mètodes diferirien de la implementació de l’apartat anterior, caldria redefinir les implementacions de tots els mètodes, sense poder-ne reutilitzar cap de les existents. Els tipus paràmetrics proporcionen una solució elegant a aquest problema, ja que permeten definir tipus genèrics que tenen com a paràmetres altres tipus, que no cal concretar en el moment de la definició del tipus genèric, ja que es concretaran més endavant, en el moment de la instanciació.

No podríem aprofitar les implementacions existents mitjançant herència; però sí fent ús d’altres tècniques de l’orientació a objectes (vegeu l’exercici d’autoavaluació 11).


© FUOC • P06/05001/00575 • Mòdul 1

44

Tipus abstractes de dades

En aquesta assignatura, aquesta tècnica ens permetrà definir col·leccions d’elements sense concretar el tipus dels elements a l’hora de proporcionar la implementació. Serà posteriorment, en el moment d’utilitzar la col·lecció, quan concretarem el tipus dels elements. El concepte de tipus paramètrics, també anomenats genèrics, ja s’esmenta a l’assignatura Programació orientada a objectes. En aquest apartat, veurem com utilitzar tipus paramètrics en Java. La utilització de tipus paramètrics no resulta gaire complexa. En aquest apartat, la presentarem mitjançant un exemple en què incorporarem l’ús de tipus paramètrics a la col·lecció Conjunt de l’apartat anterior.

Definirem i utilitzarem un tipus paramètric en dues fases: 1) Definició del tipus paramètric en funció d’un o més paràmetres que corresponen a altres tipus que no es concreten en el moment de la definició. 2) Substitució dels paràmetres per tipus concrets, i conversió del tipus paramètric en un tipus “normal”, del qual podrem crear instàncies. Normalment, aquest pas es fa en el mateix moment de la instanciació, però en alguns casos ens interessarà fer-lo en algun altre moment (per exemple, quan estiguem definint un altre tipus).

Vegem aquestes dues fases per a la col·lecció Conjunt. En primer lloc definim la interfície Conjunt amb un paràmetre que correspon al tipus dels elements, tal com es mostra a continuació:

Conjunt.java package uoc.ei.exemples.modul1.generics; public interface Conjunt<E> { public void afegir(E elem); public boolean hiEs(E elem); public E esborrar(E elem); }

Fixeu-vos en els aspectes següents: • S’especifica el paràmetre al final del nom de la interfície (o classe), i es delimita mitjançant els símbols ‘<’ i ‘>’. Podríem tenir més d’un paràmetre. En aquest cas, utilitzaríem la mateixa tècnica i separaríem els paràmetres amb comes (per exemple, podríem definir la classe Parell<A,B>).

Els tipus paramètrics s’esmenten en el subapartat 6.2 del mòdul 3 de l’assignatura Programació orientada a l’objecte, tot i que no s’estudien en profunditat.

Els tipus paramètrics en Java La incorporació dels tipus paramètrics en Java s’ha realitzat en la seva versió 1.5, disponible des de ben avançat el 2004. Altres llenguatges com C++, Eiffel i Ada també disposen de tipus paramètrics.


© FUOC • P06/05001/00575 • Mòdul 1

45

Tipus abstractes de dades

• Un cop especificats els paràmetres, els podem fer servir dins el cos de la classe o de la interfície en qualsevol moment, com qualsevol altre tipus que tinguem definit. Fixeu-vos com les signatures dels mètodes de la interfície Conjunt són idèntiques a les de l’apartat anterior, i han substituït Object per E. A més de la interfície Conjunt, i igual que hem fet en l’apartat anterior, cal definir la interfície ConjuntAfitat (que no mostrarem aquí) i la implementació ConjuntVectorImpl, de la qual en teniu un tros a continuació.

ConjuntVectorImpl.java package uoc.ei.exemples.modul1.generics; public class ConjuntVectorImpl<E> implements ConjuntAfitat<E> {

/** Taula que guarda els elements del conjunt. */ private E[] elements; /** Nombre d'elements que el conjunt conté actualment. */ private int nombreElementsActual; public ConjuntVectorImpl(int n) { elements = (E[])new Object[n]; nombreElementsActual = 0; } ... public E esborrar(E elem) { E elementEsborrat = null; int posicio = cercarPosicioElement(elem); if (posicio!= -1) { elementEsborrat = elements[posicio]; nombreElementsActual--; if (posicio<nombreElementsActual) elements[posicio] = elements[nombreElementsActual]; } return elementEsborrat; } }

De l’ús dels tipus paramètrics en aquesta implementació cal remarcar diverses coses:

a) La definició d’un tipus paramètric com a una extensió d’un altre tipus paramètric. En aquest exemple definim ConjuntVectorImpl<E> com a una extensió de ConjuntAfitat<E>.

Trobareu la interfície ConjuntAfitat en els recursos electrònics associats a l'assignatura.


© FUOC • P06/05001/00575 • Mòdul 1

46

b) L’aparició del paràmetre amb diferents funcions. D’una banda, apareix en la signatura dels mètodes, tal com ja havíem vist a Conjunt; però també en la definició de l’atribut elements, o en la variable local del mètode esborrar anomenada elementEsborrat; i també en l’operació de càsting del constructor. No és sobrer tornar a recalcar que podem fer servir el paràmetre (o paràmetres) de la mateixa forma que podríem fer servir qualsevol altre tipus. c) La forma com es crea l’atribut elements. La manera que podria semblar més intuïtiva per a crear un vector de dimensió n de E seria mitjançant la instrucció new E[n]. Això no és possible en Java. Per tant, hem de recórrer a l’alternativa que es mostra en el codi: crear un vector d’Object i després convertir el seu tipus en un vector de E mitjançant l’operació de càsting adequada. Per acabar, ens queda veure com concretem el tipus paramètric Conjunt<E> en un tipus que puguem instanciar per treballar amb conjunts, tal com hem fet al final de l’apartat anterior. Adaptarem a continuació l’exemple de l’apartat anterior per fer servir els conjunts paramètrics definits en aquest apartat. ProvaConjunt.java package uoc.ei.exemples.modul1.generics; ... public class ProvaConjunt { ... public static void main(String[] args) { System.out.println("Introdueix nombres per afegir al conjunt"); boolean sortir = false; ConjuntAfitat<Integer> cjt = new ConjuntVectorImpl<Integer>(10); while (!sortir) { try { ... cjt.afegir(Integer.parseInt(elem)); ... } ... } } System.out.println("El conjunt resultant és: "+cjt); } }

En el codi anterior s’observa el següent: • La substitució dels paràmetres per tipus concrets. Únicament és necessari repetir el nom del tipus i substituir els noms dels paràmetres pels tipus concrets

Tipus abstractes de dades


© FUOC • P06/05001/00575 • Mòdul 1

47

desitjats (en aquest cas Integer). Fixeu-vos que hi ha dues substitucions diferents: d’una banda, la del tipus de cjt, que és ConjuntAfitat<Integer>; i de l’altra, tenim la classe que s’instancia, que és ConjuntVectorImpl<Integer>. • Com els usos posteriors a la creació del conjunt, es realitzen exactament de la mateixa forma que en l’apartat anterior. La biblioteca de TAD de l’assignatura fa ús extensiu dels tipus paramètrics. Per tant, serà necessari que tingueu instal·lada la versió 1.5 de Java (també anomenada Java 5).

Tipus abstractes de dades


© FUOC • P06/05001/00575 • Mòdul 1

48

Tipus abstractes de dades

5. Biblioteca de col·leccions de l’assignatura

Com a part força important del material docent del curs disposeu d’una biblioteca de col·leccions que s’ha dissenyat i implementat especialment per a aquesta assignatura. Aquesta biblioteca, a la qual també ens referirem com a biblioteca de TAD de l’assignatura, conté: • Una jerarquia de TAD amb totes les col·leccions que anireu veient al llarg del curs. S’ha intentat que la jerarquia sigui senzilla però, a la vegada, suficientment potent i flexible perquè resulti efectiva en un ampli rang de situacions. • Totes les implementacions d’aquests TAD que apareixen en el text. D’aquesta manera, podreu examinar el codi Java de cada una de les implementacions. La biblioteca us acompanyarà al llarg de tot el curs. D’una banda, estudiareu la signatura i el comportament de diferents TAD, i a continuació la seva implementació. Trobareu definides totes dues coses a la biblioteca, a la qual podreu recórrer per completar la informació subministrada en el material en paper. A més, a la biblioteca trobareu la definició dels contractes per a cada una de les implementacions. D’altra banda, en el curs es fa èmfasi en l’ús de les col·leccions mitjançant exercicis. En aquest altre vessant, actuareu com a usuaris de la biblioteca, com qualsevol desenvolupador Java quan fa ús de les classes definides en la Java Collections del JDK. A continuació, es descriuen l’estructura i els elements principals de la biblioteca de TAD de l’assignatura, de manera que quan hagueu de fer servir o examinar alguna col·lecció concreta, ja en tingueu una visió general.

5.1. Jerarquia de col·leccions Les col·leccions de la biblioteca estan estructurades en una jerarquia. L’arrel de la jerarquia és la interfície Contenidor, que defineix els mètodes bàsics que tota col·lecció proporcionarà: • elements(): permet accedir a cadascun dels elements emmagatzemats en la col·lecció mitjançant una construcció auxiliar anomenada Iterador. • estaBuit(): retorna ‘cert’ si la col·lecció és buida i ‘fals’ en cas contrari. • nombreElems(): retorna el nombre d’elements de la col·lecció.

Vegeu els iteradors en el mòdul “Contenidors seqüencials” d’aquesta assignatura.


© FUOC • P06/05001/00575 • Mòdul 1

49

Tipus abstractes de dades

Els tipus de contenidor bàsics que apareixen al llarg dels mòduls es defineixen com interfícies que estenen la interfície Contenidor directament. Així, estenen directament l’arrel de la jerarquia: ContenidorAfitat, Pila, Cua, Llista, Arbre, Diccionari i Conjunt. A fi de proporcionar una biblioteca senzilla, les úniques interfícies de la biblioteca que representen col·leccions seran aquestes. Cadascuna d’aquestes interfícies proporciona la signatura directament utilitzable dels respectius TAD (excepte la base, Contenidor i ContenidorAfitat, que defineix únicament un aspecte dels contenidors). Podeu copsar aquesta jerarquia a la figura 1. Aquesta “minijerarquia” d’interfícies es completa amb una jerarquia de classes que té un doble objectiu: D’una banda, té l’objectiu obvi de proporcionar les implementacions de les diferents col·leccions. De l’altra, ens serveix per a proporcionar aquestes implementacions de manera orientada a objectes i així permetre definir el comportament més general i reutilitzable en el nivell més alt de la jerarquia i el comportament específic d’una implementació concreta en els nivells més baixos. Figura 1

En les figures que apareixen a continuació, es mostren diverses parts de la jerarquia d’implementacions de la biblioteca. En la figura 3, podeu observar les implementacions dels TAD seqüencials: les dues implementacions proporcionades per al TAD Llista i les implementacions afitades dels TAD Pila i Cua.

Tractarem en profunditat les col·leccions esmentades en el mòdul “Contenidors seqüencials” i en el mòdul “Cues amb prioritat”, en què s’estudia amb detall aquesta implementació concreta.


© FUOC • P06/05001/00575 • Mòdul 1

50

Tipus abstractes de dades

Figura 2

Pel que fa als arbres, la biblioteca té una jerarquia d’implementacions moderadament profunda, motivada principalment per maximitzar la reutilització del codi. En l’àmbit funcional, ens trobem però amb un conjunt de TAD més reduït: principalment arbres binaris, arbres n-aris i arbres ordenats.

Si bé no és tasca d’aquesta assignatura tractar la reutilització del codi en una jerarquia de classes, quan estudieu les implementacions arbòries, seria força interessant que reviséssiu el codi de la biblioteca tenint en compte aquest aspecte.


© FUOC • P06/05001/00575 • Mòdul 1

51

Figura 3

Així, per exemple, l’ArbreBinari és una classe abstracta que implementa la interfície Arbre. En aquesta classe, es defineixen el conjunt de mètodes dels arbres binaris i el comportament d’aquests arbres, que és independent de la

Tipus abstractes de dades


© FUOC • P06/05001/00575 • Mòdul 1

52

Tipus abstractes de dades

representació. Això permet reutilitzar aquest comportament tant per als arbres de cerca com per a les cues amb prioritat. En la figura 3 es mostra la jerarquia d’implementacions referent als arbres. Una tercera part de les implementacions proporcionades a la biblioteca consisteix en implementacions de les interfícies Diccionari i Conjunt. A diferència de les implementacions introduïdes en els diagrames anteriors, aquestes no defineixen una estructura per als elements, sinó que es limiten a definir una funcionalitat implementable mitjançant diferents tipus d’estructures. La biblioteca proporciona diverses implementacions per a cadascuna d’aquestes interfícies. Aquestes implementacions són funcionalment equivalents, si bé difereixen en l’organització de les dades que permet proporcionar el comportament volgut. La majoria deleguen en una altra col·lecció. En la figura 4 es mostra la part de la jerarquia relacionada. Figura 4

Els arbres binaris s’expliquen en el mòdul “Arbres”. Els arbres de cerca es veuen en el mòdul “Arbres de cerca”. Les cues amb prioritat s’estudien en el mòdul “Cues amb prioritat”.

Els TAD Diccionari i Conjunt i les seves implementacions s’estudien en els mòduls “El TAD Taula” i “Arbres de cerca”.


© FUOC • P06/05001/00575 • Mòdul 1

53

Tipus abstractes de dades

Per acabar, la biblioteca també proporciona la possibilitat de treballar amb grafs. A causa de la seva natura, el TAD Graf no està classificat com a subclasse de Contenidor. La jerarquia corresponent queda aïllada de la resta de TAD presentats, i té com a base la mateixa interfície Graf. En la figura 5 podeu apreciar la part de la jerarquia relacionada. Figura 5

5.2. Tipus auxiliars A part de les interfícies i classes que representaran les col·leccions i les seves implementacions, es necessiten també alguns tipus auxiliars que ens permetran treballar d’una manera homogènia amb les col·leccions.

Les implementacions de Graf es treballen en el mòdul “Grafs” d’aquesta assignatura.


© FUOC • P06/05001/00575 • Mòdul 1

54

Tipus abstractes de dades

En concret, a la biblioteca de col·leccions de l’assignatura necessitarem fer referència als aspectes següents: • Els elements de la col·lecció. Atès que la biblioteca fa ús extensiu de tipus paramètrics, podrem instanciar qualsevol col·lecció amb qualsevol tipus Java, incloent-hi els tipus bàsics. Hi ha col·leccions, però, que imposen alguna condició sobre el tipus dels elements com, per exemple, que implementin alguna interfície (Comparable). • Claus. Algunes col·leccions permeten accedir als elements que emmagatzemen mitjançant claus. Per exemple, podem voler accedir a les dades d’una persona pel seu DNI. Com passa amb els elements, les claus també poden ser de qualsevol tipus. Ara bé, sovint les col·leccions imposen condicions sobre els tipus semblants a les comentades en el punt anterior per als elements. • Posicions. Hi ha col·leccions que estan organitzades posicionalment, de manera que cada element està emmagatzemat en una posició concreta. Aquest tipus de col·leccions ofereixen un conjunt d’operacions per treballar amb posicions. La biblioteca conté una interfície Posicio que representa aquesta abstracció. • Iteradors i recorreguts. Sovint ens interessarà recórrer els elements d’una col·lecció. La noció d’iterador permet fer-ho d’una manera homogènia per totes les col·leccions de la biblioteca. La biblioteca defineix una interfície Iterador. D’una forma semblant, també defineix una interfície Recorregut, anàloga a Iterador, però per a les posicions d’un contenidor. • Comparadors. Algunes col·leccions necessiten comparar els seus elements de manera interna. Per exemple, si la col·lecció emmagatzema els elements en un cert ordre, serà necessari poder comparar dos elements per a decidir quin dels dos és més petit. Totes les col·leccions de la biblioteca que tenen aquest requisit ofereixen dues maneres de fer aquesta comparació: o bé el tipus dels elements implementa la interfície Comparable, o bé en el moment de creació de la instància de la col·lecció es proporciona un objecte comparador que fa la feina. En el primer cas, es delega la feina en els mateixos elements. En el segon, proporcionem un objecte independent que sap com comparar els elements. En aquest cas, en lloc de proporcionar les interfícies a la biblioteca de l’assignatura, es fa ús de les interfícies java.util.Comparable i java.util.Comparator definides en el JDK.

El concepte de posició apareix en les col·leccions de seqüències i també en els arbres, i es treballa a fons en el mòdul “Contenidors seqüencials” d’aquesta assignatura.


© FUOC • P06/05001/00575 • Mòdul 1

55

Tipus abstractes de dades

6. Presentació de la resta de mòduls

En el mòdul “Complexitat algorísmica”, s’estudia la noció de cost asimptòtic. Aquesta noció és cabdal en aquesta assignatura i ens permet fer-nos una idea de l’eficiència de les implementacions tant en l’àmbit temporal (temps necessari per a fer les operacions dels TAD) com espacial (espai ocupat per una representació). La noció de cost asimptòtic ens acompanyarà al llarg de tota l’assignatura i ens servirà per a decidir la representació o implementació més convenient en cada moment, segons cada situació. Tal com ja s’ha comentat en la presentació de la biblioteca de TAD de l’assignatura, en els mòduls “Contenidors seqüencials”, “Arbres”, “Cues amb prioritat”, “El TAD Taula”, “Arbres de cerca” i “Grafs”, es presenten els diferents TAD que s’estudien en l’assignatura (un o més en cada mòdul). Per a cada un d’aquests TAD s’explica, en primer lloc, la interfície proposada a la biblioteca de col·leccions de l’assignatura juntament amb el seu comportament i, després, es discuteixen una o més implementacions. En el mòdul “Contenidors seqüencials”, es presenten les col·leccions amb una organització seqüencial. El seu estudi serveix per a introduir també alguns conceptes que ens serviran en altres mòduls, com ara el concepte d’iterador, les representacions encadenades i l’amortització de costos. En el mòdul “Arbres”, s’estudien els arbres i també s’introdueix la recursivitat, una noció algorísmica important que resulta molt útil, entre d’altres coses, per a definir algorismes sobre estructures arbòries. En el mòdul “Cues amb prioritat” es veuen les cues prioritàries, i se n’estudia una implementació força eficient basada en una estructura arbòria. El TAD Diccionari s’estudia entre els mòduls “El TAD Taula” i “Arbres de cerca”. Un Diccionari és un TAD que ens permet accedir a un element a partir d’una clau. En el mòdul “El TAD Taula” es presenta el TAD i se n’estudia una implementació eficient basada en el que s’anomena taules de dispersió. En el mòdul “Arbres de cerca” s’estudia una altra implementació basada en arbres. En aquest mòdul, es presenten també algunes variants dels arbres de cerca especialment dissenyades per a emmagatzemar-les en la memòria secundària (en lloc de la memòria primària, com les estructures que s’hauran presentat fins aleshores). Aquestes variants s’utilitzen en la implementació de bases de dades. El mòdul “Grafs” presenta diverses versions del TAD Graf. Els mòduls anteriors es centren en TAD que permeten representar col·leccions d’elements i que tenen una estructura força rígida. L’objectiu d’un graf no és tant representar un

La memòria secundària Aquest tipus de memòria normalment fa referència al disc, i és molt més lenta que la memòria primària. En la implementació de bases de dades, s’ha de tenir en compte el factor de la memòria secundària, ja que la mida de les dades fa normalment inviable tenir-les totes a la vegada en memòria primària.


© FUOC • P06/05001/00575 • Mòdul 1

56

conjunt d’elements, sinó més aviat definir una estructura en què participin un conjunt d’elements, sense cap restricció en la seva forma. Aquest TAD és la base per a un conjunt d’algorismes extrapolables a moltes situacions del món real que ens permeten dur a terme una diversitat de càlculs complexos. En aquesta assignatura, però, ens centrarem estrictament en temes de representació i implementació de les operacions bàsiques del TAD, ja que els algorismes aplicables sobre grafs s’estudien en l’assignatura Matemàtica discreta. Per acabar, un cop vistos tots els TAD que s’estudien en el curs, el mòdul “Disseny d’estructura de dades explica com dissenyar nous TAD basant-nos en un conjunt de col·leccions bàsiques (les que haureu vist en els altres mòduls). A part d’això, el mòdul parla sobre el disseny de biblioteques de col·leccions, i analitza els factors que fan que una biblioteca sigui més còmoda d’usar que una altra, o més potent. Finalment, es presenten algunes de les biblioteques més comunament usades en el món del desenvolupament orientat a objectes.

Tipus abstractes de dades


© FUOC • P06/05001/00575 • Mòdul 1

57

Resum

En aquest mòdul hem presentat les nocions bàsiques i el marc teòric que ens acompanyarà al llarg de la resta del curs. En primer lloc, hem estudiat la noció de contenidor, un element bàsic en el desenvolupament de qualsevol aplicació informàtica. Seguidament, hem vist la noció de TAD, que fem servir com a abstracció per a separar l’especificació de la implementació dels contenidors. Una altra de les nocions bàsiques introduïdes en aquest mòdul és la del disseny per contracte (DBC), que s’ha presentat a l’apartat 2. La noció de contracte ens fa raonar sobre què és responsabilitat del TAD i què és responsabilitat de l’usuari del TAD. El dissenyador del TAD és qui estableix el contracte i qui decideix qui té cada responsabilitat. Depenent de les decisions que prengui el dissenyador, el TAD serà més o menys usable, i més o menys mantenible. Un cop definit, el contracte és inamovible, i l’usuari del TAD s’hi ha d’adaptar. Posteriorment, s’ha definit una col·lecció d’exemple, usant el format que es farà servir en els mòduls següents per a presentar cadascuna de les col·leccions estudiades en el curs. S’han presentat els tipus paramètrics en Java i s’ha tornat a definir la col·lecció d’exemple, a fi i efecte de treure’n partit. Finalment, s’ha presentat la biblioteca de col·leccions de l’assignatura.

Tipus abstractes de dades


© FUOC • P06/05001/00575 • Mòdul 1

59

Exercicis d’autoavaluació 1. Expliqueu la diferència entre la noció de contenidor i la noció de tipus abstracte de dades. 2. Expliqueu amb les vostres paraules els avantatges principals de desvincular l’especificació de la implementació en el cas de les col·leccions o contenidors. 3. Quina relació hi ha entre l’especificació d’un algorisme vista en l’assignatura de Fonaments de programació i l’especificació d’un TAD vista en aquest mòdul? 4. Reviseu l’especificació del TAD Nat del subapartat 1.2.3 i la de la interfície Nat del subapartat 2.4. Diuen el mateix? Raoneu-ne les diferències i el motiu. 5. Incorporeu dues noves operacions al TAD Nat: una operació anomenada multiplicar que multipliqui el natural per un altre natural, i una altra operació anomenada dividir, que en faci la divisió entera. Se us demana el següent: • Definiu la signatura de les dues operacions de manera similar a la de l’operació sumar (amb un paràmetre de tipus Nat). • Establiu el contracte per a aquestes dues noves operacions. Penseu a definir-lo seguint les recomanacions de l’apartat 2. Proporcioneu l’especificació de les noves operacions del TAD en llenguatge natural. • Incorporeu les dues operacions a les implementacions vistes en el text: – Definiu-ne l’especificació. – Codifiqueu les operacions en llenguatge Java. 6. Definiu un TAD anomenat Hora, que representi un moment del dia constituït per hora (0-23), minut (0-59) i segon (0-59), amb les quatre operacions següents: • constructor(int h,int m,int s): crea una hora a partir de tres enters que representen hora, minut i segon. • tick: avança l’hora un segon. • sumar(Hora h): suma una altra hora. • restar(Hora h): resta una altra hora. L’especificació del TAD no se us dóna de manera expressa. a) Avalueu diferents possibilitats a l’hora de definir el contracte del TAD, i raoneu-ne els avantatges i desavantatges. b) Trieu-ne una i proporcioneu l’especificació del TAD. c) Definiu el TAD en Java. d) Proposeu una representació per al TAD Hora en Java. e) Proporcioneu l’invariant de la representació. f) Especifiqueu (usant el sistema DBC de l’assignatura) i implementeu el TAD Hora fent servir la representació proposada (si voleu hi, podeu definir mètodes addicionals com, per exemple, toString). 7. Desenvolupeu una aplicació usuària del TAD Hora que calculi el dia i l’hora d’arribada d’un vol, a partir del dia i l’hora de sortida, el temps de vol i el nombre de franges horàries que creua. Per exemple, en un vol Barcelona-Ciutat de Mèxic que surt a les 10 del matí, triga 14 hores (amb escala) i creua 6 franges horàries, l’apliació hauria de donar com a resultat les 18 hores. • Quina problemàtica veieu en l’ús del TAD Hora per a aquesta aplicació? Es podria produir incompliment del contracte en alguna situació? • En cas que el contracte i/o la signatura del TAD Hora que hagueu dissenyat en l’exercici anterior no s’adapti a aquesta aplicació, feu les modificacions necessàries, i raoneu-ne els motius. 8. Reviseu la documentació Javadoc de les classes del JDK i, més en concret, la interfície Collection i la classe abstracta AbstractCollection: • Feu una ullada a la jerarquia d’interfícies i classes que hereten de les dues i identifiqueu la representació Java de, al menys, dos TAD. És a dir, us demanem que identifiqueu dues interfícies, i per a cada una d’elles, al menys una classe que la implementi. • Cerqueu al menys un TAD que tingui més d’una implementació. 9. Volem afegir, a la col·lecció Conjunt desenvolupada a l’apartat 3, l’operació unio(Conjunt c). Aquesta operació ha d’afegir al conjunt els elements de c que no hi siguin. Se us demana el següent: • Especifiqueu l’operació unio en un nivell de TAD en llenguatge natural. • Especifiqueu-la amb el sistema de DBC de l’assignatura per a la representació del subapartat 3.2.1. • Afegiu l’operació a la implementació del subapartat 3.2. Feu el mateix amb les operacions respectives d’intersecció i diferència de conjunts.

Tipus abstractes de dades


© FUOC • P06/05001/00575 • Mòdul 1

60

10. Doneu una explicació informal sobre l’eficiència temporal de les tres operacions de l’exercici 9 (després d’estudiar el mòdul “Complexitat algorísmica” podreu fer aquest raonament d’una manera totalment precisa!). Addicionalment, se us demana: • Busqueu alguna forma de modificar la representació (o les seves propietats) amb l’objectiu de fer més eficients les implementacions de l’exercici anterior. • Contesteu si després d’aquesta modificació, és necessari modificar l’especificació del TAD. En cas afirmatiu, digueu quines parts. • Contesteu si passa el mateix amb l’especificació de la implementació amb el sistema DBC de l’assignatura. En cas afirmatiu, digueu quines parts. 11. Imagineu que no existeixen els tipus paramètrics en Java i que volem definir un conjunt de strings. A més, volem estar segurs en temps de compilació que tots els elements que s’afegeixen al conjunt són del tipus String. Per això se us demana el següent: • Definiu en Java una interfície anomenada ConjuntString amb la signatura adaptada per a forçar els elements del conjunt a ser del tipus String. • Digueu si podeu heretar la signatura Conjunt vista en l’apartat 3 i per què. • Doneu una implementació d’aquesta interfície. Podeu reaprofitar d’alguna manera la implementació vista al subapartat 3.2? En cas afirmatiu, us hi pot ajudar alguna tècnica de l’orientació a objectes? 12. Traslladeu les implementacions de l’exercici 9 a la variant de TAD Conjunt que fa ús de tipus paramètrics definida en l’apartat 4. 13. Busqueu semblances entre els TAD que heu trobat en l’exercici 8 i els de la biblioteca de TAD de l’assignatura. Guieu-vos únicament per la funcionalitat bàsica del TAD. Tingueu en compte que TAD conceptualment semblants poden diferir en el nombre d’operacions i la signatura d’aquestes segons els criteris fets servir en el disseny de la biblioteca de la que formin part. 14. Hi ha a la biblioteca de TAD de l’assignatura un equivalent per a la interfície Collection del JDK? I per a AbstractCollection? Quins creieu que són els motius per a ambdues coses? 15. Feu una ullada al codi de la biblioteca de TAD de l’assignatura. Intenteu esbrinar per a quines implementacions es fa servir delegació; és a dir, quines implementacions deleguen la definició del comportament del TAD en una altra implementació (molt probablement d’un altre TAD). 16. Reviseu el Javadoc de la classe java.util.Comparator del JDK. Proporcioneu-ne una implementació per a la implementació del TAD Hora que heu definit a l’exercici 7.

Tipus abstractes de dades


© FUOC • P06/05001/00575 • Mòdul 1

61

Solucionari 1. Quan parlem de contenidor, definim la funcionalitat d’una entitat: emmagatzemar elements. En canvi, quan parlem de tipus abstracte de dades o TAD, fem referència a una abstracció que ens permet desvincular la definició del seu comportament (especificació) del mètode concret que es fa servir per a proporcionar aquest comportament (implementació). Un TAD és una bona eina per a definir contenidors; ara bé, el seu ús és més ampli. 3. L’especificació d’un algorisme vista en l’assignatura Fonaments de programació és equivalent a l’especificació d’una operació d’un TAD. Aquí definim l’especificació d’un TAD com un conjunt d’especificacions per a les seves operacions. 4. Sí, no podria ser d’altra manera. L’única diferència és el formalisme que es fa servir: en el primer cas es fa servir llenguatge natural i, en el segon, el sistema DBC de l’assignatura. No sempre serà possible especificar els TAD fent servir el sistema DBC de l’assignatura. Les implementacions, en canvi, sí, ja que sempre disposarem de la representació, a la qual podrem fer referència en les expressions Java que el sistema DBC usa. 7. Problemàtica de l’ús del TAD Hora: es pot donar la situació que un avió surti del seu origen un dia i arribi el dia següent al seu destí. Si el contracte establert en l’exercici 6 responsabilitza l’usuari del TAD de fer que les operacions executades no canviïn de dia, o bé es produiria incompliment de contracte o bé es requeriria un tractament laboriós de l’aplicació usuària. Aquest tractament laboriós replicaria en part el comportament definit al TAD Hora, cosa no desitjable. Modificacions necessàries El contracte s’hauria de modificar de manera que fos responsabilitat del TAD tractar el pas d’un dia a un altre. Això es pot fer de diverses maneres: a) Ampliant el conjunt de valors representats, és a dir, passant del TAD Hora a un TAD que podríem anomenar Instant i que tingués en compte també el dia, i en modifiqués el valor si la suma o resta d’hores fes canviar de dia. b) Modificant la signatura de les operacions relacionades del TAD Hora de manera que s’“avisi” l’usuari d’alguna manera que s’ha canviat el dia. Per exemple, retornant un booleà que seria ‘cert’ si es canvia de dia i, ‘fals’, en cas contrari. c) Notificant a l’usuari del TAD la situació excepcional de sobreeiximent mitjançant el llançament d’una excepció. L’usuari del TAD hauria de capturar l’excepció, que contindria informació necessària sobre la situació excepcional. Podríem pensar que aquest és un exemple en què la programació defensiva és una bona solució. En realitat, es tracta més d’un tema funcional en què l’aplicació desenvolupada delega el control sobre el canvi de dia al mateix TAD que s’encarrega de fer el càlcul d’hores, ja que ambdues tasques estan força relacionades. 8. La jerarquia de subinterfícies de java.util.Collection proporcionada en el JDK és força senzilla. Podem identificar la interfície List i la interfície Set com als TAD Llista i Conjunt (totes dues al paquet java.util, com les classes comentades a continuació). El JDK proporciona diverses implementacions per a cada una d’elles. En el cas de java.util.List trobem 3 implementacions diferents: ArrayList, LinkedList i Vector. En el cas de Set, tenim HashSet, LinkedHashSet i TreeSet (noteu que aquest darrer implementa també una subinterfície de Set: SortedSet). 13. El TAD representat al JDK mitjançant la interfície java.util.List seria conceptualment semblant al representat per la interfície de la biblioteca de TAD de l’assignatura uoc.ei.tad.Llista. Noteu, però, que hi ha força diferències pel que respecta a la signatura! Aquestes diferències es deuen principalment a la diferència de criteris usats en el disseny d’ambdues biblioteques (aquest tema es comenta detalladament en el mòdul “Disseny d’estructures de dades”. El TAD representat en el JDK per java.util.Set correspondria a la interfície uoc.ei.tad.Conjunt de la biblioteca de col·leccions de l’assignatura. Pel que fa a les implementacions, la interfície Llista té dues implementacions: LlistaEncadenada i LlistaDoblementEncadenada. I Conjunt té també dues implementacions: ConjuntAVLImpl i ConjuntTaulaImpl. Totes les implementacions estan ubicades també en el paquet uoc.ei.tad.

Glossari contenidor m Abstracció que permet emmagatzemar i gestionar un grup d’elements. contenidor afitat m Contenidor que té una capacitat màxima.

Tipus abstractes de dades


© FUOC • P06/05001/00575 • Mòdul 1

62

contracte m Definició de les responsabilitats i drets que té un TAD, classe, mètode o operació, d’una banda, i els seus usuaris de l’altra. disseny per contracte m Tècnica de disseny basada en la definició de contractes per a separar les responsabilitats d’una classe de les dels seus usuaris. encapsulació f Característica oferta per alguns llenguatges de programació que permet definir entitats (mòduls, classes...) en què els detalls d’implementació queden amagats dels seus usuaris. especificació d’un TAD f Descripció del comportament del TAD corresponent. implementació d’un TAD f Conjunt d’algorismes presentats en una signatura igual que la del TAD i que, a més, compleix l’especificació d’aquest. invariant de la representació m Propietat que és certa per a qualsevol de les representacions vàlides d’una implementació d’un TAD, i que és falsa per a representacions no vàlides. biblioteca f Conjunt de classes, mòduls, funcions o rutines que es troben habitualment empaquetades en un únic fitxer (.lib, .dll, .jar, etc.) i que es posen a disposició d’un conjunt de desenvolupadors (usuaris de la biblioteca). programació defensiva f Tècnica de programació que consisteix a protegir un algorisme contra qualsevol mal ús que se’n pugui fer (habitualment mitjançant paràmetres no adequats). operació d’un TAD f Unitat de comportament bàsica d’un TAD. Proporciona la única via d’interacció entre el TAD i els seus usuaris. representació (d’una implementació) d’un TAD f Conjunt de dades que permet emmagatzemar l’estat d’una instància del TAD per a una implementació d’aquest. tipus genèric m Vegeu tipus paramètric. tipus paramètric m Definició de tipus en què un o més elements no s’han concretat i romanen com a paràmetres.

Bibliografia Bibliografia bàsica Meyer, B. (1999). Construcción de software orientado a objetos. Madrid: Prentice Hall. Franch, X. (2001). Estructures de dades. Especificació, disseny i implementació (4a. ed.). Barcelona: Edicions UPC. Disponible en línea a: www.edicionsupc.es Peña Marí, R. (2000). Diseño de programas. Formalismo y abstracción (2a. ed.). Prentice Hall. Goodrich, M.; Tamassia, R. (2001). Data structures and algorithms in Java (2a. ed.). John Wiley and Sons.

Tipus abstractes de dades


© FUOC • P06/05001/00575 • Mòdul 1

63

Tipus abstractes de dades

Annex

Per a saber-ne més En aquest mòdul hem presentat el marc teòric en el qual desenvoluparem el curs. S’han presentat un parell de temes teòrics de manera intuïtiva i sense entrar en detalls de formalismes matemàtics: els tipus abstractes de dades (TAD) i el disseny per contracte.

Quant al disseny per contracte, us pot resultar força interessant revisar l’obra de Meyer (1999). Conté una descripció detallada i molt ben argumentada sobre el disseny per contracte, la seva motivació i els seus avantatges.

Respecte als TAD, hi ha força temes que s’han esmentat sense entrar en gaires detalls: l’especificació algebraica, les àlgebres que són models de l’anterior, els diferents tipus de semàntica que es poden fer servir per a lligar els models d’una especificació amb aquesta, etc. Tots aquests temes els podeu trobar explicats de manera detallada en les obres de X. Franch (1999) i R. Peña Marí (2000).

Els punts referents al llenguatge Java –tipus paramètrics o genèrics, i sistemes de disseny per contracte– són, o bé massa nous (el primer), o bé massa concrets (el segon) per a trobar-ne bibliografia en el moment d’escriure aquest text. On sí que podreu trobar més informació respecte als tipus paramètrics per a Java és al lloc web de Sun (http://java.sun.com).



M1