« En caso de duda, composición ( II ) | Inicio | [software] Maxivista »

En caso de duda, composición ( y III )

Aparte de las consideraciones expuestas en el post anterior, hay un par de razones para favorecer la composición sobre la herencia que probablemente sean de mucho más peso.

En primer lugar, la herencia no permite cambiar las implementaciones de las clases heredadas en tiempo de ejecución, y además, rompe la encapsulación, ya que la clase base es visible a través de las clases heredadas.

Sin embargo, la composición de objetos se suele hacer dinámicamente en tiempo de ejecución, por lo que requiere que todos los objetos tengan muy presentes las interfaces de los otros, lo cual nos obliga a tener mucho cuidado al implementar las interfaces de todos los objetos de nuestro sistema, lo cual, a su vez, favorece la encapsulación, ya que el único conocimiento que van a tener unos objetos de otros será el de la interfaz.

Pero además, la composición de objetos tiene otra gran ventaja. Favorecer la composición frente a la herencia, hace que nuestros programas sean más una especie de puzzle formado por pequeñas piezas ( objetos ), altamente< encapsuladas, y que se encargan cada una de una sola tarea. De esta manera, las jerarquías de clases no se convertirán en monstruos inmanejables, y no sólo obtendremos esa ventaja, sino que al estar basado en la composición de objetos, y no en clases, el comportamiento del sistema podrá cambiar más fácilmente en tiempo de ejecución, y no estará determinado por el código que hayamos escrito en una clase. Por tanto, será mucho más flexible y adaptable.

Volvemos a intentar explicarlo con un ejemplo. Supongamos que estamos trabajando en una aplicación de gestión de, por ejemplo, un gimnasio. Una de las partes de la aplicación debe mostrar un listado de los socios del gimnasio, que se carga de un XML.

Bien, pues podemos implementar una clase que extienda de XML, y que se encargue de cargar ese archivo, parsearlo, y devuelva un array de objetos con los datos de todos los socios.

Pero, ¿qué pasa si el resto del programa cambia, y ya no necesitamos un array de objetos con esos datos, sino otra estructura distinta?. Bueno, pues podemos escribir otra clase, que extienda también XML, y con un método distinto de parseo, que devuelva la estructura de datos que necesitamos. O incluso, extender la clase que escribimos en un primer momento, y sobrescribir su método de parseo. ¿ Y si ahora nos dicen que el parseo tiene que tener cierta lógica, porque el XML tiene cierta semántica? ( con valores dependientes del valor de algún atributo, por ejemplo ). ¿Escribimos otra clase nueva?.

¿ Y si desde un primer momento atacamos el problema siguiendo el ejemplo del puzzle? ¿Porqué no hacemos una clase que simplemente cargue un XML y devuelva un objeto XMLNode con el contenido del firstChild, y dejamos que sea el objeto que necesite de esos datos el que se encargue de parsearlos y materializar al estructura en memoria que necesite?. ¿Porqué?. Entre otras razones, porque realizar ambas tareas ( carga y parseo ) implica que la clase encargada de la carga tenga conocimiento de cómo y para qué quiere los datos el modelo de la aplicación . No sólo ganamos en flexibilidad en este caso, sino que tenemos un componente ( la clase que carga un xml y devuelve un XMLNode ) que podremos utilizar en todos y cada uno de nuestros proyectos.. Aparte de otros beneficios como el desacoplar la estructura de datos del servicio a través del cual se obtiene, encapsular los datos y la forma de obtenerlos, etc.

Visto así, parece que la solución ideal es la composición, ¿no?. Bueno, también tiene sus puntos oscuros. En primer lugar, hemos dicho que el basar nuestros sistemas en piezas que se componen en tiempo de ejecución favorece la flexibilidad y adaptabilidad de los mismos, pero eso hace también que sean más propensos a que aparezcan bugs no previstos, y además, que esos bugs sean más difíciles de debugear que en un sistema con un comportamiento más restrictivo. Además, el código basado en composición puede ejecutarse de forma más lenta que el basado en herencia, lo cual es importante cuando hablamos de flash.

Por todo lo que he intentado explicar, considero que es conveniente favorecer la composición de objetos frente a la herencia de clases. Eso no quiere decir que no debamos heredar nunca. Sólo que, en una situación ideal, lo único que tendríamos que hacer es ir creando nuevos componentes según los fuéramos necesitando. Aunque, en realidad, siempre vamos a necesitar de la unión de estos dos mecanismos.

Volviendo al ejemplo de la carga del XML. Habíamos visto la posibilidad de escribir una clase que cargara el XML, y devolviera un XMLNode con su contenido. Parece lógico ( realmente, casi obligado ) hacer que esa clase extienda de XML. Igualmente, la clase encargada del parseo, que recordemos, no tiene porqué ser siempre la misma, ni parsear de la misma forma, debería, o bien extender de una clase "abstracta" que implementara el método de parseo, o dada la falta de clases abstractas en actionScript, implementar un interfaz en el que se definiera ese método. Pero lo que no es lógico es que una sola clase tenga asignada ambas responsabilidades

Dicho de otra forma, la herencia no es mala. Muchas veces es incluso casi obligatoria, pero siempre que la utilicemos ( al igual que la composición ) para solucionar nuestros problemas, no como una imposición. Y la mayoría de las veces, nuestros problemas se van a resolver mejor componiendo que heredando.

Comentarios

¡¡Qué bueno el artículo!!

La verdad es que a mi la herencia siempre me ha dado un poco de miedo por lo que decís, y si tengo que cambiar mi superclase? Pues probablemente se me vaya todo al carajo, jeje.

De esta forma que decís vosotros está muy bien, aunque se me hace un tanto confusa ahora mismo, pero todo es practicar ;)

Gracias y un saludo!!

Muy buenos articulos. Sería, sin embargo, interesante comentar uno de los casos que, particularmente en flash, con más frecuencia causan dolores de cabeza al decidir entre herencia y composicion, es decir el caso de los MovieClips. Crear una clase que hereda de la clase MovieClip tiene el problema adicional, que MovieClip es una clase dynamica. Esto ocasiona que el compilador no cheque la existencia de métodos o propiedades a la hora de compilar.

Y no sólo en lo que al chequeo de tipos se refiere, sino sobre todo conceptualmente. Muchas veces se utilizan clases que extienden de MovieClip para representar entidades con comportamientos complejos, o que realmente no SON MovieClips, sino que se pueden representar gráficamente con un MovieClip ( que es algo muy distinto ).