Enrera
Mòdul 4
Iniciació a la programació en Java
  Pràctica
1
2
3
4
   
Exercicis
Exercicis
 
 
  L'herència
   
  Avantatges de l'herència
   

Quan Java va néixer, els dissenyadors de llenguatges buscaven solucions per a fer la programació més eficaç, més ràpida i més barata. L'antecedent eren els llenguatges procedurals com ara C. En aquell context,amb la finalitat d'aprofitar el codi de programes anteriors, es copiaven les llibreries ja escrites i s'adaptaven les funcions que contenien a les noves necessitats. Funcionava, malgrat ser poc pràctic.

Es va pensar en l'herència com una estratègia per a facilitar la reutilització de codi. A Java, s'aprofita el codi creant classes noves, però no sobre el buit, sinó sobre altres classes que, en molts casos, no cal ni tocar perquè s'adaptin a les noves necessitats. La idea és simple: tenim un objecte que fa una tasca i necessitem una adaptació per a resoldre un problema nou o diferent. No tenim la necessitat obligatòria de fer una classe completament nova, sinó que estenem la que tenim a una classe filla que incorpora noves funcionalitats o en reescrivim (override) les de la classe mare.

L'herència no és tampoc una solució màgica per a tot. De fet, exigeix al programador una bona dosi de planificació abans de posar-se a escriure codi: les virtuts de les classes es propaguen a les classes filles, però també els seus defectes. Un projecte Java amb una planificació deficient o incorrecta pot convertir l'experiència de codificació en un suplici. El consell és simple: repenseu-vos sempre amb molta cura la forma i continguts que doneu a les vostres classes.

A partir d'aquesta pràctica aprendrem a manipular algunes de les possibilitats de l'herència en Java. Aprendrem a crear classes filles que seran capaces d'ampliar o modificar les capacitats de les classes mares i els donarem plasticitat a través de la sobrecàrrega de mètodes i de constructors.

   
  Superclasses i Subclasses:
   
 

La definició de l'herència és molt simple. Si escric una classe nova que és descendent d'una classe que ja existeix només he de dir:

class Nova extends Vella {

    ...

}

Els membres no privats de la classe mare passen a ser automàticament membres de la classe filla. La classe mare és la superclasse i la filla és una subclasse.

   
  Imaginem un centre escolar com un edifici que agrupa diferents categories d'espais físics. Una expressió simplificada d'aquesta realitat seria aquesta:
 
 

Qualsevol espai del centre és un "objecte" Espai, però no tots els espais es descriuen de la mateixa forma: la cafeteria té peculiaritats que no té una aula genèrica o el despatx d'un departament. El gràfic interpreta de forma jeràrquica les categories d'espais;disposem d'aules i d'espais de treball dels professors. Les aules poden ser un gimnàs, aules d'informàtica o laboratoris.

Aquesta estructura d'arbre es pot traduir fàcilment a diferents classes vinculades per herència, on la superclasse de totes és "Espai". Un espai qualsevol té un codi que l'identifica, una superfície, disposa o no de llum natural, té un consum elèctric particular, etc. Un despatx a més, té taules per als professors o màquina de cafè. Aquest instrument no està instal·lat a les aules, les quals, en canvi, tenen un aforament màxim o disposen o no de cadires de pala.

Si codifiquem tot això podria quedar així: escriviu cadascuna d'aquestes classes en el mateix fitxer:

   

class Espai {

    String codi;
    int metresquadrats;
    boolean llumnatural;
    int consumelectric;

    public Espai() {
    }

    public Espai(String codi, int metres, boolean llum, int consum) {
        this.codi = codi;
        this.metresquadrats=metres;
        this.llumnatural=llum;
        this.consumelectric=consum;
    }

    public double consumM2() {
        if (metresquadrats>0)
            { return consumelectric/metresquadrats; }
        else  { return 0; }
    }

}

 

class Aula extends Espai {

    boolean cadiresdepala;
    boolean connectorsxarxa;
    int places;
    int ordinadors;

    public Aula() {
    }

    public Aula(String codi, int metres, boolean llum,
                int consum, int places) {
        super(codi,metres,llum,consum);
        this.places=places;
    }

    public double espaiEstudiant() {
            if (places>0) {
                return metresquadrats / places;
            } else {
                return 0;
            }
    }

    public double espaiEstudiant(int m, int p) {
        this.places = p;
        this.metresquadrats = m;
            if (places>0) {
                return metresquadrats / places;
            } else {
                return 0;
            }
    }

}

 
class Despatx extends Espai {

    int taules;
    boolean maquinadecafe;

    public Despatx() {
    }

    public Despatx(String codi, int metres,
                   boolean llum,int consum) {
        super(codi,metres,llum,consum);
    }

}

 
class Laboratori extends Aula {

    int bunsens;

    public Laboratori() {
    }

    public Laboratori(String codi, int metres, boolean llum,
                      int consum, int places) {
        super(codi,metres,llum,consum,places);
    }

}
 
class Informatica extends Aula {
    int impressores;

    public Informatica() {
    }

    public Informatica(String codi, int metres, boolean llum,
                       int consum, int places) {
        super(codi,metres,llum,consum,places);
    }

}
 

class Gimnas extends Aula{

    boolean dutxes;
    int grades;

    public Gimnas() {
    }

    public Gimnas(String codi, int metres, boolean llum,
                                       int consum, int places) {
        super(codi,metres,llum,consum,places);
    }

    public double espaiEstudiant() {
        if (places>0) { return (metresquadrats+grades) / places; }
            else { return 0; }
    }

}

   
  La classe Espai disposa de quatre variables (codi de l'espai, superfície, disponibilitat de llum natural i consum elèctric per hora). Aquestes variables estaran disponibles per a qualsevol de les seves subclasses, cadascuna de les quals també disposarà del mètode public double consumM2(), un senzill mecanisme de càlcul que ens permet saber quanta electricitat es consumeix en aquest espai per m2. A més, la classe disposa de dos constructors, un que inicialitza les quatre variables i un altre que no n'inicialitza cap.

D'aquesta superclasse hereten les classes Aula i Despatx. Per a les aules hem previst una utilitat específica, que no necessitarem per les oficines o departaments: la disponibilitat d'espai per a cada plaça d'estudiant (mètode public double espaiEstudiant()). Pels espais "Despatx" no hem previst cap càlcul específic, només el còmput de taules i màquines de cafè.

Finalment, definim dues classes que hereten de la classe Aula, les classes Laboratori i Gimnas. "Laboratori" ens servirà per especificar les particularitats d'aquest tipus d'espai: en el nostre cas, un comptador de bunsens. En el cas de Gimnas, tot i ser una Aula, descobrim que el mètode de la superclasse espaiEstudiant() no ens serveix, perquè la superfície de la instal·lació està dividida entre la pista i les grades. Per això n'hem creat un mètode de càlcul específic: hem sobreescrit (override) un mètode de la superclasse.

Ara escriurem un programa per calcular la distribució del consum elèctric al nostre centre i la disponibilitat d'espai per estudiant a cada aula:

   

/**
* Càlcul de consum elèctric i espai de les aules de l'institut
*
*/


class CalculAules {

    public static void main(String args[]) {

        // Creem un despatx
        Despatx d1 = new Despatx("Oficina 1",40,false,600);
        d1.maquinadecafe=true;

        // creem un laboratori
        Laboratori a1 = new Laboratori("Laboratori 1",
                                       75,true,1200,20);
        a1.bunsens = 12;

        // creem una aula d'informàtica
        Informatica a2 = new Informatica("Informàtica 1",
                                         75,true,6000,15);
        a2.impressores = 3;

        // creem un gimnàs
        Gimnas a3 = new Gimnas("Gimnàs",500,true,3000,200);
        a3.grades = 2000;

        // Consum d'electricitat
        Espai[] espais = {d1,a1,a2,a3};
        System.out.println("Consum elèctric");
        System.out.println("---------------------------------");
        double consumtotal=0;
            for (int n=0; n<espais.length; n++) {
                consumtotal+=espais[n].consumelectric;
                System.out.println("L'espai "+
                                   espais[n].codi+" consumeix: "+
                                   espais[n].consumM2()+
                                   " W/m2");
            }
        System.out.println("El consum total de l'institut és de: "+
                           consumtotal+
                           " W\n\n");

        // Càlcul de superfícies per estudiant
        Aula[] aules = {a1,a2,a3};
        System.out.println("Superfície per estudiant");
        System.out.println("---------------------------------");
            for (int n=0; n<aules.length; n++) {
                System.out.println("L'Aula "+
                                   aules[n].codi+
                                   " disposa de: "+
                                   aules[n].espaiEstudiant()+
                                  " m2 per estudiant");
            }
    }
}

   
 

El càlcul de l'electricitat de les aules forma part de la superclasse "Espai". Podem cridar el mètode public double consumM2() des de qualsevol subclasse. Les aules disposen d'un mètode específic public double espaiEstudiant(), que podem cridar des de qualsevol objecte d'aquesta classe. Els objectes "Gimnas" tenen lleugerament canviat el sistema de càlcul dels espais.

Segons el llenguatge d'objectes en el programa hem utilitzat:

  • Sobrecàrrega de mètodes constructors i d'instància. Totes les classes disposen de dos constructors, un amb paràmetres i l'altre sense. Podem utilitzar els dos indistintament. El compilador s'encarregarà de decidir quin dels dos ha d'utilitzar quan creem un objecte de la classe. Buscarà aquell mètode que s'adeqüi exactament a l'estructura de paràmetres que passem. Podem sobrecarregar els mètodes tantes vegades com vulguem, sempre que la distribució de paràmetres sigui única i, si es donen repeticions, el compilador ens donarà error. Una sobrecàrrega com aquesta no seria correcta:

    class Aula extends Espai {

        public Aula(int metres) {
            ...
        }

        
    //Repetim constructor amb un únic paràmetre enter,
        //No es pot fer!

        public Aula(int places) {
            ...
        }

    }

    Hem utilitzat sobrecàrrega de mètodes d'instància a la classe Aula, on permetem dues formes diferents de càlcul de la superfície disponible per estudiant.

  • Sobreescriptura de mètodes: moltes vegades és interessant conservar el nom i tipus de retorn dels mètodes a les subclasses, però definint-ne un funcionament intern diferent. Nosaltres hem aplicat aquest criteri a la classe Gimnas. Els objectes Gimnas disposen d'un mètode public double espaiEstudiant() aparentment igual a la resta d'aules. El mecanisme de càlcul del mètode, però, no és el mateix. Com hem vist, la diferència queda encapsulada i es fa invisible per als programes que utilitzin les aules.

  • Hem cridat al constructor de la superclasse amb l'expressió super(). Si la superclasse disposa de més d'un constructor, com podem decidir quin d'ells cridarem a l'hora l'instanciar les subclasses? super() ens permet accedir als constructors de la superclasse seguint un criteri similar a la sobrecàrrega de mètodes: en funció dels paràmetres que definim a super() s'activarà un o altre constructor.
 

 

 
Amunt