El Polimorfismo es la Clave

0
4054

 

En este artículo analizamos, a través de un ejemplo, el Polimorfismo como característica clave de los lenguajes orientados a objetos.

Índice de contenido

1. Introducción

Clean Code, Principios de diseño (SOLID, DRY, IoC…), inyección de dependencias, patrones de diseño… son todos conceptos, técnicas, principios, que nos aportan conocimientos indispensables para desarrollar software de calidad, pero hay una característica de los lenguajes orientados a objetos que es clave para implementar y comprender la mayoría de estos conceptos más avanzados: El polimorfismo.

La palabra Polimorfismo significa «múltiples formas». En el caso de la programación orientada a objetos (POO) el polimorfismo está más ligado al comportamiento, por lo que pudiéramos ver más su significado como «múltiples comportamientos». Más formalmente podemos definir el polimorfismo como la característica que le permite a un cliente enviar un mensaje a objetos de diferentes tipos sin conocer exactamente el tipo que se está usando. El único requisito que deben de cumplir estos objetos es saber responder a dicho mensaje. De esta manera, bajo un mismo símbolo (el método o mensaje usado) se van a ejecutar comportamientos diferentes dependiendo de a qué objeto apunte la variable de referencia que usamos para enviar el mensaje.

Aunque en este artículo nos vamos a centrar en el polimorfismo de tipo inclusión o controlado por la herencia (subtype polymorphism), existen otros tipos de polimorfismos que es conveniente conocer.

2. Tipos de polimorfismo

Luca Cardelli en el artículo On Understanding Types, Data Abstraction, and Polymorphism agrupa los tipos de polimorfismo en las siguientes categorías:

Universal (True polymorphism)

  • Parametric: Se refiere a la parametrización de tipos (Genericidad).
  • Inclusion o subtyping: Es al que nos referimos casi siempre cuando hablamos de polimorfismo en los lenguajes de POO. Está basado en la herencia/implementación de clases/interfaces.

 

Ad-hoc (Apparent polymorphism)

  • Overloading: Sobrecarga de métodos.
  • Coersion: Casting implicito y explicito.

 

Solo la primera categoría agrupa los tipos que se conocen como polimorfismo verdadero porque es en tiempo de ejecución cuando se determina que comportamiento ejecutar (Late binding). El resto se resuelve en tiempo de compilación, por lo que podemos decir que es un polimorfismo aparente.

Por ejemplo, en la sobrecarga de métodos (Overloading) puede parecer que el método sobrecargado se comporta de varias formas, pero en realidad se invoca a distintos métodos dependiendo del número, orden y tipo de los parámetros. Es decir, el compilador reemplaza la llamada al método a partir de la signatura de éste (Static binding).

3. Un ejemplo

Veamos el uso del polimorfismo a través de un ejemplo sencillo. Los requisitos son los siguientes: dado un texto plano y el nombre de un algoritmo de encriptación, el programa tiene que imprimir por pantalla el texto encriptado utilizando dicho algoritmo. Para implementarlo vamos a utilizar el patrón de diseño Strategy combinado con una factoría que nos dará el algoritmo de encriptación a utilizar a partir del nombre de éste.

import java.util.HashMap;
import java.util.Map;

public class PolymorphismDemo {

    public static final void main(String[] args) {
        String plainText = args[0];
        Algorithm algorithm = Algorithm.valueOf(args[1]);

        EncryptionStrategy encryptionStrategy = EncryptionStrategyFactory.getInstance(algorithm);

        String encryptedText = encryptionStrategy.encrypt(plainText);

        System.out.println(
            String.format("Text encrypted using '%s' algorithm: %s", 
                algorithm, encryptedText)
        );
    }

    enum Algorithm {
        AES, TWOFISH
    }

    interface EncryptionStrategy {
        String encrypt(String input);
    }

    static class EncryptionStrategyFactory {

        public static Map<Algorithm, EncryptionStrategy> encryptionStrategies = new 
            HashMap<Algorithm, EncryptionStrategy>();

        static {
            registerStrategy(Algorithm.AES, (input) -> "aes:" + input);
            registerStrategy(Algorithm.TWOFISH, (input) -> "twofish:" + input);
        }

        public static EncryptionStrategy getInstance(Algorithm name) {
            return encryptionStrategies.get(name);
        }

        public static void registerStrategy(Algorithm algorithm, EncryptionStrategy encryptionStrategy) {
            encryptionStrategies.put(algorithm, encryptionStrategy);
        }

    }

}

 

En la línea 12 es donde ocurre la magia. El cliente, a través del mensaje ‘encrypt’, va a ejecutar comportamientos diferentes dependiendo de a qué estrategia concreta apunta la variable encryptionStrategy, sin conocer realmente el tipo de algoritmo que se está utilizando.

Indiscutiblemente estamos aplicando otros conceptos de la POO. Nos estamos abstrayendo porque el código cliente que usa el algoritmo de encriptación está expresando en términos generales, además representamos el comportamiento común en la abstracción EncryptionStrategy. Encapsulando porque los detalles de cada implementación se ocultan. Aunque no usemos la herencia, las estrategias concretas implementan la interfaz EncryptionStrategy creando una relación Is-A entre diferentes objetos. Pero realmente, es la posibilidad de ejecutar comportamientos diferentes bajo un mismo mensaje lo que le da flexibilidad al código.

Si hacemos una análisis de los conceptos que hemos aplicado. Tenemos el principio de Abierto/Cerrado, añadir nuevos algoritmos no implica modificar el código cliente, solo tendríamos que registrarlo en la factoría. Tenemos el patrón de diseño Strategy, que nos da solución a la problemática de cambiar algoritmos en tiempo de ejecución. Y en la base, como característica del lenguaje, tenemos el polimorfismo, que nos permite ejecutar distintos algoritmos de encriptación utilizando una misma llamada (encrypt(input)) sin conocer exactamente el algoritmo específico que se está utilizando.

3. Conclusiones

Es importante entender el concepto de polimorfismo como base para entender otros conceptos más avanzados. Principios de diseño como Abierto/Cerrado e IoC, técnicas como la inyección de dependencias y muchas de las implementaciones de patrones de diseños tienen como base esta característica clave del lenguaje.

3. Referencias

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad