« ¡Temblad, madrileños! | Inicio | En caso de duda, composición ( II ) »

En caso de duda, composición ( I )

La forma en la que todos nos adentramos en el mundo de la programación orientada a objetos suele ser parecida. Tendemos a depender en exceso de la herencia, a realizar jerarquías de clases muy cerradas y muy estrictas, a solucionar cualquier problema, por pequeño que sea, a base de crear clases hijas particularizadas para ese problema. ¿Pero es ésa la mejor forma de organizar nuestro código?

Hace poco, alguien me dijo que discutir sobre herencia y composición era como discutir sobre pc vs mac. Yo creo que no es así. Voy a intentar explicar por qué

La herencia es un mecanismo muy poderoso, probablemente sea la herramienta más poderosa que nos ofrece cualquier lenguaje orientado a objetos, y probablemente, por eso también, la que es más susceptible de ser mal utilizada. Pero primero veamos qué es la herencia.

Básicamente, la herencia permite definir una clase utilizando como base otra clase ya existente. Veámoslo con un ejemplo claro ( probablemente, el ejemplo más utilizado de la historia ).

class Persona { function Persona( ) { } public function comer( ){} public function dormir( ){} public function hablar( ){} } class Programador extends Persona { function Programador( ){} } Class Camionero extends Persona { function Camionero( ){} }

Tanto el programador como el camionero ( en ambos casos, mientras no se demuestre lo contrario ) son personas, por lo que habrá una serie de comportamientos o acciones comunes a ambos ( comer, dormir, hablar ) que son las que les corresponden por el hecho de ser personas. Por tanto, aquí ya se nos han planteado los dos conceptos que van más unidos a la herencia.

Por un lado, la herencia permite la reutilización de código. Cuando haya varias clases que tengan que implementar una serie de métodos comunes, y para evitar tener el mismo código ( o código muy parecido ) en muchos sitios, podemos simplemente implementar ese código común en una clase base, y hacer que el resto de clases hereden de ésta.

Por otra parte, la herencia también sirve para agrupar las clases conceptualmente. En este caso, tanto el programador como el camionero son personas, por lo tanto, es lógico que las clases que los representan hereden de la clase persona. De hecho, en todos los libros de programación orientada a objetos, ésta es la norma que se da para saber cuándo se debe hacer que una clase herede de otra, la famosa "es-un". El programador "es una" persona, el camionero "es una" persona, por lo tanto deben heredar de la clase persona.

Además, de esta manera, el polimorfismo es inmediato. Podemos utilizar una variable del tipo de la clase base para almacenar una instancia de cualquiera de sus subclases. En nuestro ejemplo, una variable de tipo Persona nos permitirá hacer referencia a un Programador, a un Camionero, etc.

La relación de composición, por el contrario, es una relación de pertenencia. Veámoslo con un ejemplo.

class Programador extends Persona { var mascota: Mascota; function Programador( ) { this.mascota = new Mascota( ); } }

El programador tiene una mascota. Ésa es la palabra clave para definir una relación de composición ( "tiene un" ).

Bien, hasta ahora todo parece estar bastante claro. Entonces, ¿porqué el título del post es "en caso de duda, composición"?. ¿Es que puede haber duda ?.

Pues sí, hay dudas, y muchas, sobre si la herencia es realmente tan beneficiosa como parece. Veamos algunos casos en los que las implementaciones basadas en la herencia no son la mejor solución de entre todas las posibles.

En primer lugar, vamos a replantearnos el primer ejemplo. Ahora, vamos a implementar una clase "Trabajador", que es la que va a tener todas las características propias de una persona que tiene un trabajo, y que, obviamente, heredará de la clase Persona ( un trabajador "es una" Persona ).

class Trabajador extends Persona { function Trabajador( ){} public function entrarEnElTrabajo( ){} public function trabajar( ){} public function salirDelTrabajo( ){} }

Por lo tanto, ahora, las clases Programador y Camionero serían:

class Programador extends Trabajador { } class Camionero extends Trabajador { }

De este modo, tanto el Programador como el Camionero heredarían los métodos de la clase Persona y los de la clase Trabajador.

Bien, tenemos una clase base ( Persona ) de la que extiende la clase Trabajador ( Un trabajador es una persona ), de la que a su vez extienden la clase Programador ( un Programador es un trabajador ) y Camionero ( que también es un trabajador ). ¿Correcto?. Bueno, pues no del todo. ¿Porqué?. Pues porque un programador no es sólo un trabajador. De hecho, algunos programadores, cuando salen de trabajar se convierten en novios, otros en amigos de alguien, otros en cinéfilos, etc. Luego podemos decir que un programador no es siempre un trabajador.

Bien, pues como un programador no es siempre y en todo caso un trabajador, no debería extender de la clase Trabajador, porque su comportamiento no va a ser siempre el de Trabajador. Dicho de otra forma, la prueba de "es un" para decidir si heredar, debería refinarse más, y ser "es un y sólo un y siempre un".

En este caso, por ejemplo, la solución podría pasar por que la funcionalidad correspondiente a los trabajadores formara parte de un interfaz que implementaran tanto el programador como el camionero. O, mejor aún, las clases Programador y Camionero pueden tener una variable de clase que sea una instancia de la clase Trabajador.

class Programador extends Persona { var workerPart: Trabajador = new Trabajador( ); public function entrarEnElTrabajo( ) { this.workerPart.entrarEnElTrabajo( ); } }

Esta solución nos trae otra ventaja. Si tuviéramos que cambiar el perfil de alguno de los métodos de la clase Trabajador, ese cambio sólo afectaría a las clases Programador y Camionero, ya que podríamos resolver ese cambio de perfil en ellas, por lo que el resto de clases que interactúen con ellas no tienen porqué "notar" ese cambio. Hemos ganado también en facilidad de refactorización.

Pero no acaban ahí los problemas. Seguiremos en el próximo post.