Herencia
La herencia es una forma de reutilizar código en la programación orientada a objetos. A través de herencia, una clase puede recibir los métodos de otra clase.
Veamos un ejemplo para aclarar este concepto. Supongamos que estamos creando una aplicación de dibujo. En la mayoría de aplicaciones de dibujo puedes agregar formas: círculos, cuadrados, etc:
Todas las clases están repitiendo los atributos stroke
y fill
que corresponden a la línea y el relleno de la figura. ¿Cómo podemos hacer para no repetir los mismos atributos en todas las figuras?
Acá es donde entra la herencia. Podemos crear una clase padre llamada Figure
que va a contener todos los métodos que todas las figuras tienen en común. La sintaxis para indicar que una clase hereda (también vas a escuchar extender) de otra clase es la siguiente: a la definición de la clase hija le agregas el operador <
seguido del nombre de la clase padre. Por ejemplo:
En este caso Circle
, Square
y Triangle
heredan de Figure
, es decir, que tienen todos los métodos se definan en Figure
(en este caso los métodos para leer y escribir stroke
y fill
). Por ejemplo:
Aunque Circle
no tiene los métodos para leer y escribir fill
, este código funciona porque Circle
extiende (o hereda) de la clase Figure
, que sí tiene esos métodos.
Nota: Una clase padre puede tener muchas clases hijas, pero una clase hija solo puede tener un padre.
La ventaja de utilizar herencia es que si necesitas que agregar más métodos a la clase padre, todas las clases hijas reciben ese método automáticamente. Por ejemplo, supongamos que agregamos otros atributos a Figure
:
Ahora todas las clases que extiendan de Figure
también tendrán los métodos para leer y escribir los atributos x
, y
, shadow
.
Jerarquía de clases
Una clase puede ser padre de muchas otras clases, e hija de otra al mismo tiempo.
Siguiendo con nuestra aplicación de dibujo, supongamos que queremos implementar la figura cilindro. Un cilindro es como un círculo, pero también tiene una longitud, así que nuestra jerarquía de clases sería la siguiente:
Circle
es hija de Figure
y padre de Cylinder
. A medida que se van creando más clases se va creando una jerarquía de clases.
Object
Por ejemplo, a pesar de que Figure
en nuestro ejemplo anterior parece no heredar de ninguna clase, realmente está heredando de Object
, así que puedes llamar cualquier método que esté definido en Object
:
Nota: Object
extiende de otra clase llamada BasicObject
, que es la raíz de toda la jerarquía de clases en Ruby, pero esa es una clase vacía, así que en la práctica puedes pensar en Object
como la clase raíz.
Los que hemos vistos hasta acá son los conceptos básicos de la herencia. Sin embargo, hay varios detalles que surgen, por ejemplo:
¿El constructor de la clase padre se llama cuando creo una instancia de la clase hija?
¿Qué pasa si la clase hija tiene un método que se llama igual que uno de la clase padre?
¿Cómo puedo llamar métodos de la clase padre?
Empecemos a responder cada una de estas preguntas:
Herencia y el constructor
Considera el siguiente código:
¿Qué pasa si ejecutamos Child.new
, se imprimirá "Este es el constructor de Parent"?
La respuesta es no. Los constructores son independientes. Si quieres invocar el constructor del padre debes hacerlo explícitamente utilizando la palabra clave super
:
La palabra super
debe ser la primera línea dentro de initialize
. Si aparece después de cualquier otra línea se genera un error:
Volvamos a nuestro ejemplo de la aplicación de dibujo para ver cómo funcionaría esto:
La clase Figure
define un constructor que recibe dos argumentos: stroke
y fill
, que inicializa @stroke
y @fill
.
Circle
define un constructor que recibe tres argumentos: stroke
, fill
y radius
. Utiliza super
para llamar el constructor de Figure
e inicializa @radius
.
El llamado a super
es equivalente a copiar el código que existe en la clase padre y reemplazarlo por el super
:
Sobrescribiendo métodos
Es posible sobreescribir los métodos de la clase padre definiendo un método que se llame igual en la clase hija. Por ejemplo, es muy común sobrescribir el método to_s
que está definido en Object
:
Nota: No es necesario que el método esté definido directamente en la clase padre, puede estar más arriba en la jerarquía como en este ejemplo.
Ahora, cuando llamemos el método to_s
sobre cualquier instancia de Circle
veremos la frase "Este es un círculo con radio x":
Puedes utilizar la palabra clave super
para invocar el método en la clase padre. Imagina que estamos haciendo una aplicación para calcular el salario de los empleados. A todos los empleados de la compañía se les calcula el salario de la misma forma, exceptuando a los directores que reciben un bono adicional.
En este ejemplo Manager
sobrescribe el método calculate_salary
de Employee
y utiliza super
para invocar calculate_salary
de Employee
. El resultado lo almacena en una variable llamada base_salary
que después suma al bono. De esa forma no es necesario copiar todo el código que ya teníamos en calculate_salary
de Employee
, que podría ser bastante!
¿Cuándo utilizar herencia?
Resumen
La herencia es una forma de reutilizar código. Para indicar que una clase hereda de otra se utiliza el operador <
en la definición de la clase seguido del nombre de la clase que se quiere extender (heredar). Por ejemplo:
Cuando una clase extiende (hereda) de otra, la clase hija recibe todos los métodos de la clase padre, incluyendo aquellos que la clase padre haya heredado de otras clases.
En Ruby una clase solo puede extender de una sola clase (solo puede tener un padre). Sin embargo, una clase puede tener muchas hijas.
Todos los objetos que no extiendan de otra clase, extienden de Object
implícitamente.
El constructor no se hereda. Sin embargo, se puede utilizar la palabra super
para invocar el constructor de la clase padre.
Es posible sobrescribir métodos de la clase padre creando un método con el mismo nombre en la clase hija. El método en la clase hija puede utiliza la palabra super
para invocar el método de la clase padre.
La herencia
Ejercicios
Last updated