Programación Avanzada

Polimorfismo y sobrecarga

Conceptos de polimorfismo y sobrecarga

Introducción

Los términos polimorfismo y sobrecarga se pueden ver como complementarios.

El polimorfismo es un concepto unificador mientras que la sobrecarga es un concepto seleccionador.

Ambos son extensamente utilizados en POO, pero mientras que el polimorfismo puede ayudar enormemente a construir software, la sobrecarga puede ser un elemento de distorsión. Veamos por qué.

Contenidos

  1. Sobrecarga.
  2. Polimorfismo.
  3. Cuando utilizarlos.
  4. Resumen.

Sobrecarga

Como ya sabemos, los métodos se distinguen por su signatura, y la signatura está formada tanto por el nombre como por el número y tipo de los argumentos que recibe el método.

Recuerda que el tipo de retorno de un método no forma parte de su signatura, y por lo tanto no sirve para diferenciar métodos.

Supongamos que estamos definiendo un punto en tres dimensiones y escribimos la siguiente implementación.

Sobrecarga

public class Punto3D {
    private int x;
    private int y;
    private int z;
    public Punto3D() {
        x = y = z = 0;
    }
    public Punto3D(int x) {
        this.x = x;
    }
    public Punto3D(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public Punto3D(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

Sobrecarga

Tenemos el constructor sobrecargado cuatro veces, es demasiado. Los constructores con uno y dos parámetros son ambiguos:

new Punto3D(1); //¿Qué atributo estamos iniciando, x, y o z?
new Punto3D(1, 1); //¿Qué atributo NO estamos iniciando?

Demasiadas ambigüedades.

Sobrecarga

Utilicemos la sobrecarga en su justa medida:

public class Punto3D {
    private int x;
    private int y;
    private int z;
    public Punto3D() {
        x = y = z = 0;
    }
    public Punto3D(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

O pasamos todos los atributos a iniciar, o no pasamos ninguno.

Sobrecarga

Fíjate que Java selecciona el método al que se invoca según su signatura. La sobrecarga nos permite seleccionar el método adecuado.

No confundas la sobrecarga con la sobrescritura (@Override).

Sobrecargar significa definir nuevos métodos.

Sobrescribir significa ocultar un método con una nueva definición de ese mismo método.

La sobrecarga no implica herencia, la sobrescritura sí.

Polimorfismo

El polimorfismo está directamente relacionado con la herencia.

El polimorfismo nos permite que allá donde utilicemos un objeto de tipo madre, también podremos utilizar cualquier instancia de alguna de sus hijas.

Polimorfismo

¿Recuerdas este ejemplo del capítulo de Herencia?

Si tenemos un método como:

public void reciboUnPunto(Punto2D punto) {
    ...
}

Lo podemos utilizar tanto con referencias de tipo Punto2D como con referencias de tipo Punto3D.

Polimorfismo

Ahora podemos hacer:

reciboPunto(new Punto2D());
reciboPunto(new Punto3D());

Por otra parte recuerda el Principio de sustitución de Liskov que era la L de los principios SOLID. Este principio nos dice que nuestras aplicaciones se deben comportar igual si a un método le pasamos un objeto de clase padre como un objeto de cualquiera de sus hijos.

Polimorfismo

El polimorfismo también funciona con interface. ¿Te acuerdas del ejemplo de las recetas?:

public interface Receta {
    void preparaReceta();
}
...
public class Paella implements Receta {
...
public class LubinaAlHorno implements Receta {
...

Dos clases que implementan el mismo interface.

Recuerda, los interface definen tipos de datos abstractos.

Polimorfismo

Si el tipo del objeto es correcto, podremos utilizar este método:

private void preparaReceta(Receta receta) {
    receta.preparaReceta();
}

La Paella y la LubinaAlHorno son recetas:

private void aCocinar() {
    Receta receta = new Paella();
    preparaReceta(receta);
    receta = new LubinaAlHorno();
    preparaReceta(receta);
}

Cuando utilizarlos

Utiliza la sobrecarga con cuidado. Un método con múltiples signaturas puede despistar, ¿cuál debo utilizar?

Utiliza el polimorfismo a discreción, cuanto más, mejor.

Por otro lado, un concepto fuertemente relacionado con el polimorfismo es la Vinculación dinámica, la máquina virtual de Java encuentra el método a invocar basándose en el tipo del objeto que hay por debajo de una referencia.

Cuando utilizarlos

Un ejemplo de mal uso de la sobrecarga:

public class Persona {
    private String nombre;
    private String apellidos;
    private String direccion;
    public void cambia(String nombre) {
        this.nombre = nombre;
    }
    public void cambia(String nombre, String apellidos) {
        this.nombre = nombre;
        this.apellidos = apellidos;
    }
    public void cambia(String nombre, String apellidos,
                        String direccion) {
        this.nombre = nombre;
        this.apellidos = apellidos;
        this.direccion = direccion;
    }
}

Cuando utilizarlos

¿Qué hacer?:

public void cambia(Persona datos) {
    nombre = datos.nombre;
    apellidos = datos.apellidos;
    direccion = datos.direccion;
}

Un único método.

La idea que hay detrás es: si un método recibe un gran número de parámetros, igual te está indicando que debes crear una nueva clase para representarlos.

Resumen

Los conceptos de sobrecarga y polimorfismo son fundamentales de POO.

La sobrecarga permite seleccionar la llamada a un método a partir del número y tipo de sus parámetros.

Úsala con cuidado, hacer un uso intensivo de la sobrecarga puede dar lugar a confusión desde el punto de vista del programador.

Resumen

El polimorfismo nos permite utilizar clases con tipos compatibles en cualquier punto de nuestro código.

En Java tipos compatible significa que una clase extiende a otra o que una clase implementa un interface.

Coloquialmente: a una referencia de tipo padre podemos enganchar referencias de sus hijas. A una referencia de tipo interface le podemos enganchar cualquier instancia de una clase que implemente el interface.

La Vinculación dinámica es el mecanismo que utiliza la máquina virtual de Java para averiguar, en tiempo de ejecución, a qué método debe llamar, a partir del tipo del objeto asignado a una referencia.