Pero pensemos en las clases Coche y Camión. Seguro que muchos de los atributos de Camión serán parecidos o iguales a los de Coche, y lo mismo ocurrirá con los métodos. Hasta ahora, con programación convencional, tendríamos que rescribir de nuevo todo el código y datos para Camión y gran parte coincidiría con los de Coche.
Bien, la herencia es un mecanismo que permite reutilizar código indicando cómo una clase se diferencia de otra definida previamente, tomando parte de sus características (o todas) y cambiando o ampliando el resto. Aunque en la orientación a objetos existen muy diversos tipos de herencia, para simplificar diremos que cuando una clase hereda de otra, lo que está expresando es una relación "es un". Por ejemplo si la clase Perro hereda de la clase Mamifero significará que Perro es un Mamifero, es decir, todo lo que haga un Mamifero lo podrá hacer también Perro. En Java lo que queremos decir es que cuando una clase hereda de otra, sin que nosotros lo tengamos que especificar en su definición, automáticamente tiene todos los atributos(datos) y métodos (funcionalidad) de la clase de la que hereda. Como hemos comentado esta clase hija podrá cambiar la especificación de estos miembros o añadir tantos como desee.
La herencia funciona de un modo estrictamente jerárquico. Cada clase tiene una superclase o clase padre (de la que hereda atributos y métodos) y varias (de 0 a n) subclases o clases hijas (que heredan de ella atributos y métodos). La herencia transmite atributos y métodos hacia abajo en la jerarquía a través de todos los niveles.
En otros lenguajes orientados a objetos no es así, pero en Java se cumple:
Por ejemplo, ¿cómo rediseñaríamos jerárquicamente nuestro Coche y nuestro Camión? Pues se escapa de este curso definir el proceso de diseño, pero un buen principio puede ser preguntarse ¿qué es más general? Si todo lo que tiene y hace un coche también lo hace un camión, y además más cosas, coche sería una clase padre de camión.
En este caso no es así (hay cosas de los camiones que no tienen los coches y viceversa): no se incluyen. Pero lo que sí podemos es definir una clase más general que recoja el comportamiento común de coches y camiones, y que pueda ser padre de ambas clases. ¿Cómo la llamamos? Vehículo podría ser un buen nombre (no siempre tiene que haber un buen nombre para este tipo de clases).
La idea sería definir la clase vehículo, incluir en ella la información común a todos los vehículos; y después definir las clases Coche y Camión, ambas como hijas de Vehículo.
Esta jerarquía debería complicarse mucho más para representar adecuadamente una realidad más compleja. Por ejemplo, podríamos dividir los vehículos en vehículos de tracción mecánica y vehículos de tracción animal, o bien en aéreos, terrestres, marinos y anfibios… no siempre hay una única forma de diseñar una jerarquía (más bien, casi nunca hay sólo una manera de crear una jerarquía). Se habla de estas jerarquías también como taxonomías, por la similitud que tiene con las clasificaciones de especies que a menudo se realizan en las ciencias naturales.
En cualquier caso, lo importante es recordar que una vez que añadimos una característica (un atributo o un método) en una clase de la jerarquía, esa característica se propaga a todos sus hijos sin necesidad de rescribirla (aunque sí se podrá modificar o eliminar).
Para ello definimos la clase CocheElectrico como descendiente de Coche, del siguiente modo:
class CocheElectrico
extends Coche {
}
Con esto no añadimos nada más, si queremos redefinir algún método de Coche o añadir alguna información podemos hacerlo de la misma forma que antes:
class CocheElectrico extends Coche {
int autonomia;
void visualizar() {
System.out.println( "Este coche es eléctrico!!!!" );
System.out.println( "Autonomía: " + autonomia + " kilómetros"
);
super.visualizar();
}
}
A partir de ahí podría acceder a la siguiente funcionalidad:
CocheElectrico
miCocheElectrico = new CocheElectrico("azul", "Philips K30", 30);
miCocheElectrico.autonomia
= 100;
miCocheElectrico.arrancar();
miCocheElectrico.visualizar();
Class Motor {
String modelo;
int caballos, consumoMedio;
Motor(String sModelo) {
modelo = sModelo;
}
void arrancar() {
...
}
}
Vemos que nuestra clase Motor tiene un método que es arrancar, que tendrá su implementación para que el motor arranque, pero que ni siquiera la hemos puesto porque a nuestra clase Coche, como está haciendo uso de clientela, sólo le interesará que para arrancar el Motor hay que llamar al método "arrancar", pero no le interesará cómo lo hace exactamente. Nuestra clase Coche sería ahora.
class Coche {
String color, modelo;
int velocidadMaxima;
boolean motorEncendido;
Motor motor;
Coche(String sColor, String sModelo, int iVelocidadMaxima, int
modeloMotor) {
color=sColor;
modelo=sModelo;
velocidadMaxima=iVelocidadMaxima;
motorEncendido=false;
motor = new Motor(modeloMotor);
}
void arrancar() {
if (motorEncendido == true)
System.out.println( "El motor ya estaba encendido" );
else {
motorEncendido = true;
motor.arrancar();
System.out.println( "El motor está ahora encendido" );
}
}
void visualizar() {
System.out.println( "Este coche es un " + modelo + ", de color " + color
);
if (motorEncendido == true)
System.out.println( "El motor está encendido." );
else System.out.println( "El motor está apagado."
);
}
}
Por tanto, con la clientela hacemos uso de una clase cuando no nos interesa cambiar su comportamiento, si no, simplemente usarla, por tanto no necesitaremos conocer detalles de su implementación, si no la funcionalidad que desempeña, mientras que mediante la herencia, lo que haremos será usar otra clase para reutilizar su comportamiento variándolo en los puntos que nos sean necesarios.
¿Qué es una interfaz? Una interfaz es una especie de clase pero que sólo define especificaciones de métodos, no su implementación, es decir sólo especifica la cabecera del método. Por ejemplo:
interface VehiculoTerrestre
{
void arrancar();
void ponerElFrenoDeMano();
}
interface VehiculoAcuatico
{
void arrancar();
void echarElAncla();
}
class ClaseVehiculoTerrestre
implements VehiculoTerrestre {
Motor motor;
Freno freno;
void arrancar() {
motor.arrancar();
}
void ponerElFrenoDeMano() {
freno.poner();
}
}
class ClaseVehiculoAcuatico
implements VehiculoAcuatico {
Helices helices;
Ancla ancla;
void arrancar() {
helices.girar();
}
void echarElAncla() {
ancla.echar();
}
}
class Anfibio
implements VehiculoTerrestre, VehiculoAcuatico {
VehiculoTerrestre vehiculoTerrestre;
VehiculoAcuatico vehiculoAcuatico;
void ponerElFrenoDeMano() {
vehiculoTerrestre.ponerElFrenoDeMano();
}
void echarElAncla() {
vehiculoAcuatico.echarElAncla();
}
boolean estoyEnElAgua() {
...
return true; // Si está en el agua
...
return false; // Si no está en el agua.
}
void arrancar() {
if ( estoyEnElAgua() ) {
vehiculoTerrestre.parar();
vehiculoAcuatico.arrancar();
}
else {
vehiculoAcuatico.parar();
vehiculoTerrestre.arrancar();
}
}