Mostrando entradas con la etiqueta Herencia. Mostrar todas las entradas
Mostrando entradas con la etiqueta Herencia. Mostrar todas las entradas

29 de enero de 2019

Polimorfismo

El Polimorfismo es uno de los conceptos que siempre suenan cuando se habla de POO junto con la encapsulación o la herencia. Sin embargo aplicarlo no es nada nuevo, llevamos ya muchas sesiones usándolo aunque no hayamos nombrado el concepto.

En resumen: dos objetos de distinto tipo pueden recibir el mismo mensaje (invocación de un método) y responder de dos (Poli) formas (morfia) distintas.

Polimorfismo: objetos de distinto tipo pueden recibir el mismo mensaje y responder de forma distinta


¿Dónde hemos usado polimorfismo?

Te invito a que pienses un momento sobre si hemos utilizado una misma invocación a distintos objetos y el resultado fue distinto.

Estos ejemplos ya vistos usan polimorfismo:
  1. Método toString() de todos los objetos que hemos visto (Integer, String, Coche, etc...) todos nos devuelven un String, pero cada uno lo construye de maneras distintas
  2. Particularmente para nuestros ejemplos de vehículos, unos tienen matrícula, otros ruedas, incluso un barco con su eslora... a todos se les llama con toString() y devuelven un String pero lo calculan de manera distinta
  3. Método setColor(String color): Ahora la invocación añade el argumento color. Podíamos poner el color que quisiéramos hasta que instanciamos nuestra Harley-Davidson que sólo aceptaba dos colores. Pasó de una simple asignación a avisarnos que hay limitaciones con ese objeto = polimorfia.
  4. Veremos más ejemplos cuando lleguemos a las Interfaces viendo como tipos sin relación responden distinto a un mismo mensaje.

Esta es la entrada más corta que he hecho y quiero dejarla así porque la próxima vez que oigas el concepto polimorfismo no quiero que te eches las manos a la cabeza, quiero que pienses que es uno de los conceptos más simples pero potentes de la POO.

28 de enero de 2019

Clases anónimas

Terminábamos nuestra sesión de clases abstractas preguntándonos ¿por qué una clase abstracta (que no se puede instanciar) tiene constructores?

Obviando que los constructores declarados en VehiculoConRuedas sirven para aprovechar constructores de Vehiculo, lo correcto es que las clases abstractas contengan todo el código relacionado con sus miembros.

Es normal que el código que implementemos para inicializar los atributos modelo y color, pueda heredarlo para no tener que implementarlo en cada subtipo (mejorando el mantenimiento como ya hemos repetido varias veces)

No obstante, hay un caso muy importante para tener también constructores incluso en clases abstractas: instanciar con clases anónimas.

Una clase anónima nos va a permitir declarar una clase e instanciarla en la pequeña parte de código donde nos haga falta, haciendo nuestro código más legible y económico. Como podemos deducir de su definición, es una clase que no tiene nombre, no está contenida en su propio archivo .java, ni es interna (esto último todavía no lo hemos visto)

Una clase anónima nos va a permitir declarar una clase e instanciarla brevemente, haciendo nuestro código más legible y económico


Hasta ahora hemos visto que cada clase tenía que tener su propio archivo .java con el mismo nombre o Eclipse nos daba un error (MiClase tiene el código en el archivo MiClase.java).

Sin embargo, vamos a encontrarnos infinidad de situaciones en las que necesitaremos un objeto que podría usar un tipo prácticamente igual a otro ya creado, salvo por un pequeñísimo cambio que además sólo vamos a utilizar en ese objeto en un punto concreto de nuestro código y nunca más nos interesará.

Es demasiado repetitivo y excesivo tener que crear un archivo para una nueva clase que herede de un tipo sólo para instanciar un objeto en una línea de código (imaginad crear un botón en un menú donde lo único que cambia con otro botón es qué tiene que pasar al hacer click... ¿nos creamos una clase para cada botón?). Para estos casos, Java tiene las clases anónimas que son una herramienta poderosísima de personalización de un tipo.

Una clase anónima se crea al vuelo a la hora de instanciar un objeto que necesitamos y no encaja exactamente con ningún tipo. Se define llamando al constructor de un tipo conocido y añadiéndole un cuerpo con el comportamiento personalizado.

Vamos a ver un ejemplo con nuestro código sobre Vehiculo creándonos un triciclo. No nos vamos a crear una nueva clase Triciclo, pues sólo queremos imprimir nuestro objeto triciclo, pero no encaja con ningún tipo que tengamos: tiene tres ruedas (no es una Moto) y no tiene matrícula (no es un Coche).
NOTA: No es una buena práctica utilizar tipos que tengan todo lo que necesitamos y hacer null el resto de atributos. Los objetos deben ser del tipo con el que se construyen, el resto son apaños que aumentan la deuda técnica.

Lo primero es seleccionar la clase que más se acerca a lo que queremos: VehiculoConRuedas. Éste sería el código para crearme un triciclo:
VehiculoConRuedas triciclo = new VehiculoConRuedas("Fisher-Price", "Multicolor") {
    
    @Override
    public int getNumeroDeRuedas() {
        return 3;
    }

};
Vemos que declaramos una variable del tipo VehiculoConRuedas (pues hereda de éste tipo), pero al ser una clase abstracta y no poderse instanciar (está incompleta) directamente nos añadirá lo que le falta por implementar: getNumeroDeRuedas().

Si imprimimos nuestro triciclo veremos que el método toString() funciona igual que en las clases NO anónimas Coche y Moto que heredaban de VehiculoConRuedas. Tenemos la siguiente salida.

Fisher-Price (Multicolor), 3 ruedas

¿Sólo puedo modificar métodos que ya existan?

No. Al ser VehiculoConRuedas una clase abstracta debemos implementar los métodos no implementados, pero podemos añadir más miembros o modificar (incluso reutilizar) los que ya estén implementados.

En nuestros ejemplos anteriores nos hemos creado una Moto. Vamos a crearnos otra que sea una Harley-Davidson. Ésta marca de moto sólo permite dos colores: negro y rojo. Vamos a cambiar el método setColor(String) para que no acepte ningún color que no sea uno de esos dos:
Moto harley = new Moto("Harley-Davidson", "Rosa") {

    @Override
    public void setColor(String color) {
        if(!(color.equals("Rojo") || color.equals("Negro"))) {
            System.out.println("No se permite ese color para " + modelo);
        } else {
            super.setColor(color);
        }
    }
    
};
System.out.println(harley);
Con este código tenemos la salida:

No se permite ese color para Harley-Davidson
Moto: Harley-Davidson (null), 2 ruedas

Nos avisa que el color no está permitido y de hecho vemos que nos imprime null porque no se ha asignado. Si le asignamos (reutilizando con super el método que hereda) un color válido si que funcionará:
harley.setColor("Negro");
System.out.println(harley);
Moto: Harley-Davidson (Negro), 2 ruedas

¿Necesito una clase abstracta para crear una clase anónima?

No. Vamos a crearnos un barco usando la clase Vehiculo que no es abstracta:
Vehiculo barco = new Vehiculo("CMB Yachts", "Blanco") {
    double eslora = 47.8;

    private double getEslora() {
        return eslora;
    }
    
    @Override
    public String toString() {
        return super.toString() + " con " + getEslora() + "m de eslora";
    }
    
};
System.out.println(barco);
Vemos que podemos tanto sobrescribir, como crear nuevos miembros (incluyendo atributos como eslora) y no afecta si el tipo del que heredamos es abstracto o no. Incluso podemos crear clases anónimas usando una Interface como veremos cuando las conozcamos. El resultado por consola es:

CMB Yachts (Blanco) con 47.8m de eslora

Las clases anónimas y el concepto scope

Un aspecto importante a destacar es que las clases anónimas se crean en el ámbito (scope) de otra y tienen acceso a los miembros tanto de su clase como del tipo donde se crean incluso con el modificador de acceso private. Esto nos permite referenciar valores que permanecen privados pero que pueden ser usados en la implementación de nuestra clase anónima (por ejemplo un botón puede desencadenar un cambio en un componente privado de un formulario).

Las clases anónimas tienen acceso a todos sus miembros y a los del tipo donde se crean


Vamos a ver un ejemplo. Declaro un atributo privado:
private static int miCodigoSecreto = 12345; // ¿En serio?
Pero si me instancio una clase anónima dentro de su ámbito voy a tener acceso:
Coche cocheConPin = new Coche() {

    @Override
    public String toString() {
        return "El código secreto es: " + miCodigoSecreto;
    }
    
};
System.out.println(cocheConPin);
Si imprimo mi nuevo objeto tendré la salida:

El código secreto es: 12345

Siendo anónimas ¿Cómo nos referimos a su class?

Las clases van a tomar por defecto el nombre del tipo donde se instancian más un autonumérico precedido de un dolar ($). Podemos verlo con el siguiente código:
System.out.println(triciclo.getClass());
System.out.println(harley.getClass());
System.out.println(barco.getClass());
Como vemos nos salen 3 clases distintas (instanciadas en la clase Igualdad) y lo único que cambie es el autonumérico:

class Igualdad$1
class Igualdad$2
class Igualdad$3

Conclusión

Puede que todavía no veas las posibilidades que aportan las clases anónimas, pero cuando conozcas los Listener, las lambdas, los stream, las Interfaces Funcionales y otras capacidades de Java, no querrás seguir programando sin ellas.

25 de enero de 2019

Clases abstractas

Las clases abstractas son clases pensadas para ser heredadas de ellas y que encapsulan código dirigido . Se comportan exactamente igual que las clases que hemos visto hasta ahora con estas diferencias:
  1. Debe declararse que son abstractas usando en su declaración la palabra reservada abstract como modificador.
  2. Tendrán métodos abstractos que se declararán también con abstract. Estos métodos no tienen cuerpo y serán implementados por sus subtipos.
  3. Al carecer de una implementación completa (los métodos abstractos no están implementados), no podrán construirse instancias de este tipo pues para poder crear objetos deben tener todos sus miembros implementados.
NOTA: Si bien el código no nos arrojará ningún error ni nos obligará a cumplir las condiciones 2 y 3, el hecho de no cumplirlas sería un indicativo de que no tiene sentido declararla abstracta.

Con las clases abstractas vamos a tener código relacionado en un único sitio para que por herencia lo completen y aprovechen sus subtipos


Como todo se ve mejor con un ejemplo, vamos a crearnos una clase abstracta llamada VehiculoConRuedas, que servirá para heredar de Vehiculo y gestionar lo que tenga que ver con ruedas. Vamos a crearla de la misma forma que lo hicimos la sesión anterior, pero en esta ocasión, aunque pensemos que debemos escoger el atributo numeroDeRuedas, no lo vamos a marcar y sólo le vamos a poner el nombre "VehiculoConRuedas" a la clase abstracta.
NOTA: Si ya tuviera más clases con comportamientos a agrupar (por ejemplo ya tuviera creada Moto), Eclipse nos permite añadir todas las clases de las que queramos extraer la superclase.
public class VehiculoConRuedas extends Vehiculo {

  public VehiculoConRuedas() {
    super();
  }

  public VehiculoConRuedas(String modelo, String color) {
    super(modelo, color);
  }

}
Eclipse ha insertado en nuestra jerarquía la clase VehiculoConRuedas entre Coche y Vehiculo para que todo siga funcionando correctamente. Todo esto también podríamos haberlo escrito a mano y no debe dejar de practicarse para coger soltura.

Si abrimos nuestra nueva clase vemos que sólo tiene dos constructores y que es una clase normal. Vamos a añadirle un método abstracto para ver qué una clase normal no lo permite. Lo haremos simplemente declarándolo incluyendo el modificador abstract y en vez de un cuerpo terminaremos con punto y coma:
public abstract int getNumeroDeRuedas();
Tendremos un error en Eclipse que nos dirá que quitemos el modificador abstract del método o que hagamos la clase abstracta. Vamos a declarar nuestra nueva clase abstracta y ahora ya no tendremos errores.

Nuestra clase completa queda:
public abstract class VehiculoConRuedas extends Vehiculo {

  public VehiculoConRuedas() {
    super();
  }

  public VehiculoConRuedas(String modelo, String color) {
    super(modelo, color);
  }

  public abstract int getNumeroDeRuedas();
}
Guardamos los cambios en VehiculoConRuedas y volvemos al código de Coche para ver que hereda de VehiculoConRuedas (lo hizo Eclipse) pero nos aparece un error: No tiene implementado el método getNumeroDeRuedas().

Podemos usar Eclipse para que nos añada este método vacío o hacerlo a mano. Si aceptamos la solución que nos ofrece Eclipse (Add unimplemented methods) cuando nos situamos encima del error vamos a obtener en Coche el siguiente código (seguramente estará añadido al final de todo):
@Override
public int getNumeroDeRuedas() {
  // TODO Auto-generated method stub
  return 0;
}
Con esto Eclipse nos crea ese método vacío y nos añade una tarea (//TODO) para rellenar la implementación. Como nosotros tenemos precisamente un atributo llamado numeroDeRuedas vamos a devolver su valor como hacemos con cualquier otro getter:
@Override
public int getNumeroDeRuedas() {
  return numeroDeRuedas;
}
Ahora si imprimimos un coche veremos que el método toString() que implementamos funciona correctamente:
  1. Obtiene el modelo y color desde Vehiculo
  2. Obtiene el número de ruedas usando u miembro heredado de VehiculoConRuedas
  3. El método toString() está implementado en Coche.
Compruébalo añadiendo este código:
System.out.println(coche1);
A nuestro main hecho en la sesión de igualdad, verás que nos muestra por consola:

Placa 1234 BBB - Seat Ibiza (Rojo), 4 ruedas

Reutilizamos nuestra jerarquía

Ahora llega el momento de reutilizar nuestra jerarquía creándonos una clase Moto. Podemos usar las ventajas de Eclipse y decir en la ventana de creación de clase que hereda de VehiculoConRuedas. Así tenemos ya trabajo hecho:
public class Moto extends VehiculoConRuedas {

  @Override
  public int getNumeroDeRuedas() {
    // TODO Auto-generated method stub
    return 0;
  }

}
Nos queda implementar el método abstracto getNumeroDeRuedas(). No es necesario crearse un atributo para completar este método. Bastaría con que siempre devuelva el valor 2 para ver el ejemplo:
@Override
public int getNumeroDeRuedas() {
  return 2;
}

Colocando más cosas en su sitio

Si ahora hago la prueba de imprimir una nueva moto veré que obtengo un resultado bastante feo como ya vimos en la sesión sobre el método toString():
System.out.println(new Moto());
// Devuelve algo como Moto@52e922
Tiene sentido colocar el código que está relacionado en el mismo sitio: Si todos los Vehiculo tienen modelo y color, y todos los VehiculoConRuedas tienen ruedas, el método toString() podría ir mejorándose en función de qué nivel ocupa mi tipo en la jerarquía. Voy a implementarlo haciendo dos cosas:
  1. Crearé un constructor para Moto que acepte modelo y color. (puedes usar Source > Generate Constructors from Superclass
  2. Moveré la parte del código de Coche.toString() a las clases a las que pertenecen los miembros correspondientes.
Aquí están los cambios que hay que hacer:

En Vehiculo:
@Override
public String toString() {
  return modelo + " (" + getColor() + ")";
}
En VehiculoConRuedas:
@Override
public String toString() {
  return super.toString() + ", " + getNumeroDeRuedas() + " ruedas";
}
En Coche:
@Override
public String toString() {
  return "Placa " + matricula + " - " + super.toString();
}
En Moto:
public class Moto extends VehiculoConRuedas {

  public Moto(String modelo, String color) {
    super(modelo, color);
  }

  @Override
  public int getNumeroDeRuedas() {
    return 2;
  }

  @Override
  public String toString() {
    return "Moto: " + super.toString();
  }

}
Ahora si imprimo los ejemplos de toString() anteriores:
System.out.println(coche1);
System.out.println(new Moto("Suzuki", "Negro"));
Tengo la salida:

Placa 1234 BBB - Seat Ibiza (Rojo), 4 ruedas
Moto: Suzuki (Negro), 2 ruedas

Queda demostrado el ahorro de tiempo tanto construyendo como manteniendo nuestro código si nos apoyamos en jerarquías que me permitan tener el código relacionado escrito en un único sitio para que luego sea aprovechado de manera particular en otras clases más especializadas.

Si alguno ha examinado el código quizás pueda preguntarse: Si las clases abstractas no se pueden instanciar ¿Por qué VehiculoConRuedas tiene constructores?

Es una buena pregunta, vamos a verlo mejor en la sesión que explica qué son las clases anónimas.

24 de enero de 2019

Herencia

Ya he adelantado que las clases pueden heredar de otras clases. Esta capacidad de heredar nos va a servir para que clases relacionadas que deben tener miembros parecidos o repetidos no tengan que ser implementados para cada clase. Si una clase superior (superclase o clase padre) tiene una serie de miembros implementados, pueden ser aprovechados por clases que hereden de ella (subtipo o clase hija).

Con la herencia reutilizamos código de clases fuertemente relacionadas, estableciendo una relación "es un" o de especialización


Se van a heredar todos los miembros (no los constructores), pero su visibilidad lo regirá el control de acceso que marquemos (un miembro private no se podrá ver desde su heredero como ya vimos, deberá ser marcado mínimo como protected para tener acceso desde un heredero)

Para poder definir y usar la herencia vamos a usar las palabras reservadas extends y super:
  • extends: sirve para definir la clase de la que se hereda (superclase) en la propia cabecera de la clase que hereda (subtipo).
  • super: sirve para referirnos a miembros de la superclase (parecido a como hacíamos con this)
Vamos a verlo con el código. En nuestro ejemplo vamos a crear una clase padre con nombre Vehiculo que nos sirva también para aeronaves, embarcaciones, etc... y decidimos que lo que deben tener todos los vehículos es un modelo y color. La forma de hacerlo es extraer los miembros que tienen que ver con modelo y color y moverlos a nuestra nueva clase Vehiculo.

Lo más fácil usando Eclipse es usar las ayudas que nos ofrece este IDE. Si hacemos click derecho sobre la Coche (en el Package Explorer o directamente en la ventana de su código fuente) elegimos Refactor > Extract Superclass... y en la ventana que nos aparece le ponemos el nombre Vehiculo y elegimos en la parte de abajo los miembros que vamos a extraer. Estos son: modelo, color, getColor(), setColor(String). Al clickar en Finish tendremos nuestra superclase creada (ver en su fichero .java).
public class Vehiculo {

    protected String modelo;
    String color;

    public Vehiculo() {
        super();
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

}
Ahora vemos que Coche carece de esos miembros (en su código ya no aparecen) y lo que debemos hacer es declarar que herede de Vehiculo con la palabra reservada extends. Eclipse ya ha hecho ese trabajo por nosotros. Si hacemos la refactorización a mano no nos podemos olvidar de declarar esta herencia:
public class Coche extends Vehiculo {

  // Codigo sin los miembros de Vehiculo

}
Ahora sí que son miembros que conoce la clase Coche. Y los podremos usar de la misma forma que ya los usábamos. Puedes comprobarlo con el código que hicimos en la sesión de igualdad de objetos.

De hecho vamos a cambiar un poco el código para ver cómo los valores de tipo Coche SON también un Vehiculo:
public class Igualdad {

  public static void main(String[] args) {
    Coche coche1 = new Coche("Seat Ibiza", "Rojo");
    coche1.setMatricula("1234 BBB");
    //...
    Vehiculo coche3 = coche1;
    // coche3.setMatricula("5678 CCC");// error!

    //...
  }
}
Aquí vemos que coche1 puede asignarse a una variable de tipo Vehiculo y el resto de código funciona correctamente. Es una buena práctica acostumbrarse a usar los tipos más básicos de lo que necesito: como sólo uso los miembros de modelo y color, me sirve un tipo Vehiculo. Sin embargo si quisiera usar el método setMatricula(String) sí que tendría que usar una variable de tipo Coche, porque sólo así tendré acceso a ese método como ya os adelantaba en la sesión donde construíamos objetos. Queda demostrado entonces que las variable dan acceso a los miembros de su tipo, independientemente de que el valor sea de un subtipo más especializado.

Con la herencia se crea una jerarquía de tipos más especializados compatibles con sus ancestros


Aprovechando los constructores

Hemos dicho que se heredan los miembros pero no los constructores: ¿Cómo aprovecho el código que ya tenía en los constructores?

Lo que hacemos es acceder a miembros de las clases superiores o sus constructores. Voy a crearme un constructor de Vehiculo con los dos parámetros modelo y color:
public Vehiculo(String modelo, String color) {
    this.modelo = modelo;
    this.color = color;
}
Ahora puedo llamar al constructor de la superclase para aprovecharlo y sólo añadir código nuevo. En mi caso uso el constructor de Coche con modelo y color, pero voy a vaciarlo del código repetido ahora en Vehiculo. Nuestro constructor pasa de esto:
public Coche (String color, String modelo) {
    super(color, modelo);
    numeroDeRuedas = 4;
}
NOTA: Asigno 4 a numeroDeRuedas pues ya no utiliza el constructor por defecto Coche(). Te propongo que modifiques los otros constructores para que reutilicen éste como vimos en la sesión sobre constructores.


Con la palabra reservada super hacemos referencia a la superclase (igual que con this hacemos referencia a sí mismo). Si usamos el asistente de contenido veremos que nos salen los miembros de la superclase a los que tengo acceso: modelo, color, getColor(), setColor(String) y todos los de Object que es "el abuelo".

Con lo que hemos visto aquí ya tenemos nuestra primera jerarquía de clases. En la siguiente sesión sobre clases abstractas vamos a hacernos otra clase Moto que también herede de Vehiculo, pero en medio, para reutilizar el código que tenga que ver con el número de ruedas, crearemos la clase abstracta VehiculoConRuedas.

Compárteme

Entradas populares