sábado, 8 de diciembre de 2012

Herencia vs. Composición

Cuando se desarrolla software, entendiendo por desarrollo no sólo la implementación sino también las fases previas de análisis y diseño, uno de los objetivos perseguidos, o por lo menos así debería ser, es conseguir la máxima capacidad de reutilización de las distintas funcionalidades implementadas, y por lo tanto también del código.

En los entornos de programación orientados a objetos (p.ej. Java) existen básicamente dos formas de conseguir esto:

Mediante herencia de clases
Esta manera de desarrollar enfocada a la reutilización también es conocida como 'reutilización de caja blanca'. Este término se refiere a la visibilidad, porque en la herencia las interioridades de las clases padres suelen hacerse visibles a las subclases para poder definir una implementación en términos de otra.

La forma de representar la herencia en UML sería mediante una flecha (con la punta triangular vacia) dirigida desde la clase hija (subclase) a la clase padre (superclase).


En este caso, las subclases de la clase 'Mamifero' serían las clases: 'Perro', 'Tigre' y 'Oveja'. De forma que las clases hijo heredarían la interfaz e implementación de la clase padre 'Mamifero'
  • Ventajas
Se define estáticamente en tiempo de compilación, y no en tiempo de ejecución. Por otro lado al estar permitido por el lenguaje de programación resulta sencilla de usar e implementar. Y también hace que sea más fácil modificar la implementación que esta siendo reutilizada
  • Inconvenientes
Sin embargo, precisamente porque se define en tiempo de compilación, no se pueden cambiar las implementaciones heredadas de las clases padre en tiempo de ejecución. Así mismo también rompe la encapsulación al exponer a una subclase los detalles de implementación de su padre, de forma que cualquier cambio en la implementación del padre obligará a cambiar la subclase. Estas dependencias de implementación también pueden causar problemas al tratar de reutilizar una subclase. Cuando algún aspecto de la implementación heredada no sea apropiada para nuevos dominios, la clase padre deberá ser escrita de nuevo o reemplazada por otra más adecuada. Esto limita la flexibilidad y la reutilización. Una solución a esto es heredar sólo de clases abstractas, ya que éstas normalmente tienen poca o ninguna implementación.

Mediante composición  
Esta forma de desarrollar enfocada a la reutilización también es conocida como 'reutilización de caja negra', porque los detalles internos de los objetos no son visibles y la nueva funcionalidad se obtiene ensamblando o componiendo objetos para obtener una funcionalidad más compleja.

La forma de representar esta relación entre clases con UML es mediante una línea con un rombo relleno en el extremo de la clase que representa el "todo" y por lo tanto contiene a las "partes" de las que esta compuesto.


En este ejemplo la clase 'Cama' esta compuesta por las clases: 'Patas', 'Colchon' y 'Somier'. De forma que la existencia de las partes está necesariamente ligada a la existencia del todo que las contiene, es decir, que si la clase 'Cama' deja de existir también lo harán las clases que la componen.
  • Ventajas
Al contrario que la herencia la composición se define en tiempo de ejecución, y no en tiempo de compilación, a través de objetos que tienen referencias a otros objetos. Así mismo puesto que a los objetos se accede sólo a través de sus interfaces no se rompe la encapsulación. Cualquier objeto puede ser reemplazado por otro en tiempo de ejecución siempre que sea del mismo tipo. También ayuda a mantener cada clase encapsulada y centrada en una sola tarea. De esta forma nuestras clases y jerarquías de clases permanecerán pequeñas y más manejables. Y al haber mas objetos por tener menos clases, el comportamiento del sistema dependerá de las relaciones entre estos objetos en vez de estar definido en una clase.

Con esto podemos concluir que hay que favorecer la composición de objetos frente a la herencia de clases

Idealmente sólo crearíamos nuevos componentes para lograr la reutilización. Deberíamos ser capaces de conseguir toda la funcionalidad que necesitásemos simplemente ensamblando componentes existentes a través de la composición de objetos. Sin embargo, rara vez es éste el caso, puesto que el conjunto de componentes disponibles nunca es, en la práctica, lo suficientemente rico. Reutilizar mediante la herencia hace más fácil construir nuevos componentes que puedan ser combinados con los antiguos. Y de esta forma la herencia y la composición trabajan por lo tanto juntas.

2 comentarios: