Introducción
Herencia y polimorfismo son dos conceptos muy importantes en la programación orientada a objetos (POO de ahora en más) y saber cómo implementarlos nos puede potenciar nuestra capacidad para resolver problemas.
El ejemplo que vamos a resolver en este artículo es definir una jerarquía de clases en la que se tienen las siguientes clases: Device, Mouse y Teclado, las clases Mouse y Teclado tendrán sus propias propiedades y heredarán de la clase Device, como se ilustra en el diagrama de clases de la figura 1.
Tendremos funciones para crear nuevos Mouse y Teclados que se añadirán a una lista y tendremos definiremos la función ToString() en las tres clases, de tal forma que esta función devolverá un resultado dependiendo de la naturaleza del objeto al que se ejecute esta función.
En el siguiente vídeo se explica la implementación del ejercicio propuesto de herencia y polimorfismo en lenguaje C# para Unity.
Clic aquí para descargar el paquete de Unity con el ejercicio de herencia y polimorfismo
Repaso de los conceptos clave antes de pasar al ejemplo
Concepto de clases en programación
Las clases en programación son una estructura de datos que nos permiten modelar objetos con propiedades y funciones asociadas. Se utilizan como plantilla para crear instancias de estos objetos, cada una de estas instancias tiene la misma estructura (propiedades y funciones), pero tienen un estado individual, es decir que los valores almacenados en sus campos son propios de cada instancia.
Concepto de herencia en programación
Para resolver problemas utilizando POO lo que se hace es crear clases de programación que modelan un determinado objeto. La herencia nos permite crear clases que serán hijas de otras clases (super clases), esto permite reutilizar el código y aumentar la abstracción ya que las propiedades y funciones definidas en la clase de mayor jerarquía se heredarán a las clases de menor jerarquía, dando la posibilidad de utilizar esas funciones tal cual están, extender su comportamiento o reescribirlas completamente.
Una ventaja de esto es que podemos agrupar objetos de distinto tipo en colecciones (por ejemplo arrays) que sean del tipo de la super clase que tengan en común.
Concepto de polimorfismo en programación
El polimorfismo es la capacidad de los objetos de responder a un mismo mensaje (llamada a una función) de distintas formas.
Por ejemplo, supongamos que tenemos tres clases de programación, Persona, Trabajador y Estudiante, Trabajador y Estudiante heredan de la clase Persona, el razonamiento de esto es que los Trabajadores y Estudiantes son personas, solo que un grupo específico de ellas.
Ahora supongamos que dentro de estas tres clases hay una función llamada ´Introducirse´ que devuelve un string, al ejecutarlo sobre una persona el mensaje devuelto es: «´Mi nombre es´ + variableNombre». Si la función se ejecuta sobre un Trabajador, el mensaje es el mismo que el de una persona pero además se le añade: «´, soy un trabajador.». Si en cambio ejecutamos la función sobre un estudiante, al mensaje de Persona se le añadirá: «´, soy un estudiante.´».
Entonces tenemos una estructura de clases en la que los objetos de los tres tipos responden al mismo mensaje, pero el resultado de esa ejecución será distinto dependiendo el tipo de objeto del que se trate.
Ejemplo de herencia y polimorfismo en programación implementado en C#, Unity
Vamos a ver cómo implementar el sistema propuesto en la introducción de este artículo, se trata de una estructura de clases como la que se ilustra en la figura 1, la clase Device tiene un dato privado de tipo String llamado «identificationCode» y una función pública llamada «ToString()». Las clases Mouse y Keyboard heredan de la clase Device (lo cual tiene sentido ya que los mouse y teclados son en efecto dispositivos), estas clases tienen sus propios datos, Mouse tiene una variable privada tipo float llamada «dpi», que representan los DPI del mouse en cuestión y teclado tiene una variable privada entera llamada «numberOfKeys», que representa la cantidad de teclas que tiene el teclado en cuestión. Ambas clases responden al mensaje ToString().
Implementación de la clase Device (super clase)
En la figura 2 está la implementación de la clase Device en lenguaje C# en Unity, esta es la clase de la que van a heredar las clases Mousue y Device, noten que en la definición de la clase (línea 5 de la figura 2) se ve que la clase Device hereda de Object, la cual es la clase base que da origen a todas las demás clases.
En la línea 7 se declara el string «identificationCode» y en la línea 15 la función pública «ToString()», observen que está declarada como «virtual», con esto indicamos que la función «ToString» podrá ser sobreescrita por las subclases de Device. En la línea 10 está el constructor de la clase, una función que permite crear un objeto tipo Device, el constructor requiere como parámetro que se le pase el string con el que se iniciará la variable «identificationCode».
Implementación de las clases Mouse y Keyboard (sub clases)
En las figuras 3 y 4 vemos los Scripts en los que se definen las clases Mouse y Keyboard, subclases de la clase Device, observen que cada una tiene sus propios parámetros, DPI para la clase Mouse y número de teclas para la clase Keyboard. Cada una tiene su propio constructor que permite crear los objetos (líneas 9 de ambas figuras 3 y 4), observen que requieren como parámetro el string de identificación para la clase Device y en el caso de Mouse se piden los DPI, mientras que en el caso de Keyboard, además del ID se pide el número de teclas. En los constructores se hace la llamada al constructor de la subclase, la parte «: base(id)».
Luego tenemos las funciones «ToString()» (líneas 14 de ambas figuras 3 y 4), en la definición de la función se incluye la palabra «override» que indica que se va a sobreescribir la función ToString() de Device, pero esto no significa que se pierde el contenido de la función ToString() presente en Device. Esta puede ser reutilizado haciendo referencia a la superclase con la palabra «base», como se observa en las líneas 16 de ambas figuras, se ejecuta el método ToString() de la clase base (Device) y esto da como resultado lo que se muestra en la línea 17 de la figura 2; a esto se le adiciona otro mensaje que va a depender de la clase, Mouse responde con una frase y Keyboard responde con otra, aquí es donde se ilustra el concepto de polimorfismo en programación, una misma función que produce distintos resultados dependiendo del objeto del que se trate.
Ejemplo de un programa que crea y gestiona los objetos
La jerarquía de clases nos permite crear objetos de distintos tipos que tienen una super clase en común, la idea es que esos objetos los vamos a usar para resolver algún problema, por lo que se parte del problema y luego se propone una estructura de clases que facilitará la resolución del problema. En este caso vamos a tener un Script que será el administrador de los dispositivos en el que se podrá crear nuevos dispositivos, concretamente Mouse o Teclados y las referencias de esos objetos estarán almacenadas en una lista de dispositivos, el administrador tendrá funciones para listar los dispositivos o limpiar la lista. Esto es solo un ejemplo de cómo hacer uso de la estructura de clases propuestas.
Funciones para crear nuevos objetos
En la figura 5 se observan las funciones que permiten crear nuevos dispositivos, AddMouse() y AddKeyboard(), estas funciones las ejecutarán un conjunto de botones en la interfaz gráfica (consultar figura 7).
La función AddMouse genera datos aleatorios y luego crea un nuevo objeto tipo Mouse llamando al constructor de la clase Mouse (línea 34 de la figura 5), finalmente ejecuta el método AddDevice pasando como parámetro el Mouse creado. Análogamente la función AddKeyboard genera datos, crea el objeto y ejecuta AddDevice pasando como parámetro el objeto creado.
La función AddDevice recibe como parámetro un objeto de tipo Device y ya que, tanto Mouse como Keyboards son en esencia Devices (por la jerarquía de clases), es posible pasar estos objetos como parámetros por más que no sean específicamente objetos tipo Device. Dentro de la función no sabemos si el objeto que viene como parámetro es un Mouse o un Keyboard pero eso no importa, lo tratamos como un Device, se añade a la lista y se actualiza la información de la interfaz gráfica. Observen como en la línea 57 de la figura 5 se ejecuta la función ToString() sobre el device, esta función es polimórfica, si el Device es un Mouse se ejecuta el ToString de Mouse y si es un Keyboard se ejecuta el ToString de Keyboard.
Funciones para utilizar los objetos
Como un ejemplo de utilización de los objetos creados hice una función que permite listar todos los objetos, es decir generar un texto a partir de ejecutar la función polimórfica ToString de cada uno de los objetos de la colección, este texto se muestra en el panel de la figura 7. Esta función se ilustra en la figura 6, como se observa se genera rellena una variable tipo string llamada «list» con el resultado de la ejecución de la función ToString() sobre cada uno de los elementos presentes en la lista de dispositivos y luego ese texto se imprime en la interfaz gráfica. En este punto se evidencia el polimorfismo ya que ToString() es una función polimórfica cuyo resultado dependerá si el objeto dispositivo de la lista es un Mouse o un Teclado.