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!!
Publicado por: Ra˙l JimÈnez | Octubre 19, 2004 10:28 AM
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.
Publicado por: Julio Garcia | Octubre 21, 2004 02:49 PM
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 ).
Publicado por: Cesar Tardaguila | Octubre 21, 2004 03:10 PM