Herencia vs Composición (C++).

   Uno de los aspectos más importantes del software en general, y de la programación orientada a objetos en particular, es la conveniencia de la reutilización de código por medio de la abstracción. En este sentido, dos de los esquemas más comunes al respecto son: la herencia y la composición.

   Esta sección muestra, utilizando un ejemplo ya conocido y presentado previamente al lector, la implementación de una pila por medio de una lista enlazada. Dicha implementación se realiza empleando los dos esquemas mencionados con anterioridad. Así mismo, se analizan las ventajas y desventajas de cada uno de ellos.

   Implementación de una pila utilizando herencia.
   La implementación de una pila por medio de una lista con un enfoque basado en la herencia es en realidad bastante simple. Para muestra, basta con ver el código del código del Ejemplo pila_herencia.

   Observe que la clase pila del ejemplo (líneas 57-64) no define atributos, y que únicamente define dos constructores y los métodos push y pop; lo cual también ha sido representado en el diagrama de clases UML de la siguiente figura:

Diagrama de clases UML para la implementación de una pila utilizando herencia y una lista enlazada.

    Insto nueva y amablemente al lector a que compare, contraste, y analice con detenimiento antes de continuar, el diagrama de la figura anterior con el Ejemplo pila_herencia.

   Note que los métodos push (líneas 234-237) y pop (líneas 239-242) del ejemplo no hacen otra cosa más que encapsular el comportamiento de los métodos inserta_al_inicio y elimina_del_inicio respectivamente, los cuales son servicios o comportamiento definidos en la clase lista. Note también que es posible acceder a dichos métodos a través de la referencia this debido a que la clase pila hereda de la clase lista (línea 58).

   Observe también que como parte del mecanismo de la herencia inherente tampoco es necesario definir el comportamiento de los métodos esta_vacia e imprime dentro de la clase pila, ya que se encuentran definidos en la clase lista. El primero de dichos comportamientos forma parte de la definición de las primitivas de la estructura de datos pila, mientras que el segundo es utilizado en la función main del programa principal.

La salida del ejemplo en turno se muestra a continuación:

La Pila genérica es: 0
La Pila genérica es: 1 0
La Pila genérica es: 2 1 0
La Pila genérica es: 3 2 1 0
La Pila genérica es: 4 3 2 1 0
La Pila genérica es: 5 4 3 2 1 0
La Pila genérica es: 6 5 4 3 2 1 0
La Pila genérica es: 7 6 5 4 3 2 1 0
La Pila genérica es: 8 7 6 5 4 3 2 1 0
La Pila genérica es: 9 8 7 6 5 4 3 2 1 0

Elemento eliminado de la pila: 9
La Pila genérica es: 8 7 6 5 4 3 2 1 0
Elemento eliminado de la pila: 8
La Pila genérica es: 7 6 5 4 3 2 1 0
Elemento eliminado de la pila: 7
La Pila genérica es: 6 5 4 3 2 1 0
Elemento eliminado de la pila: 6
La Pila genérica es: 5 4 3 2 1 0
Elemento eliminado de la pila: 5
La Pila genérica es: 4 3 2 1 0
Elemento eliminado de la pila: 4
La Pila genérica es: 3 2 1 0
Elemento eliminado de la pila: 3
La Pila genérica es: 2 1 0
Elemento eliminado de la pila: 2
La Pila genérica es: 1 0
Elemento eliminado de la pila: 1
La Pila genérica es: 0
Elemento eliminado de la pila: 0
Vacia: Pila genérica


 
    Consideraciones.
   Al menos en apariencia, el mecanismo de la herencia resulta sumamente conveniente con base en lo expuesto con anterioridad; sin embargo, presenta algunos inconvenientes potenciales que vale la pena considerar.

   Las instancias de la clase pila, al heredar las características y el comportamiento inherentes a una lista enlazada, pueden hacer uso de los métodos de inserción y de eliminación correspondientes a ésta, por lo que desde esta perspectiva, un objeto de dicha clase podría permitir inserciones y eliminaciones, no únicamente del tope de la pila (representado por inicio), sino también de la base de la pila ("representada" por fin) a la que se supone, por definición de pila, no se debe tener acceso.

   Tome en consideración también que todavía se podría transgredir aún más la definición de una pila, ya que potencialmente es posible insertar en, o eliminar de cualquier parte de la estructura de datos con las modificaciones correspondientes (vea el Ejercicio 2.2 de los Ejercicios selectos), lo cual queda completamente fuera tanto de la definición que se hizo de la pila, como de las operaciones primitivas que le son inherentes.

Implementación de una pila utilizando composición.
   La implementación de una pila utilizando el enfoque de composición, se basa en la idea de que una lista enlazada contiene ya definidas las operaciones que necesita una pila pero que también, como se comentó con anterioridad, contiene otras operaciones que no deberían ser utilizadas por la misma.

   En función de lo anterior y de manera conveniente, lo que puede hacerse es encapsular las operaciones de la lista enlazada dentro de las de la pila con la finalidad de proporcionar una interfaz o un conjunto de servicios ad hoc con la definición de la estructura de datos en cuestión: la pila.

Diagrama de clases UML para la implementación de una pila utilizando composición y una lista enlazada.

    La figura anterior muestra el diseño en diagrama de clases UML la definición de una pila que utiliza o contiene (has-a) una lista enlazada para su implementación. Observe y compare con detenimiento dicho diagrama con el respectivo diagrama UML para la implementación con herencia, y note que el cambio principal está en la clase pila y en la relación entre ésta y la clase lista.

   Ahora bien, la definición de la clase pila se muestra en el Ejemplo pila_composición. Insto una vez más al lector a que ponga atención en dos aspectos principalmente:
  1. La relación entre el diagrama de clases de la figura anterior y el Ejemplo pila_composición.
  2. Los métodos push (líneas 236-239), pop (líneas 241-244) e imprime (líneas 246-249) no hacen otra cosa más que encapsular de manera conveniente los mensajes enviados al objeto tope (línea 59), mismos que son llevados a cabo por los métodos correspondientes definidos en la clase lista.
   Con base en lo anterior, los objetos instanciados de la clase pila sólo podrán acceder a los servicios definidos explícitamente en dicha clase y a ningún otro más, independientemente de los métodos que posea la clase lista, lo cuál hace que, desde el punto de vista de la abstracción y la representación de la estructura de datos, el enfoque de la composición sea mucho más conveniente, aunque un poco más laborioso que el de la herencia. Por otro lado, para implementar una pila utilizando una lista enlazada, la cantidad nueva de código se reduce significativamente; sin embargo, si bien es cierto que uno de los enfoques del paradigma orientado a objetos se basa en la reutilización del código, es importante que el lector se haga consciente de estos dos esquemas que se han comentado, ya que cada uno de ellos tiene sus ventajas y desventajas y la decisión final de implementación debería tomarse con pleno conocimiento de causa, ya que no siempre es mejor tomar el camino más fácil (herencia) simplemente por conveniencia.

   El programa principal para el ejemplo en turno sigue el mismo mecanismo de inserción que el los ejemplos de prueba anteriores. Finalmente, compruebe que la salida de este ejemplo coincide exactamente con la salida correspondiente al de la herencia, y que desde el punto de vista de la ejecución no se podría saber cuál fue el enfoque de diseño elegido.

No hay comentarios.:

Publicar un comentario