Temas:
Por ejemplo, todos los glyphs tienen operaciones comunes como getX, getY y moveTo. Además, desde el punto de vista de representación, todos tienen un punto de referencia. Estas operaciones se definen normalmente en la clase:
en la clase:
Los métodos getX, getY y moveTo son los métodos que se usaron la
clase pasada. Son métodos definidos para el usuario de esta biblioteca
de clases. En cambio el método draw nunca será usado por el usuario
de la biblioteca. Este método se coloca en la clase Glyph para poder
implementar la clase Animator. Esta clase mantiene un arreglo con todos
los glyphs puestos en la ventana. Cuando llega el momento de dibujar
todos estos glyphs, basta ejecutar el siguiente código:
class Glyph extends Program {
int x, y; // el punto de referencia
// las operaciones comunes a todos los glyphs
int getX() {
return x;
}
int getY() {
return y;
}
void moveTo(int x, int y) {
this.x= x;
this.y= y;
}
void draw(Pizarra p) {
}
}
en donde glyphs es el arreglo de objetos de la clase Glyph y
p es un objeto de la clase Pizarra (representa la ventana).
int i= 0;
while (i<numGlyphs) {
glyphs[i].draw(p);
i= i+1;
}
Es importante destacar que el usuario utilizará finalmente las subclases de Glyph, es decir Box, Ellipse y Text, y no Glyph propiamente tal. Sin embargo, es útil definir la clase Glyph, porque es el factor común que permite simplificar el uso y la definición de las subclases. El programador de las subclases usará Glyph como un molde, preocupándose solamente de los aspectos específicos de las subclases.
Explicaciones:
class Box extends Glyph {
int w; // nuevas variables de instancia
int h;
// el constructor
Box(int x, int y, int w, int h) {
this.x= x;
this.y= y;
this.w= w;
this.h= h;
}
// redefinición de métodos
void draw(Pizarra p) {
p.drawRect(x, y, x+w, y+h);
}
// nuevos métodos
void setDim(int w, int h) {
this.w= w;
this.h= h;
}
}
En Java todos los objetos pertenecen a la clase Object.
Una subclase hereda de su clase base:
Como la clase base ha heredado las variables de instancia y métodos de su propia clase base, entonces la subclase también las hereda. Es decir, la herencia también es transitiva.
Por ejemplo, la clase Program define los métodos println, readLine, readInt, etc. Estos métodos son heredados por la clase Glyph y por lo tanto por la clase Box.
Observación importante: la subclase no hereda los constructores.
Los métodos redefinidos se distinguen de la definición
de nuevos métodos porque poseen el mismo nombre, número y
tipo de paramétros que un método de la clase base (que eventualmente
fue heredado de otra clase).
void draw(Pizarra p) {
p.drawRect(x, y, x+w, y+w);
}
Enlace dinámico
En el siguiente código:
¿Qué método se invoca? ¿El que se definió en la clase Glyph?
¿El que se definió en la clase Box?
Pizarra p= ...;
Glyph gl= new Box(100, 200, 20, 30);
...
gl.draw(p);
La respuesta es que se invoca el de la clase Box.
En el código que dibuja todos los glyphs del animator:
Los glyphs pertenecerán a distintas subclases: Box, Ellipse y Text.
Este código invoca el método que se ha redefinido en cada una de estas
subclases. En realidad el método draw definido en la clase Glyph
nunca será invocado porque no existirán objetos que sean exactamente
de la clase Glyph.
int i= 0;
while (i<numGlyphs) {
glyphs[i].draw(p);
i= i+1;
}
Aún así es necesario declarar el método draw en la clase Glyph para indicar que es un método común a todas las subclases.
La desventaja es que estos métodos no pueden ser invocados
a partir de variables que sean de tipo Glyph:
void setDim(int w, int h) {
this.w= w;
this.h= h;
}
Box box= new Box( ... );
Glyph gl= new Box( ... );
...
box.setDim( ... ); // Ok
gl.setDim( ... ); // error!
Esto se puede lograr gracias al concepto de subclases. La idea consiste en definir una clase que encapsule el algoritmo de ordenamiento En esta clase se dejan pendiente las operaciones de las cuales depende el algortimo de ordenamiento. La metodología toma real sentido cuando se definen susclases que redefinen las operaciones faltantes.
La examinar los distintos algoritmos de ordenamiento se puede observar que la parte dependiente del tipo de arreglo se encuentra en:
class Ordenador extends Program {
void seleccion(int n) { // n: número de elementos en el arreglo
for (int i= 0; i<n; i++) {
// Buscamos la posicion del minimo en a[i], a[i+1], ..., a[n-1]
int k= i;
for (int j= i+1; j<n j++) {
if (comparar(j, k)<0)
k= j;
}
// intercambiamos a[i] con a[j]
intercambiar(i, j);
}
}
int comparar(int i, int j) {
return 0;
}
void intercambiar(int i, int j) {
}
}
Para ordenar un arreglo se debe crear una instancia de la
clase OrdenadorInt y luego invocar el método seleccion:
class OrdenadorInt extends Ordenador {
int[] a;
OrdenadorInt(int[] a) {
this.a= a;
}
int comparar(int i, int j) {
if (a[i]<a[j])
return -1;
if (a[i]>a[j])
return 1;
return 0;
}
void intercambiar(int i, int j) {
int aux= a[i];
a[i]= a[j];
a[j]= aux;
}
}
La dos últimas instrucciones se pueden abreviar en una sola:
int[] a= new int[10];
a[0]= ...;
a[1]= ...;
...
OrdenadorInt ord= new OrdenadorInt(a);
ord.seleccion(10);
new OrdenadorInt(a).seleccion(10);
Ejercicio: arreglos de strings El arreglo nombres contiene n nombres que deben ordenarse según el orden lexicográfico. Para realizar el ordenamiento se define una subclase de Ordenador:
Para ordenar el arreglo de nombres basta ejecutar:
class OrdenadorString extends Ordenador {
String[] s;
OrdenadorString(int[] s) {
this.s= s;
}
int comparar(int i, int j) {
if (s[i]<s[j])
return -1;
if (s[i]>s[j])
return 1;
return 0;
}
void intercambiar(int i, int j) {
String aux= s[i];
s[i]= s[j];
s[j]= aux;
}
}
new OrdenadorString(a).seleccion(10);
Se dispone de un arreglo de n personas. Para ordenar este
arreglo lexicográficamente primero por apellidos y luego por nombre
se define la siguiente subclase:
class Persona extends Program {
String nombres;
String apellidos;
int edad;
double peso;
...
}
Ahora se desea ordenar el mismo arreglo por edad. Como la clase
OrdanedorPersona ya incluye la definición adecuada del arreglo y
el intercambio se puede definir una subclase de OrdenarPersona:
class OrdenadorPersona extends Ordenador {
Persona[] personas;
OrdenadorPersona(Persona[] personas) {
this.personas= personas;
}
int comparar(int i, int j) {
int cmp= compare(personas[i].apellidos, personas[j].apellidos);
if (cmp<0)
return -1;
if (cmp>0)
return 1;
cmp= compare(personas[i].nombres, personas[j].nombres);
if (cmp<0)
return -1;
if (cmp>0)
return 1;
return 0;
}
void intercambiar(int i, int j) {
Persona aux= personas[i];
personas[i]= personas[j];
personas[j]= aux;
}
}
Ejercicio:
class OrdenadorPersonaXEdad extends OrdenadorPersona {
// El arreglo se hereda
OrdenadorString(int[] personas) { // Los constructores no se heredan
this.personas= personas;
}
int comparar(int i, int j) {
if (personas[i].edad<personas[j].edad)
return -1;
if (personas[i].edad>personas[j].edad)
return 1;
return 0;
}
// El metodo intercambiar se hereda
}
Ordene el arreglo de personas primero descendentemente por edad y ascendentemente por peso cuando las edades coinciden.