Mòdul 5   

Pràctica 3: Interfícies i Classes Abstractes
Tornar presentació tema
    Pràctica 1 Pràctica 2 Pràctica 3 Pràctica 4 Pràctica 5  
     
 

A la pràctica anterior has emparentat els diferents recursos que pot tenir una biblioteca tot fent-los fills de la superclasse Item. Amb això has aconseguit uns quants avantages: simplificació, facilitat de manteniment i escalabilitat.

En aquesta pràctica descobriràs les Interfícies i les Classes Abstractes, instruments que permeten ampliar la potència de gestió d'objectes del llenguatge.

Començaràs per les Interfícies. Si ens permets, preferim presentar-te aquest particular tipus de classes a través d'un exemple i després ja t'explicarem els seus fonaments: les interfícies són molt útils, però no sempre resulta trivial entendre el seu significat i funcionament.

 
     
  Les Interfícies I: un exemple del món del circ sense intefícies  
     
 

Imagina que ets el director d'un circ i estàs preparant una part de l'espectacle. Es tracta d'un número d'humor en el que sortiran a la pista persones, ximpanzes i elefants damunt de bicicletes tot rodant per la pista. El teu objectiu és fer un programa de Java que et tregui els artistes ciclistes a la pista.

Necessitaràs, doncs, unes classes que representin als animals i persones, "ensenyar-los" a anar a bicicleta amb algun mètode adequat i una classe que els tregui a tots a la pista.

 
     

Obre el BlueJ i crea un nou projecte amb el nom de circ. Inicialment has de crear cinc classes seguint la següent jerarquia:

 
     
 
 
     
 

Dins l'univers Java del teu circ, els cavalls, ximpanzes i elefants són de la classe Animal, que no està emparentada amb la classe Persona.

Recorda que per a crear una subclasse has d'escriure a la seva capçalera l'expressió extends NomClasseMare.

Si et fa mandra anar creant les classes amb el BlueJ, aquí et deixem el codi font. Pots tallar i empegar.

Hem ensenyat a anar en bicicleta a les classes susceptibles de ser ciclistes. Ho fem a través d' un mètode que es diu vesEnBici() que servirà, a més, per a indicar que el ciclista ha entrat a la pista i està rodant.

Com dels animals només van el bici els ximpanzès i els elefants, no escrivim el mètode vesEnBici() a la classe Animal ni tampoc a la classe Cavall (els cavalls no saben anar en bici). La resta d'objectes poden ser ciclistes i tenen, per tant, el seu propi mètode vesEnBici().

Si escrius les classes a mà en el BlueJ no oblidis d'afegir aquests mètodes.

 
     

/**
* @author Angel Solans
* @version 28-05-2005
*/

public class Animal {
    public Animal() {
    }
    // No tots els animals saben anar en bicicleta. No puc crear
    // un mètode genèric vesEnBici() per a tots els animals
}

 
 

 

 
/**
* @author Angel Solans
* @version 28-05-2005
*/

public class Persona {
    public Persona() {
    }
    public void vesEnBici() {
       System.out.println("Sóc un Home i vaig en bicicleta "+
           "sense mans");
       }
}
 
     
/**
* @author Angel Solans
* @version 28-05-2005
*/

public class Cavall extends Animal{
    public Cavall() {
    }
    // Els cavalls no saben anar en bicicleta
}
 
     

/**
* @author Angel Solans
* @version 28-05-2005
*/

public class Ximpanze extends Animal{
    public Ximpanze() {
    }
    public void vesEnBici() {
        System.out.println("Sóc un Ximpanzè i vaig en bicicleta");
    }

}

 
     
/**
* @author Angel Solans
* @version 28-05-2005
*/

public class Elefant extends Animal{
    public Elefant() {
    }
    public void vesEnBici() {
        System.out.println("Sóc un Elefant i vaig en bicicleta... "+
            "Oh! he trencat la bicicleta");
    }
}
 
     
 

Ara hem de fer el programa que ens posi els ciclistes a la pista seguint la mateixa estratègia de les pràctiques anteriors:

  • crear una base de dades de ciclistes
  • crear mètodes per a afegir ciclistes a la base de dades
  • crear un mètode per a fer anar en bicicleta a tots els ciclistes de la base de dades.

Si tots els individus que poden anar en bicicleta fossin fills de la mateixa superclasse, podríem copiar el programa de la biblioteca amb herència i ja estaria llest el problema. Però NO ho podem fer:

  • Hi ha ciclistes Persona i ciclistes Animal, i aquestes dues classes no estan emparentades.
  • A més a més, no tots els animals poden ser ciclistes (no existeix un mètode vesEnBici() a la classe Animal), i per tant, no podràs gestionar junts els ximpanzès i els elefants.

Sense més eines, has d'escriure un programa amb un plantejament similar a la primera versió del programa de biblioteca però encara una mica més pesant. Crea la classe ARodarPerLaPista1 i omple-la així:

 
     
/**
* @author Angel Solans
* @version 28-05-2005
*/

import java.util.ArrayList;
import java.util.Iterator;

public class ARodarPerLaPista1 {
    ArrayList elsQueRodenXimpanzes;
    ArrayList elsQueRodenElefants;
    ArrayList elsQueRodenHomes;

    public ARodarPerLaPista1() {
        elsQueRodenXimpanzes=new ArrayList();
        elsQueRodenElefants=new ArrayList();
        elsQueRodenHomes=new ArrayList();
    }

    public void afegeixCiclistaXimpanze(Ximpanze ciclista) {
        elsQueRodenXimpanzes.add(ciclista);
    }

    public void afegeixCiclistaElefant(Elefant ciclista) {
        elsQueRodenElefants.add(ciclista);
    }

    public void afegeixCiclistaHome(Persona ciclista) {
        elsQueRodenHomes.add(ciclista);
    }

    public void rodar() {
        for (Iterator iterador = elsQueRodenXimpanzes.iterator();
            iterador.hasNext();) {
            Ximpanze artista = (Ximpanze)iterador.next();
            artista.vesEnBici();
        }
        for (Iterator iterador = elsQueRodenElefants.iterator();
            iterador.hasNext();) {
            Elefant artista = (Elefant)iterador.next();
            artista.vesEnBici();
        }
        for (Iterator iterador = elsQueRodenHomes.iterator();
            iterador.hasNext();) {
            Persona artista = (Persona)iterador.next();
            artista.vesEnBici();
        }
    }
}

 
     
 

Compil·la el projecte, crea una persona, un ximpanzè, un elefant i una instància de la classe ARodarPerLaPista1. Afegeix a la base de dades el ximpanzè cridant el mètode afegeixCiclistaXimpanze(). Procedeix de la mateixa forma tot afegint l'elefant i la persona.

Executa finalment el mètode rodar() de la classe ARodarPerLaPista1. Has d'obtenir la següent sortida per la cònsola:

 
     
 
 
     
 

El programa funciona, tots els artistes han entrat a la pista i l'elefant ha trencat la bicicleta tal qual l'hem ensinistrat.

No cal ni dir, però, els inconvenients del programa que acabes d'escriure: Has necessitat tres ArrayList, tres mètodes per afegir entrades a la base de dades i tres bucles per a treure els artistes a pista. No t'has pogut ni permetre agrupar els animals i gestionar-los junts perquè no tots els animals saben anar en bicicleta.

Com ho podríem fer per a simplificar el programa?

En termes d'herència podríem fer que els ximpanzès, elefants i persones fossin tots descendents d'una nova superclasse, la classe Ciclista. Així podríem fer un tractament agrupat de tots ells. Però, si faig l'elefant fill de Ciclista, l'he de treure de la superclasse Animal. No té sentit. En algun altre moment de l'aplicació necessitaré identificar l'elefant com a animal.

I si pogués fer que l'elefant fos subclasse d'Animal i també subclasse de Ciclista? Així podria incorporar les habilitats de les dues classes mare. Seria la situació ideal. Aquesta capacitat de ser fill de dues mares és coneix com herència múltiple, C++ la incorpora, però... Java no la té!

L'eina que ens proporciona Java per a solucionar problemes com el del nostre programa són les interfícies.

Amb una interfície podré dir-li a Java que tracti als elefants, ximpanzès i persones com a ciclistes per a gestionar-los junts. No importa que no siguin classes amb vincles de parentiu, puc agrupar qualsevol classe! És una meravella, i extremadament fàcil de muntar.

 
     
Creant i Implementant Interfícies  
     
 

Anem a modificar el projecte del circ.

El que farem en primer lloc serà crear la classe SapAnarEnBici. És una classe una mica especial, però molt senzilla. Afegeix-la al teu projecte:

 
     

/**
* @author Angel Solans
* @version 28-05-2005
*/

public interface SapAnarEnBici{
    void vesEnBici();

}

 
     
 

Observa que no escrius public class sinò public interface. Acabes de crear una interfície.

La gràcia d'aquesta classe és que funciona com a maqueta. No fa cap més feina. Només conté una llista amb capçaleres de mètodes, que no tenen cos: observa com el mètode vesEnBici de la interfície no té contingut, només definició: public void vesEnBici();

Ara veuràs la gran utilitat d'aquesta maqueta que acabes de crear.

Obre la classe Persona i fes aquesta modificació:

 
     
/**
* @author Angel Solans
* @version 28-05-2005
*/
public class Persona implements SapAnarEnBici{
    public Persona() {
    }
    public void vesEnBici() {
       System.out.println("Sóc un Home i vaig en bicicleta "+
           "sense mans");
       }
}

 
     
 

amb aquesta expressió, implements SapAnarEnBici, li estàs dient a Java que les persones s'ajusten a la Interface SapAnarEnBici. La única condició que ha de cumplir una classe per a implementar una interfície és la de desplegar tots els mètodes que estan enunciats al cos de la interfície. Una classe pot implementar moltes interfícies, nomes cal anar-les escrivint després de l'expressió implements separades amb comes (implements SapAnarEnBici, SapPujarALaCorda, SapNedar).

Com la nostra interfície anuncia un mètode vesEnBici(), per a què la classe Persona s'hi ajusti ha de tenir un mètode vesEnBici(). Si no el tingués, no podríem compil·lar la classe.

 
     

Repeteix el procediment tot fent que les classes Ximpanze i Elefant implementin l'interfície SapAnarEnBici. Per exemple, la classe Ximpanze l'has de modificar així:

    public class Ximpanze extends Animal implements SapAnarEnBici {

Un cop completis les modificacions hauràs aconseguit que els ximpanzes, els elefants i les persones estiguin vinculades per la pertanyença a la intefície SapAnarEnBici.

La gràcia del procés és que Java pot gestionar agrupadament els objectes que implementen la mateixa interfície, d'una forma similar com ho fa amb classes vinculades per herència.

Ara refaràs la classe ARodarPerLaPista1 per adaptar-lo al nou "parentiu" que hem declarat entre les classes de ciclistes.

Crea una classe que es digui ARodarPerLaPista amb el següent contingut:

 
     
/**
* @author Angel Solans
* @version 28-05-2005
*/

import java.util.ArrayList;
import java.util.Iterator;

public class ARodarPerLaPista {
    ArrayList elsQueRoden;

    public ARodarPerLaPista() {
        elsQueRoden=new ArrayList();
    }

    public void afegeixCiclista(SapAnarEnBici ciclista) {
        elsQueRoden.add(ciclista);
    }

    public void rodar() {
        for (Iterator iterador = elsQueRoden.iterator();
            iterador.hasNext();) {
            SapAnarEnBici artista = (SapAnarEnBici)iterador.next();
            artista.vesEnBici();
        }
    }
}

 
     
  Desa, compil·la i executa. Crea un ximpanze, una elefant i una persona i una instància de la classe ARodarPerLaPista. Afegeix els tres ciclistes a la base de dades amb el mètode afegeixCiclista(). Executa el mètode roda(). La sortida a cònsola és la següent:  
     
 
 
     
 

Observa la clau del nou programa:

Java tracta als objectes que implementen la interfície com si fossin fills de la pròpia interficie.

Per això, puc afegir ximpanzès, persones i animals a través d'aquest mètode, que admet un paràmetre d'entrada SapAnarEnBici.

    public void afegeixCiclista(SapAnarEnBici ciclista) {
        elsQueRoden.add(ciclista);
    }

o puc fer un llistat amb un sol bucle:

        for (Iterator iterador = elsQueRoden.iterator();
            iterador.hasNext();) {
            SapAnarEnBici artista = (SapAnarEnBici)iterador.next();
            artista.vesEnBici();
        }


perquè faig un casting de qualsevol objecte a SapAnarEnBici. En el moment que creo l'objecte artista a partir de la interfície SapAnarEnBici, puc executar tots els mètodes declarats al cos de la interfície. No podria, però, executar els mètodes particulars de cada objecte.

 

 

1) Crea un objecte de la classe Cavall i intenta afegir-lo a la llista de ciclistes de la instància de la classe ARodarPerLaPista. Com et contesta el BlueJ?. Perquè es produeix l'error?

2) Crea una segona interfície que es digui SapPujarALaCorda. Aquesta interfície ha de declarar el mètode pujaALaCorda(). Fes les modificacions pertinents a les classes Ximpanze i Persona per a que implementin aquesta interfície. Quines dues coses has d'afegir a aquestes classes per a què la implementació de la nova interfície sigui possible?

 
     

La utilització d'interfícies a la programació Java és una eina extremadament potent que no has de dubtar en utilitzar. Crea sempre les teves pròpies interfícies en aquelles situacions d'agrupació d'objectes que no estan vinculats pel parentiu.

 
     
Algunes característiques de les Interfícies  
     
 

Aquí et deixem algunes de les coses que has de tenir en consideració sempre que utilitzis Interfícies:

  • Una interfície pot extendre altres interfícies , i no només una sola. Això seria correcte:

    public interface Constantsutils extends
         ConstantsPetites, ConstantsGrans {
         public final double ALTURA = 3.15;
         public final int RADI = 34;
    }

  • Una interfície no pot extendre classes .
  • Una interfície hereta tots els mètodes i constants de les superinterfícies , excepte si els sobreescriu ( override ).
  • Tots els mètodes declarats en una interfície són, per defecte, public abstract . No es permeten altres tipus com private o protected .
  • Com que els camps ( fields ) en una interfície són automàticament static i constants , la interfície ens anirà bé per a la creació de grups de valors constants:

    public interface Setmana {
         int DILLUNS = 1, DIMARTS=2, DIMECRES=3,
             DIJOUS=4, DIVENDRES=5, DISSABTE=6, DIUMENGE=7;
    }

  • Tot i ser constants, els camps ( fields ) de les interfícies es poden inicialitzar amb valors no constants, valors que prendran el primer cop que es carregui una classe que implementi la interfície :

    public interface Atzar {
        int enter = (int)Math.random()*1000);
    }

 
     
Les classes abstractes  
     
 

Java posa a la nostra disposició altres tipus de classes pensades per a incentivar dissenys de programa sòlidament estructurats tot aprofitant els avantatges de l'herència. Una nova eina, complementària a les interfícies, són les classes abstractes.

Una classe abstracta és com una maqueta buida, a l'estil d'una interfície, però en aquest cas pensada per extendres, per construir classes filles, al contrari que una interfície, que està pensada per a què pugui ser implementada per classes no vinculades pel parentiu.

Una classe abstracta és, doncs, una classe base buida que:

  • Només presenta al programador un conjunt d'atributs i mètodes. Aquests mètodes només tenen el nom, no estan desplegats. Les classes filles d'aquesta classe abstracta són les encarregades de desplegar els mètodes, d'una forma similar a com ho fan les classes que implementen una interfície.
  • No es pot instanciar mai. Les classes abstractes estan pensades només per a tenir classes filles, no pot existir un objecte concret d'una classe abstracta.

Amb un exemple es veu més clar. Observa el següent diagrama UML de tres classes vinculades per l'herència:

 
     
 
 
 
Diagrama UML d'una classe abstracta i dues classes filles
 
     
  Tens una classe abstracta Instrument que té dos mètodes abstractes afinam() i tocam() i dos classes filles GuitarraElectrica i Bateria cadascuna amb els corresponents afinam() i tocam() .  
     
Crea un projecte BlueJ a la teva carpeta de projectes i dóna-li de nom "Banda". Ha de contenir la classe abstracta Instrument i les dues classes filles. Talla i empega el següent codi:  
     
/**
* @author Angel Solans
*
* @version 30-06-2005
*
*/

public abstract class Instrument {
    public abstract void afinam();
    public abstract void tocam();
}
 
     
 

Instrument és una classe abstracta. Observa que

  • l'hem etiquetada com a abstract.
  • La classe té dos mètodes que també són abstractes, afinam() i tocam(). Després d'escriure el nom del mètode, no hem posat la parella de claus {} per circumscriure el cos del mètode. Els mètodes abstractes només tenen nom.
 
     
  Ara escriu les classes filles:  
     
/**
* @author Angel Solans
*
* @version 30-06-2005
*
*/

public class GuitarraElectrica extends Instrument {

    public void afinam() {
        System.out.println("Afinant la guitarra ... nyec, nyec! ");
    }
    public void tocam() {
        System.out.println("Tocant la guitarra .. nyec, nyec!");
    }

}

 
     
/**
* @author Angel Solans
*
* @version 30-06-2005
*
*/

public class Bateria extends Instrument {

    public void afinam() {
        System.out.println("Afinant la bateria ... boom, boom! ");
    }     
    public void tocam() {
        System.out.println("Tocant la bateria .. boom, boom!");
    }
}

 
     
 

Cadascuna de les dues classes filles implementa de la seva manera particular els dos mètodes afinam() i tocam().

Finalment, posa el grup musical a fer un concert:

 
     
import java.util.ArrayList;
import java.util.Iterator;

/**
* @author Angel Solans
*
* @version 04-jul-2005
*
*/

public class TocarBanda {


    public void toca() {
       
ArrayList instruments = new ArrayList();

        instruments.add(new GuitarraElectrica());
        instruments.add(new Bateria());

        for(Iterator iter=instruments.iterator();iter.hasNext();) {
            Instrument instrument = (Instrument)iter.next();
            instrument.tocam();
        }
    }
}

 
     
 

Crea una instància de la classe TocarBanda i executa el mètode toca(). Observa la sortida de la cònsola.

En el programa has creat un ArrayList d'instruments que, a través d'un iterador, van executant el seu mètode tocam().

Quan treballis amb classes abstractes has de tenir en consideració que

  • Tota classe que tingui un o més mètodes abstractes, s'ha de qualificar d'abstracta.
  • Es possible crear una classe abstracta sense incloure-hi cap mètode abstracte. Aquesta estratègia pot ser útil quan intentis evitar que una classe qualsevol pugui ser instanciada.
  • Les classes i mètodes abstractes són útils perquè indiquen molt clarament al programador quina estructura ha de tenir necessàriament una classe filla.
 
     

3) Afegeix un tercer instrument a la banda -per exemple l'instrument Baix-. Afegeix el mètode afinam() però no escriguis cap mètode tocam(). És possible fer la compil·lació de la classe? Perquè?

4) En el mètode toca() crea una instància de la classe Instrument:

Instrument instrument = new Instrument();

Desa i compil·la. Perquè el compil·lador et retorna un missatge d'error?

 
     
 

 

Tornar al principi