![]() |
Pràctica 2: Herència. La biblioteca escolar amb herència ![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Els problemes del programa de biblioteca sense herència |
||
Ets capaç de veure quins defectes de disseny té? |
||
La primera sensació que tens al revisar la classe és la presència de massa codi duplicat. Tot i l'escassa capacitat operativa del programa, trobem més d'una situació de duplicació de codi:
|
||
El programa tampoc resulta fàcil de mantenir. Imagina que et demanen afegir a la base de dades els DVDs, Videos o qualsevol altre recurs. L'impacte sobre el programa serà enorme. A més de crear les noves classes, hauràs de modificar completament la classe Biblioteca: et serà necessari sumar un tercer o quart mètode d'entrade de dades (afegeixDVD(), afegeixVideo(), etc), hauras de preveure nous ArrayList i a la impressió hauràs d'afegir nous Iterator. Si els efectes de la modificació són sensibles en un programa com el que has fet, mínimament funcional, imagina com podria seria en un programa complet posat en producció. |
||
Ara veuràs com les capacitats d'herència de Java t'ajudaran a resoldre amb elegància i simplicitat tots aquest problemes. | ||
El concepte d'herència |
||
La idea d'herència és força senzilla: intenta pensar en els objectes de Java en termes d'organismes biològics. Imagina que un objecte de Java pot tenir fills i que aquests fills hereten l'aspecte físic i les habilitats de la mare. Així és com funciona l'herència en els llenguatges de programació moderns: una classe mare pot tenir una classe filla i aquesta classe filla heretarà els camps i mètodes de la classe mare. Tots els fills d'una classe mare compartiran, d'entrada, aquest fons comú de camps i mètodes. Tot allò que sap fer la mare ho saben fer les classes filles. Qualsevol canvi en un mètode de la classe mare s'incorpora automàticament a les classes filles. |
||
En termes del projecte de biblioteca, oi que els llibres i les revistes de la biblioteca tenen moltes coses en comú? Tantes que podriem dir que són filles de la mateixa mare? Ambdues són recursos o Items de la biblioteca i tenen un codi d'inventari, un codi de classificació decimal, un número de pàgines, una ubicació a la biblioteca i tants altres elements d'nformació compartits. |
||
Java ens permet expressar amb facilitat aquesta relació de parentiu entre els objectes. Observa i estudia el diagrama següent. Podem crear un objecte Item que és la classe mare a la que pertany qualsevol recurs de la biblioteca, ja sigui un llibre, una revista, un video, un CDRom, un DVD o qualsevol altre format de recurs que pugui aparéixer d'aquí un temps. Després, les classes concretes ( Llibre, Revista, DVD, etc,) les podem crear com a descendents de la classe Item: |
||
![]() |
||
Nova jerarquia d'objectes de l'aplicació de biblioteca |
||
1) Hem creat una classe nova que es diu Item. Aquesta classe conté els camps i mètodes que comparteixen tots els recursos de la biblioteca. Tots els items reben una classificació decimal determinada, poden ser comentats, es van crear en una data determinada per una editorial determinada, tenen un títol i pertanyen a un gènere concret. Fixa't en que els camps de la classe Item són el que poden compartir tots els recursos. Els camps específics d'un llibre o una revista, no apareixen aquí. També afegirem a aquesta classe els mètodes corresponents a accions que afecten a tots els tipus de recursos. En algun moment o altre haurem de llistar els objectes de la biblioteca, per tant, tots els recursos comparteixen el mètode imprimir(). També el crearem a la classe Item. 2) Hem creat dues classes descendents d'Item. Són les classes Llibre i Revista. Com són filles d'Item, hereten els camps i mètodes de la classe mare. Només ens caldrà afegir els camps i mètodes particulars (en el cas dels llibres afegim el camp autor, en el cas de les revistes el camp numero, per a recollir el número de la revista). |
||
Superclasse i subclasse |
||
Quan una classe té classes filles que extenen la seva funcionalitat es diu que aquesta classe és una superclasse. Quan una classe és filla d'una altra classe, es diu que és subclasse de la classe mare. La classe Item és superclasse de les classes Llibre i Revista. Les classes Llibre i Revista són subclasses de la classe Item. |
||
![]() |
La profunditat de l'herència entre classes pot ser tan complexa com t' interessi, Java soportarà tantes subclasses com siguis capaç de crear. A l'hora de pensar la jerarquia d'herència de la teva aplicació, però, has d'intentar sempre crear una estructura que sigui comprensible. Això vol dir que has de procurar no crear jerarquies massa profundes que facin il·legible el programa. Pensa que les classes del paquet estandar de Java ràrament tenen una profunditat superior a quatre esglaons d'herència (besavia - avia - mare - filla ). | |
![]() |
Abans de revisar el codi de l'aplicació de biblioteca, jugaràs una mica a crear jerarquies d'herència. Observa la següent jerarquia de classes: |
|
![]() |
||
Es tracta d'una petita classificació dels vehicles: Tots els vehicles amb motor d'explosió comparteixen uns elements amb els vehicles elèctrics. Automòbils i Motocicletes comparteixen característiques com a vehicles impulsats per motors d'explosió i tant les motos de motocros com les de carretera són motocicletes. | ||
1) De l'institut: Professor, alumne, delegat de classe, cap d'estudis, director, bibliotecari, administratiu. 2) De la informàtica: Portàtil, sobretaula, impressora, escàner. 3) De la natura: Gat, gos, cèrvol, siamès, lluç, sardina, tonyina. |
||
Et sents ja còmode amb les jerarquies? Doncs revisa ara com es gestiona l'herència des del llenguatge Java. |
||
![]() |
Crea un nou projecte BlueJ a la teva carpeta de projectes. Dóna-li el nom de "bibliotecaherencia". Escriu en primer lloc la classe "mare" de tots els recursos, els items de la biblioteca: |
|
![]() |
/** * @author Angel Solans * @version 24/04/2005 * */ public class Item { public Item(String titol, String data) { } |
|
Observa la simplicitat de la classe: En primer lloc recollim tots els camps comuns a qualsevol recurs de la biblioteca. Seguidament creem un constructor mínim que inicialitza el títol del recurs i la data d'edició i finalment afegim un mètode void imprimir() que no fa absolutament res. Segurament et cridaran l'atenció dos aspectes del codi:
Ara et satisfarem la primera curiositat, la segona te l'explicarem una mica més avall. |
||
Visibilitat entre classes amb relació d' herència (camps i mètodes protected) |
||
Com ja saps, els camps i mètodes etiquetats com a public són visibles dins la classe i des de l'exterior de la classe. Els camps i mètodes private, en canvi, només són visibles des de l'interior de la pròpia classe i mai des de fora. |
||
Com encaixa aquest model amb l'herència? Les classes filles poden veure els camps i mètodes privats de les classes mare? La resposta és NO. Si tenim una classe mare que té camps marcats com a privats, aquests camps no són visibles a les classes filles. Per a obtenir i modificar els seus valors necessitarem utilitzar mètodes accessors, com qualsevol altra classe. Si tenim, en canvi, camps públics a la classe mare, les classes filles els poden utilitzar com a camps propis. |
||
L'etiqueta protected s'ha creat per a fer possible que les classes lligades per l'herència puguin accedir als camps i mètodes de la classe mare. | ||
Marcant un camp d'una classe com a protected aconseguim que sigui visible a totes les classes descendents però completament invisible a les classes no vinculades per herència. | ||
Observa la classe Item, al declarar el camp titol com a protected, aconseguim que les classes Llibre o Revista el vegin i puguin utilitzar com a propi mentres que queda ocult a la resta de classes. | ||
![]() |
Escriu ara les dues classes filles d'Item, les classes Llibre i Revista: | |
![]() |
/** * @author Angel Solans * @version 14/04/2005 */ public class Llibre extends Item { } |
|
![]() |
/** public class Revista extends Item { private String numero; public void imprimir() { } |
|
Compara la classe que acabes d'escriure amb l'equivalent de la primera versió del programa: | ||
/** * @author Angel Solans * @version 13/04/2005 */ public class Revista { private String numero; public Revista(String titol, String numero, String data ) { } |
||
El codi s'ha simplificat considerablement. Han desaparegut la major part dels camps perquè ambdues classes prendran com a seus els camps comuns continguts a la classe mare Item. Repassa ara les novetats del codi: 1) Declaració de l'herència (extends) . Observa el capçal de les classes: amb l'expressió extends Item estem dient a java que la classe Llibre és filla de la classe Item. A partir d'aquí, les classes i mètodes public o protected de la classe mare Item poden ser utilitzades com a pròpies per la classe filla Revista. - Busca en el constructor de la classe Llibre, un exemple d'utilització a la classe filla d'un camp de la classe mare. Efectivament, en el constructor de Llibre: utilitzem el camp editorial, que no està definit a la classe filla Llibre sinó a la classe mare Item. 2) Inicialitzacions d'objectes i herència (super) . Observa la següent expressió en el constructor de la classe Llibre: public Llibre(String autor, String titol, aquesta crida a super(titol,data) és una crida al constructor de la classe Item. Quan creem una classe que és subclasse d'una altra i aquesta superclasse té un constructor parametritzat o té més d'un constructor, hem de cridar explícitament el constructor que ens interessa des del constructor de la subclasse . Si no ho fem així, Java intentarà fer una inicialització automàtica. Tot i que no és imprescindible cridar el constructor de la superclasse si aquest no està parametritzat, és un bon costum posar com a primera expressió del constructor d'una subclasse la crida al constructor de la superclasse. Crea una nova versió de la classe Biblioteca. Ha de tenir el següent aspecte: |
||
![]() |
import java.util.Iterator; import java.util.ArrayList; /** * * @author Angel Solans * @version 14-04-2005 */ public class Biblioteca { private ArrayList items; public Biblioteca() { items = new ArrayList(); } public void afegeixItem(Item item) { items.add(item); } public void imprimir() { for (Iterator iterador = items.iterator(); iterador.hasNext();) { Item item = (Item)iterador.next(); item.imprimir(); } } } |
|
2) Reduïm els mètodes d'inserció de dades a un de sol. En lloc de necessitar els mètodes afegeixLlibre(), afegeixRevista(), afegeixDVD()... només necessitem un sol mètode, el mètode afegeixItem(). Com tant les revistes com els llibres són Items, aquest mètode accepta per igual la inserció d'instàncies de la classe Llibre com instàncies de la classe Revista o qualsevol subclasse d'Item. 3) Reduïm els bucles d'impressió a un de sol. Ja no és necessari fer un bucle per a cada tipus de recurs sinó que, com tots els objectes desats a l'ArrayList són Items, es poden recórrer junts. |
||
Sobreescriptura de mètodes |
||
Aquesta simplificació l'hem obtingut sense afegir gairebé cap novetat significativa en el codi del programa. Només és ressenyable, segurament, la utilització del mètode imprimir(). Has observat que la classe Item té un mètode imprimir() i cadascuna de les seves subclasses també? Quin sentit té aquesta duplicitat de defincions (sobreescriptura en termes de Java)? Per a comprovar-ho executa el programa. Crea instàncies de llibres i revistes i afegeix-los a la base de dades. Finalment executa el mètode imprimir() de la classe Biblioteca. Revisa la sortida a la cònsola, té el següent aspecte, idèntic al programa de la primera versió: |
||
![]() |
||
Si es dona el cas que una subclasse d'Item no defineix cap mètode imprimir(), el programa farà la impressió a través del mètode imprimir() de la classe mare. L'estratègia és senzilla i eficaç: java intentarà executar sempre el mètode de la subclasse, si no el troba, executarà el mètode de la superclasse. Com pots observar, la sobreescriptura de mètodes dóna una gran versatilitat a la programació. |
||
![]() |
Verifica que passa si la classe Revista no sobreescriu el mètode imprimeix(). Esborra aquest mètode de la classe Revista, compil·la i executa el programa afegint uns quants objectes dels dos tipus. Quin mètode imprimir() s'executa ara quan es llista un objecte de tipus Revista? | |
Polimorfisme |
||
En aquest mètode de la classe Biblioteca public void imprimir() {
|
||
Assignació de variables i casting utilitzant l'herència |
||
Per defecte, sempre que assignem un objecte a una variable, creem l'objecte del mateix tipus que la variable. Per exemple, si creem un objecte Persona procedim així: En aquesta jerarquia d'herència: |
||
![]() |
||
És possible fer inicialitzacions convencionals: però també ens pot interessar inicialitzar des de la superclasse cap a una subclasse: En aquesta situació, unaPersona i unaSegonaPersona són objectes de la mateixa classe, del tipus Persona. Podem manipular-los junts en molts contextes de programació. Però, de fet, un d'aquests objectes contindrà un Home i l'altre una Dona. El camí contrari NO és posible. No es pot inicialitat des de la subclasse cap a la superclasse: De forma similar, i seguint el mateix criteri d'inicialitzar només cap a les subclasses, has de vigilar a l'hora de fer casting entre superclasses i subclasses. Només pots fer conversions d'una superclasse a una subclasse. Mai podràs fer una conversió d'una subclasse a un superclasse ni d'una subclasse a una altra subclasse. Tot plegat és una mica complicat. Repassa-ho a través d'aquest petit repte. Observa les següents declaracions i assignacions. Et proposem dues assignacions correctes i dues d'incorrectes. Intenta identificar-les abans de continuar llegint: Persona unaPersona;
No desesperis si et costa una mica sentir-te segur amb les conversions. Continua practicant: |
||
![]() |
1) Comprova la certesa d'aquestes assignacions i conversions a partir de les classes del teu projecte: Item unItem = new Item(); a) Item unItem = new Revista(); b) Revista unItem = new Item(); Considerant c) Item item = llibre; d) llibre = (Llibre) item; e) llibre = (Llibre) revista;
2) Afegeix el mètode públic getter getTitol() a la classe mare Item. Comprova si s'incorpora a les classes filles tot creant un objecte Llibre i executant en ell dit mètode getTitol(). 3) Revisa alguns dels conceptes que has après en aquesta pràctica. Intenta definir que vol dir: superclasse, subclasse, protected, super(), sobreescriptura de mètodes, polimorfisme. 4) Per a concloure amb el projecte, intenta modificar-lo per a què funcioni tot afegint una nova subclasse a la superclasse Item. Crea una subclasse que es digui DVD am |