martes, 11 de diciembre de 2012

Polimorfismo

En la programación orientada a objetos resulta muy frecuente escuchar o leer el término 'polimorfismo'. Voy a intentar explicarlo de una forma sencilla. Porque aunque la palabra puede resultar algo complicada de pronunciar, es mucho más fácil de entender.

El polimorfismo podríamos definirlo como la posibilidad que tiene una variable objeto definida en tiempo de compilación de un tipo determinado, de poder instanciarse en tiempo de ejecución como un objeto de distinto tipo pero que tiene el mismo interfaz que el tipo definido en compilación. (Entendiendo por interfaz el conjunto completo de peticiones (métodos) que se pueden enviar al objeto)

Suena un poco a trabalenguas, pero no os asustéis. Seguro que con este ejemplo queda más claro.

Supongamos la siguiente jerarquía de clases



Podemos ver como las clases 'Triangulo', 'Rectangulo' y 'Romboide' aun siendo clases diferentes con sus características peculiares, obviamente un triangulo no es igual que un rectángulo, todas ellas son figuras geométricas y por lo tanto extienden de la clase 'Figura', de la que heredan aquellos elementos comunes a todas ellas, en este caso los atributos 'base' y 'altura', y el métodos 'calcularArea', que nos devolverá el área de la figura geométrica en cuestión. Y son estos elementos los que definen el interfaz común.

Esto traducido en código Java quedaría de la siguiente manera (Aunque quiero dejar claro que lo menos importante es el código con su sintaxis, porque esto mismo nos lo encontraremos en otros lenguajes orientados a objetos con distinta sintaxis, lo más importante es el concepto)

package test;

public abstract class Figura {
    
    protected int base;
    protected int altura;
    
    abstract int calcularArea();
    
    public Figura(int base, int altura){
         this.base = base;
         this.altura = altura;
    }

}

El hecho de que la clase 'Figura' que define el interfaz común sea un clase abstracta no es relevante, podría ser igualmente una clase "normal" completamente implementada o incluso un interfaz (propiamente dicho) con los métodos declarados pero no implementados. Lo importante es que las distintas clases hijas compartan este mismo interfaz.

Las clases hijas quedarían implementadas de la siguiente manera

package test;

public class Rectangulo extends Figura {

    public Rectangulo(int base, int altura) {
         super(base, altura);
    }

    @Override
    int calcularArea() {
        return base*altura;
    }

}

package test;

public class Romboide extends Figura {

    public Romboide(int base, int altura) {
         super(base, altura);
    }

    @Override
    int calcularArea() {
        return base*altura;
    }

}

package test;

public class Triangulo extends Figura {

    public Triangulo(int base, int altura) {
         super(base, altura);
    }

    @Override
    int calcularArea() {
        return (base*altura)/2;
    }

}

Podemos observar como aún teniendo todas ellas el mismo interfaz, cada una lo implementa de forma distinta, puesto que para cada tipo de figura el área se calcula de manera diferente.

Ahora supongamos que queremos mostrar por pantalla el área de un rectángulo, la implementación sería sencilla.

public void escribirAreaRectangulo(Rectangulo rectangulo){
     int area = rectangulo.calcularArea();
     System.out.println("El area del triangulo es " + area);
}

Ahora supongamos que queremos hacer lo mismo para un triangulo, pues siguiendo con este planteamiento implementamos lo mismo pero para un triangulo (copy-paste y un par de cambios)

public void escribirAreaTriangulo(Triangulo triangulo){
     int area = triangulo.calcularArea();
     System.out.println("El area del triangulo es " + area);
}

Y si ahora, siguiendo esta lógica queremos hacer lo mismo para un romboide, pues más de lo mismo, otro copy-paste y listo. Y así podríamos seguir indefinidamente añadiendo más y más código sin ningún problema.

¿Compila? Sí. ¿Funciona? También. Pero desde el punto de vista técnico de la ingeniería del software esto es una chapuza descomunal. Cuando se desarrolla software si es cierto que en última instancia lo más importante es que éste funcione y cumpla con unos requisitos establecidos, pero no es lo único importante, también es muy importante cómo se hace, como se implementa, el fin no justifica los medios, no todo vale con tal de que la aplicación funcione. Esto es algo que tristemente no se suele tener en cuenta y al final, más tarde o más temprano, suele pasar facturar con resultados lamentables.

Y es en este punto donde surge el polimorfismo como solución a nuestro problema. Nos aprovechamos de que todas las clases comparten el mismo interfaz, para declarar una variable del tipo común ('Figura') y ejecutar sobre ella cualquiera de los métodos definidos en su interfaz, de manera que en tiempo de ejecución esta variable "tomara la forma" de un objeto instanciado de cualquiera de las clases y sobre él se ejecutará la implementación correspondiente a su clase.

public void escribirArea(Figura figura){
     int area = figura.calcularArea();
     System.out.println("El area del triangulo es " + area);
}

De esta forma la variable 'figura' puede ser en tiempo de ejecución tanto un rectángulo, como un romboide o un triangulo, puesto que todas ellas son figuras e implementan el mismo interfaz, en este caso al método 'calcularArea()', aunque cada una de distinta manera dependiendo de su naturaleza.

Este es un ejemplo sencillo para entender qué es el polimorfismo. Su uso puede llegar a ser más complejo, como en los patrones de diseño, pero el concepto es el mismo.

No hay comentarios:

Publicar un comentario