Durante el aprendizaje de la programación orientada a objetos, y después de dominar los conceptos básicos de objetos, campos y métodos, se dedica la mayor parte del tiempo a la herencia. Herencia significa que adquirimos parte de la implementación de una clase base. Basta con crear una subclase de una clase base para heredar todos los campos y métodos no privados.
Un coche y un avión son vehículos, por lo que es obvio que ambas clases deben expandirse a partir de su clase base común llamada Vehículo. Este es un ejemplo académico típico, pero al decidir sobre la vinculación de estas clases con la relación de herencia, debemos ser conscientes de algunas consecuencias.
Fig. 1 Implementación de la relación de herencia.
En este caso las clases están estrechamente vinculadas entre sí - esto significa que los cambios en el comportamiento de cada clase se puede lograr mediante la realización de cambios en la clase base código. Esto puede ser tanto una ventaja como una desventaja - depende del tipo de comportamiento que esperemos. Si la herencia se aplica en el momento equivocado, el proceso de añadir una nueva función puede encontrar algunas dificultades de implementación porque no se ajustará al modelo de clase creado. Tendremos que elegir entre duplicar el código o reorganizar nuestro modelo, y puede ser un proceso que lleve mucho tiempo. Podemos llamar al código que ejecuta la relación de herencia como "abierto-cerrado" -esto significa que está abierto para extensiones pero cerrado para modificaciones. Asumiendo que en la clase Vehículo hay una operación de motor general, definida, de cada vehículo, en el momento en que quisiéramos añadir un modelo de vehículo sin motor (por ejemplo, moto) a nuestra jerarquía de clases, tendríamos que hacer algunos cambios serios en nuestras clases.
clase Vehículo
def arrancar_motor
end
def parar_motor
end
fin
clase Plane < Vehículo
def mover
arrancar_motor
...
parar_motor
end
fin
Composición
Si sólo nos interesa una parte del comportamiento de la clase existente, una buena alternativa a la herencia es utilizar la composición. En lugar de crear subclases que hereden todos los comportamientos (los que necesitamos y los que no necesitamos en absoluto), podemos aislar las funciones que necesitamos, y equipar nuestros objetos en referencias a ellas. De este modo, abandonamos la idea de que objeto es un tipo de un objeto base, a favor de la afirmación de que contiene sólo algunas partes de sus propiedades.
Fig. 2 Utilización de la composición
Siguiendo este enfoque podemos aislar el código que es responsable del funcionamiento del motor a la clase autónoma llamada Motor y colocar referencia a él sólo en las clases que representan vehículos con motor. Aislar las funciones con el uso de la composición hará que la estructura de la clase Vehículo sea más simple y reforzará la encapsulación de las clases individuales. Ahora, la única forma en que los vehículos pueden tener un efecto sobre el motor es utilizando su interfaz pública, porque ya no tendrán información sobre su implementación. Es más, permitirá usar diferentes tipos de motores en diferentes vehículos, e incluso permitir su intercambio mientras el programa se está ejecutando. Por supuesto, el uso de la composición no es impecable: estamos creando un conjunto de clases vagamente conectadas, que puede ampliarse fácilmente y está abierto a modificaciones. Pero, al mismo tiempo, cada clase está conectada a muchas otras clases, y debe tener información sobre sus interfaces.
clase Vehículo
fin
clase Motor
def inicio
end
def parada
end
end
class Avión < Vehículo
def inicializar
@motor = Motor.nuevo
end
def mover
@motor.iniciar
@motor.parada
end
def cambiar_motor(nuevo_motor)
@motor = nuevo_motor
end
fin
La elección
Ambos enfoques descritos tienen ventajas y desventajas, así que ¿cómo elegir entre ellos? La herencia es una especialización, por lo que es mejor aplicarlos sólo para los problemas, en los que hay "es-una" relaciones de tipo - por lo que nos ocupamos de la jerarquía real de los tipos. Debido a que la herencia vincula estrechamente las clases entre sí, en primer lugar, siempre debemos considerar si se debe utilizar la composición o no. La composición debe aplicarse a problemas en los que existan relaciones de tipo "tiene-un", es decir, la clase tiene muchas partes, pero es algo más que un conjunto de clases. Un avión consta de partes, pero por sí solo es algo más: tiene capacidades adicionales, como volar. Siguiendo con este ejemplo, las partes individuales pueden aparecer en diferentes variantes especializadas, y entonces es un buen momento para utilizar la herencia.
Tanto la herencia como la composición son sólo herramientas que los programadores tienen a su disposición, por lo que elegir la herramienta adecuada para un problema concreto requiere experiencia. Así que practiquemos y aprendamos de nuestros errores 🙂 .