PROGRAMACION ORIENTADA A OBJETOS (POO)
lunes, 4 de junio de 2012
3.1 Definición: Clase base y clase derivada
Introducción
La herencia es una
propiedad esencial de la Programación Orientada a Objetos que consiste en la
creación de nuevas clases a partir de otras ya existentes. Este término ha sido
prestado de la Biología donde afirmamos que un niño tiene la cara de su padre,
que ha heredado ciertas facetas físicas o del comportamiento de sus
progenitores.
La herencia es la
característica fundamental que distingue un lenguaje orientado a objetos, como
el C++ o Java, de otro convencional como C, BASIC, etc. Java permite heredar a
las clases características y conductas de una o varias clases denominadas base.
Las clases que heredan de clases base se denominan derivadas, estas a su vez
pueden ser clases bases para otras clases derivadas. Se establece así una
clasificación jerárquica, similar a la existente en Biología con los animales y
las plantas.
La herencia ofrece
una ventaja importante, permite la reutilización del código. Una vez que una
clase ha sido depurada y probada, el código fuente de dicha clase no necesita
modificarse. Su funcionalidad se puede cambiar derivando una nueva clase que
herede la funcionalidad de la clase base y le añada otros comportamientos.
Reutilizando el código existente, el programador ahorra tiempo y dinero, ya que
solamente tiene que verificar la nueva conducta que proporciona la clase
derivada.
La programación en
los entornos gráficos, en particular Windows, con el lenguaje C++, es un
ejemplo ilustrativo. Los compiladores como los de Borland y Microsoft
proporcionan librerías cuyas clases describen el aspecto y la conducta de las
ventanas, controles, menús, etc. Una de estas clases denominada TWindow describe el aspecto y la conducta de
una ventana, tiene una función miembro denominada Paint, que no dibuja nada en el
área de trabajo de la misma. Definiendo una clase derivada deTWindow,
podemos redefinir en ella la función Paint para que dibuje una figura.
Aprovechamos de este modo la ingente cantidad y complejidad del código
necesario para crear una ventana en un entorno gráfico. Solamente, tendremos
que añadir en la clase derivada el código necesario para dibujar un rectángulo,
una elipse, etc.
En el lenguaje
Java, todas las clases derivan implícitamente de la clase base Object, por lo que heredan las
funciones miembro definidas en dicha clase. Las clases derivadas pueden
redefinir algunas de estas funciones miembro como toString y definir otras nuevas.
Los programadores
crean clases base:
1.
Cuando se dan cuenta que diversos tipos tienen algo en común,
por ejemplo en el juego del ajedrez peones, alfiles, rey, reina, caballos y
torres, son piezas del juego. Creamos, por tanto, una clase base y derivamos
cada pieza individual a partir de dicha clase base.
2.
Cuando se precisa ampliar la funcionalidad de un programa sin
tener que modificar el código existente.
Vamos a poner un ejemplo del segundo tipo, que simule la
utilización de librerías de clases para crear un interfaz gráfico de usuario
como Windows 3.1 o Windows 95.
Supongamos
que tenemos una clase que describe la conducta de una ventana muy simple,
aquella que no dispone de título en la parte superior, por tanto no puede
desplazarse, pero si cambiar de tamaño actuando con el ratón en los bordes
derecho e inferior.
La clase Ventana tendrá los siguientes miembros dato:
la posición x e y de la ventana, de su esquina superior
izquierda y las dimensiones de la ventana:ancho y alto.
public class Ventana {
protected int x;
protected int y;
protected int ancho;
protected int alto;
public Ventana(int x, int y, int ancho, int alto) {
this.x=x;
this.y=y;
this.ancho=ancho;
this.alto=alto;
}
}
Las
funciones miembros, además del constructor serán las siguientes: la función mostrar que simula una ventana en un entorno
gráfico, aquí solamente nos muestra la posición y las dimensiones de la
ventana.
public void mostrar(){
System.out.println("posición : x="+x+", y="+y);
System.out.println("dimensiones : w="+ancho+", h="+alto);
}
La función cambiarDimensiones que simula
el cambio en la anchura y altura de la ventana.
public void cambiarDimensiones(int dw, int dh){
ancho+=dw;
alto+=dh;
}
El código
completo de la clase base Ventana,
es el siguiente
package ventana;
public class Ventana {
protected int x;
protected int y;
protected int ancho;
protected int alto;
public Ventana(int x, int y, int ancho, int alto) {
this.x=x;
this.y=y;
this.ancho=ancho;
this.alto=alto;
}
public void mostrar(){
System.out.println("posición : x="+x+", y="+y);
System.out.println("dimensiones : w="+ancho+", h="+alto);
}
public void cambiarDimensiones(int dw, int dh){
ancho+=dw;
alto+=dh;
}
}
|
Objetos de la clase base
Como vemos
en el código, el constructor de la clase base inicializa los cuatro miembros
dato. Llamamos al constructor creando un objeto de la claseVentana
Ventana ventana=new Ventana(0, 0, 20, 30);
Desde el
objeto ventana podemos llamar a las funciones miembro
públicas
ventana.mostrar();
ventana.cambiarDimensiones(10, 10);
ventana.mostrar();
LA CLASE
DERIVADA
Incrementamos
la funcionalidad de la clase Ventana definiendo una clase derivada
denominada VentanaTitulo.
Los objetos de dicha clase tendrán todas las características de los objetos de
la clase base, pero además tendrán un título, y se podran desplazar (se simula
el desplazamiento de una ventana con el ratón).
La clase derivada heredará los
miembros dato de la clase base y las funciones miembro, y tendrá un miembro
dato más, el título de la ventana.
public class VentanaTitulo extends Ventana{
protected String titulo;
public VentanaTitulo(int x, int y, int w, int h, String nombre) {
super(x, y, w, h);
titulo=nombre;
}
extends es la palabra reservada que indica que la clase VentanaTitulo deriva, o es una subclase, de la clase Ventana.
La primera
sentencia del constructor de la clase derivada es una llamada al constructor de
la clase base mediante la palabra reservada super.
La llamada
super(x, y, w, h);
inicializa
los cuatro miembros dato de la clase base Ventana: x, y, ancho, alto. A continuación, se
inicializa los miembros dato de la clase derivada, y se realizan las tareas de
inicialización que sean necesarias. Si no se llama explícitamente al
constructor de la clase base Java lo realiza por nosotros, llamando al
constructor por defecto si existe.
La función
miembro denominada desplazar cambia la posición de la ventana,
añadiéndoles el desplazamiento.
public void desplazar(int dx, int dy){
x+=dx;
y+=dy;
}
Redefine la
función miembro mostrar para mostrar una ventana con un
título.
public void mostrar(){
super.mostrar();
System.out.println("título : "+titulo);
}
En la clase
derivada se define una función que tiene el mismo nombre y los mismos
parámetros que la de la clase base. Se dice que redefinimos la funciónmostrar en la clase derivada. La función
miembro mostrar de la clase derivada VentanaTitulo hace una llamada a la función mostrar de la clase base Ventana, mediante
super.mostrar();
De este modo
aprovechamos el código ya escrito, y le añadimos el código que describe la
nueva funcionalidad de la ventana por ejemplo, que muestre el título.
Si nos
olvidamos de poner la palabra reservada super llamando a la funciónmostrar,
tendríamos una función recursiva. La función mostrar llamaría amostrar indefinidamente.
public void mostrar(){ //¡ojo!, función recursiva
System.out.println("título : "+titulo);
mostrar();
}
La
definición de la clase derivada VentanaTitulo,
será la siguiente.
package ventana;
public class VentanaTitulo extends Ventana{
protected String título;
public VentanaTitulo(int x, int y, int w, int h, String nombre) {
super(x, y, w, h);
titulo=nombre;
}
public void mostrar(){
super.mostrar();
System.out.println("titulo : "+titulo);
}
public void desplazar(int dx, int dy){
x+=dx;
y+=dy;
}
}
|
Objetos de la clase derivada
Creamos un
objeto ventana de la clase derivada VentanaTitulo
VentanaTitulo ventana=new VentanaTitulo(0, 0, 20, 30, "Principal");
Mostramos la
ventana con su título, llamando a la función mostrar,
redefinida en la clase derivada
ventana.mostrar();
Desde el
objeto ventana de la
clase derivada llamamos a las funciones miembro definidas en dicha clase
ventana.desplazar(4, 3);
Desde el
objeto ventana de la clase derivada podemos llamar a
las funciones miembro definidas en la clase base.
ventana.cambiarDimensiones(10, -5);
Para mostrar
la nueva ventana desplazada y cambiada de tamaño escribimos
ventana.mostrar();
3.2 Clasificación: Herencia simple y Herencia múltiple
Herencia
La herencia es un mecanismo que
permite la definición de una clase a partir de la definición de otra ya
existente. La herencia permite compartir automáticamente métodos y datos entre
clases, subclases y objetos.
La herencia está fuertemente ligada a
la reutilización del código en la POO. Esto es, el código de cualquiera de las
clases puede ser utilizado sin más que crear una clase derivada de ella, o bien
una subclase.
Hay dos tipos de herencia: Herencia Simple y Herencia Múltiple. La primera indica que se pueden definir nuevas clases solamente a partir de una clase inicial mientras que la segunda indica que se pueden definir nuevas clases a partir de dos o más clases iniciales. Java sólo permite herencia simple
Hay dos tipos de herencia: Herencia Simple y Herencia Múltiple. La primera indica que se pueden definir nuevas clases solamente a partir de una clase inicial mientras que la segunda indica que se pueden definir nuevas clases a partir de dos o más clases iniciales. Java sólo permite herencia simple
Herencia Simple La herencia en
C++ es un mecanismo de abstracción creado para poder facilitar, y mejorar el
diseño de las clases de un programa. Con ella se pueden crear nuevas clases a
partir de clases ya hechas, siempre y cuando tengan un tipo de relación
especial. En la herencia, las clases derivadas “heredan” los datos y las
funciones miembro de las clases base, pudiendo las clases derivadas redefinir
estos comportamientos (polimorfismo) y añadir comportamientos nuevos propios de
las clases derivadas. Para no romper el principio de encapsulamiento (ocultar
datos cuyo conocimiento no es necesario para el uso de las clases), se
proporciona un nuevo modo de visibilidad de los datos/funciones: “protected”.
Cualquier cosa que tenga visibilidad protected se comportará como pública en la
clase Base y en las que componen la jerarquía de herencia, y como privada en
las clases que NO sean de la jerarquía de la herencia. Antes de utilizar la
herencia, nos tenemos que hacer una pregunta, y si tiene sentido, podemos intentar
usar esta jerarquía: Si la frase <claseB> ES-UN <claseA> tiene
sentido, entonces estamos ante un posible caso de herencia donde clase A será
la clase base y clase B la derivada. Ejemplo: clases Barco, Acorazado,
Carguero, etc… un Acorazado ES-UN Barco, un Carguero ES-UN Barco, un
Trasatlántico ES-UN Barco, etc… En este ejemplo tendríamos las cosas generales
de un Barco (en C++) class Barco {
protected:
char* nombre;
float peso;
public:
//Constructores y demás funciones básicas de barco
}; y
ahora las características de las clases derivadas, podrían (a la vez que
heredan las de barco) añadir cosas propias del subtipo de barco que vamos a
crear, por ejemplo: Class Carguero: public Barco { // Esta es la manera de
especificar que hereda de Barco
private:
float carga;
//El resto de cosas
};
Class
Acorazado: public Barco {
private:
int numeroArmas;
int Soldados;
// Elresto de cosas
}; Por
último, hay que mencionar que existen 3 clases de herencia que se diferencian
en el modo de manejar la visibilidad de los componentes de la clase resultante:
• Herencia publica (class Derivada: public Base ) : Con este tipo de herencia
se respetan los comportamientos originales de las visibilidades de la clase
Base en la clase Derivada. • Herencia privada (clase Derivada: private Base) :
Con este tipo de herencia todo componente de la clase Base, será privado en la
clase Derivada (ojo! siempre será privado aunque ese dato fuese público en la
clase Base) • Herencia protegida (clase Derivada: protected Base) : Con este
tipo de herencia, todo componente publico y protegido de la clase Base, será
protegido en la clase Derivada, y los componentes privados, siguen siendo
privados. Herencia Múltiple La herencia múltiple es el mecanismo que permite al
programador hacer clases derivadas a partir, no de una sola clase base, sino de
varias. Para entender esto mejor, pongamos un ejemplo: Cuando ves a quien te
atiende en una tienda, como persona que es, podrás suponer que puede hablar,
comer, andar, pero, por otro lado, como empleado que es, también podrás suponer
que tiene un jefe, que puede cobrarte dinero por la compra, que puede
devolverte el cambio, etc… Si esto lo trasladamos a la programación sería
herencia múltiple (clase empleado_tienda): class Persona {
…
Hablar();
Caminar();
…
};
class
Empleado {
Persona jefe;
int sueldo;
Cobrar();
…
};
class
empleado_tienda: public Persona, Empleado {
…
Almacenar Stock();
Comprobar Existencias?();
…
}; Por
tanto, es posible utilizar más de una clase para que otra herede sus
características.
3.3 Reutilización de miembros heredados
La reutilización
de código se refiere al comportamiento y a las técnicas que garantizan
que una parte o la totalidad de un programa informático existente se pueda
emplear en la construcción de otro programa. De esta forma se aprovecha el
trabajo anterior, se economiza tiempo, y se reduce la redundancia.
La
manera más fácil de reutilizar código es copiarlo total o parcialmente desde el
programa antiguo al programa en desarrollo. Pero es trabajoso mantener
múltiples copias del mismo código, por lo que en general se elimina la
redundancia dejando el código reusable en un único lugar, y llamándolo desde
los diferentes programas. Este proceso se conoce como abstracción. La abstracción puede verse
claramente en lasbibliotecas de
software, en las que se
agrupan varias operaciones comunes a cierto dominio para facilitar el
desarrollo de programas nuevos. Hay bibliotecas para convertir información
entre diferentes formatos conocidos, acceder a dispositivos de almacenamiento
externos, proporcionar una interfaz con otros programas, manipular información
de manera conocida (como números, fechas, o cadenas de texto).
Para
que el código existente se pueda reutilizar, debe definir alguna forma de
comunicación o interfaz. Esto se puede dar por llamadas a una subrutina, a un
objeto, o a una clase.
Como se ha comentado anteriormente la
clase descendiente puede añadir sus propios atributos y métodos pero también
puede sustituir u ocultar los heredados. En concreto:
1. Se puede declarar un nuevo atributo con
el mismo identificador que uno heredado, quedando este atributo oculto.
Esta técnica no es recomendable.
2. Se puede declarar un nuevo método
de instancia con la misma cabecera que el de la clase ascendiente, lo
que supone su sobreescritura. Por lo tanto, la sobreescritura o
redefinición consiste en que métodos adicionales declarados en la clase
descendiente con el mismo nombre, tipo de dato devuelto y número y tipo de
parámetros sustituyen a los heredados.
3. Se puede declarar un nuevo método
de clase con la misma cabecera que el de la clase ascendiente, lo que
hace que éste quede oculto. Por lo tanto, los métodos de clase o
estáticos (declarados como static) no pueden ser redefinidos.
4. Un método declarado con el
modificador final tampoco puede ser redefinido por una clase derivada.
5. Se puede declarar un constructor de
la subclase que llame al de la superclase de forma implícita o de mediante la
palabra reservada super.
6. En general puede accederse a los
métodos de la clase ascendiente que han sido redefinidos empleando la palabra
reservada super delante del identificador del método. Este mecanismo sólo
permite acceder al metodo perteneciente a la clase en el nivel inmediatamente
superior de la jerarquía de clases.
Construyamos
la clase Taxista.java con el siguiente código:
public class Taxista extends Persona {
private int nLicencia;
public void setNLicencia(int num){
nLicencia = num;
}
public int getLicencia(){
return nLicencia;
}
}
Y
construyamos ArranqueTaxista.java:
public class ArranqueTaxista {
public static void main (String arg[]){
Taxista
tax1 = new Taxista();
tax1.setNombre("Luis");
tax1.setEdad(50);
System.out.println( tax1.getNombre());
System.out.println(tax1.getEdad());
}
}
Ahora
intentemos usar el constructor que existía en la clase Persona que recibia el
nombre de la persona y vamos a usarlo para la clase Taxista. Para ello
construyamos la clase ArranqueTaxista2.java:
public class ArranqueTaxista2 {
public static void main (String arg[]){
Taxista
tax1 = new Taxista("Jose");
tax1.setEdad(50);
System.out.println( tax1.getNombre());
System.out.println(tax1.getEdad());
System.out.println(tax1.getNLicencia());
}
}
Se
genera un error de compilación, debido a que los constructores no se heredan,
sino que hay que definir nuestros propios constructores. Agreguemos en la clase
Taxista los siguientes constructores:
public
Taxista(int licencia){
super();
nLicencia
= licencia;
}
public Taxista(String nombre,int
licencia){
super(nombre);
nLicencia
= licencia;
}
Ahora
si podremos compilar y ejecutar la clase ArranqueTaxista2. La llamada al método
super indica que estamos llamando a un constructor de la clase base (pensemos
que un Taxista antes que Taxista es Persona y por tanto tiene sentido llamar al
constructor de Persona antes que al de Taxista). Además gracias al número de
parámetros de la llamada a super podemos especificar cuál de los constructores
de la clase base queremos llamar.
En
java se pueden emplear dos palabras clave: this y super. Como vimos en la
unidad 1, this hace alusión a todo el objeto y super hace alusión a la parte
heredada, por ello empleamos super para referenciar al constructor de la clase
base.
Ahora
vamos a agregar la función getNombre dentro de la clase Taxista, es decir,
tenemos la misma función en Persona y en Taxista:
public
String getNombre() {
return
"Soy un taxista y me llamo: " + super.getNombre();
}
Compilamos
Taxista y ejecutamos ArranqueTaxista2. Veremos que el mensaje que aparece en
pantalla demuestra que la función getNombre llamada es la de del tipo real del
objeto construido, en este caso la de la clase derivada que es Taxista.
Tambien
apreciamos que para acceder al atributo nombre es necesario acceder al método
getNombre de la clase base (y por ello emplear super).
En
java los atributos y métodos de la clase base pueden cambiar su modificador de
visibilidad dentro de la clase derivada, la siguiente tabla recoge dichos
cambios:
Modificadores
en la clase base:
public
private
protected
paquete
En
la clase derivada se transforman en:
public
inaccesible
protected
paquete
Inaccesible
significa que, a pesar de haber sido heredado, no hay permisos en la clase
derivada para poder acceder a dicho elemento inaccesible, pero aún así, se
pueden llamar a métodos de la clase base que si pueden acceder y modificar al
elemento.
Recordemos
que protected significa que es private, pero que al heredar no se hace
inaccesible, es decir que desde la clase derivada se puede acceder.
3.4 Referencia al objeto de la clase base
Consideremos las figuras planas
cerradas como el rectángulo, y el círculo. Tales figuras comparten
características comunes como es la posición de la figura, de su centro, y el
área de la figura, aunque el procedimiento para calcular dicha área sea
completamente distinto. Podemos por tanto, diseñar una jerarquía de clases, tal
que la clase base denominada Figura, tenga las características
comunes y cada clase derivada las específicas. La relación jerárquica se
muestra en la figura
La clase Figura es
la que contiene las características comunes a dichas figuras concretas por
tanto, no tiene forma ni tiene área. Esto lo expresamos declarando Figura como
una clase abstracta, declarando la función miembroarea abstract.
Las clases abstractas solamente se
pueden usar como clases base para otras clases. No se pueden crear objetos
pertenecientes a una clase abstracta. Sin embargo, se pueden declarar variables
de dichas clases.
En el juego del ajedrez podemos
definir una clase base denominada Pieza, con las características
comunes a todas las piezas, como es su posición en el tablero, y derivar de ella
las características específicas de cada pieza particular. Así pues, la
clase Pieza será una clase abstracta con una función abstractdenominada mover,
y cada tipo de pieza definirá dicha función de acuerdo a las reglas de su
movimiento sobre el tablero.
· La
clase Figura
La definición de la clase
abstracta Figura, contiene la posición x e y de
la figura particular, de su centro, y la función area, que se va a
definir en las clases derivadas para calcular el área de cada figura en particular.
public abstract
class Figura {
protected int x;
protected int y;
public
Figura(int x, int y) {
this.x=x;
this.y=y;
}
public abstract double
area();
}
|
· La
clase Rectangulo
Las clases derivadas heredan los
miembros dato x e y de la clase base, y
definen la función area, declarada abstract en la
clase base Figura, ya que cada figura particular tiene una fórmula
distinta para calcular su área. Por ejemplo, la clase derivada Rectangulo,
tiene como datos, aparte de su posición(x, y) en el plano, sus
dimensiones, es decir, su anchura ancho y altura alto.
class Rectangulo extends
Figura{
protected double ancho, alto;
public
Rectangulo(int x, int y, double ancho, double alto){
super(x,y);
this.ancho=ancho;
this.alto=alto;
}
public
double area(){
return ancho*alto;
}
}
|
La primera sentencia en el
constructor de la clase derivada es una llamada al constructor de la clase
base, para ello se emplea la palabra reservada super. El
constructor de la clase derivada llama al constructor de la clase base y le
pasa las coordenadas del punto x e y. Después
inicializa sus miembros datoancho y alto.
En la definición de la función area,
se calcula el área del rectángulo como producto de la anchura por la altura, y
se devuelve el resultado
· La
clase Circulo
class Circulo extends
Figura{
protected double radio;
public
Circulo(int x, int y, double radio){
super(x,y);
this.radio=radio;
}
public
double area(){
return Math.PI*radio*radio;
}
}
|
Como vemos, la primera sentencia en
el constructor de la clase derivada es una llamada al constructor de la clase
base empleando la palabara reservadasuper. Posteriormente, se inicializa
el miembro dato radio, de la clase derivada Circulo.
En la definición de la función area,
se calcula el área del círculo mediante la conocida fórmula p*r2, o bien p*r*r.
La constante Math.PI es una aproximación decimal del número
irracional p.
Creamos un objeto c de
la clase Circulo situado en el punto (0, 0) y de 5.5 unidades
de radio. Calculamos y mostramos el valor de su área.
Circulo
c=new Circulo(0, 0, 5.5);
System.out.println("Area del círculo "+c.area());
Creamos un objeto r de
la clase Rectangulo situado en el punto (0, 0) y de
dimensiones 5.5 de anchura y 2 unidades de largo. Calculamos y mostramos el
valor de su área.
Rectangulo r=new Rectangulo(0, 0, 5.5, 2.0);
System.out.println("Area del rectángulo "+r.area());
Veamos ahora, una forma alternativa,
guardamos el valor devuelto por newal crear objetos de las clases
derivadas en una variable f del tipo Figura (clase
base).
Figura f=new Circulo(0, 0, 5.5);
System.out.println("Area del círculo "+f.area());
f=new
Rectangulo(0, 0, 5.5, 2.0);
System.out.println("Area del rectángulo "+f.area());
Suscribirse a:
Entradas (Atom)