Mostrando las entradas con la etiqueta Servicios. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Servicios. Mostrar todas las entradas

8 de mayo de 2017

Herencia vs. composición (listas).

   Uno de los aspectos más importantes de la programación orientada a objetos 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 entrada muestra, por medio de un ejemplo ya conocido y presentado previamente al lector, la implementación de una pila utilizando 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 PilaH.

   Observe que la clase del Ejemplo PilaH 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, a la figura anterior con el Ejemplo PilaH.

   Note que los métodos push (líneas 14-16) y pop (líneas 18-20) del Ejemplo PilaH no hacen otra cosa más que encapsular el comportamiento de los métodos insertaAlInicio y eliminaDelInicio respectivamente, los cuales son servicios o comportamiento definidos en la clase Lista del Ejemplo Lista. Note también que es posible acceder a dichos métodos debido a que la clase PilaH hereda de la clase Lista (línea 5).

   Observe que como parte del mecanismo de la herencia, tampoco es necesario definir el comportamiento de los métodos estaVacia e imprime dentro de la clase PilaH, 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 clase PruebaPilaH del Ejemplo PruebaPilaH (líneas 11 y 20), la cual es la clase de prueba del Ejemplo en cuestión.

La salida del Ejemplo PruebaPilaH se muestra en la siguiente figura:

Salida del Ejemplo PruebaPilaH.
 
    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 PilaH, 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 que todavía se podría transgredir más la definición de una pila, ya que potencialmente es posible también 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 discutió 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 PilaC y en la relación entre ésta y la clase Lista.

   Ahora bien, la definición de la clase PilaC se muestra en el Ejemplo PilaC. Insto una vez más al lector a que ponga atención en dos aspectos:
  1. La relación entre el diagrama de clases de la figura anterior y el Ejemplo PilaC.
  2. Los métodos push (líneas 16-18), pop (líneas 20-22) e imprime (líneas 24-26) no hacen otra cosa más que encapsular de manera conveniente, los mensajes enviados al objeto tope (línea 6), 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 PilaC 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 aspectos, 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).

   La clase de prueba del Ejemplo PilaC se muestra en el Ejemplo PruebaPilaC y sigue el mismo mecanismo de inserción que el los ejemplos de prueba anteriores. Finalmente, compruebe que la salida del Ejemplo PruebaPilaC coincide exactamente con la salida correspondiente del Ejemplo PruebaPilaH, y que desde el punto de vista de la ejecución, no podría saberse cuál fue el enfoque de diseño elegido.

14 de febrero de 2017

POO (mensajes y métodos).

   Una de las principales inquietudes que expresan los estudiantes acerca del paradigma orientado a objetos está relacionada con los mensajes y los métodos. En este sentido, se iniciará con un ejemplo sumamente sencillo el cual irá evolucionando progresivamente con la finalidad de ilustrar dichos conceptos.

   Métodos sin argumentos.
   El Ejemplo Parvulo1 muestra la definición de la clase Parvulo1, misma que no contiene atributos pero sí un método cuyo identificador o nombre es mensaje.

   El método mensaje tiene como única responsabilidad la impresión en la salida estándar de una cadena (los detalles generales del funcionamiento de println son presentados en la entrada Un vistazo al lenguaje Java).

   En base a lo anterior, la clase Parvulo1 es una especie de plantilla capaz de generar objetos con una única responsabilidad o servicio, y no puede ser instanciada ni ejecutada por sí misma. Para poder instanciar objetos de la clase Parvulo1 y poder visualizar su funcionamiento, se requiere de una clase de prueba que permita generar una instancia de ella.

   La clase de prueba para el Ejemplo Parvulo1 es la clase PruebaParvulo1 que se presenta en el Ejemplo PruebaParvulo1.

   La clase PruebaParvulo1 tiene la estructura de la mayoría de las clases de prueba que se utilizarán en el blog, y está basada en la definición del método main.

   En la línea 7 se define el objeto parvulo cuya clase (tipo) de la que deriva es Parvulo1; así mismo, observe que se genera una instancia por medio de la cláusula new, misma que, entre otras cosas, construye el objeto. Si la clase Parvulo1 tuviera un constructor explícito, sería precisamente aquí en donde se invocaría. Más adelante en esta misma entrada, se profundizará un poco más al respecto.

   Una vez que el objeto existe, es decir, una vez que el objeto ha sido instanciado, es posible entonces interactuar con él por medio de mensajes para solicitarle acciones que correspondan con las responsabilidades o servicios definidos para dicho objeto que, para el caso del objeto parvulo, es sólo una.

   La solicitud del único servicio que puede proporcionar el objeto parvulo se realiza a través del mensaje mensaje, el cual es enviado (invocado) al objeto en la línea 9:

parvulo.mensaje();

   La expresión anterior se interpreta como: "se envía el mensaje mensaje al objeto parvulo". El envío de mensajes no es otra cosa más que la invocación explícita de un método a través de su identificador, es utilizar el método correspondiente para realizar una acción, misma que está relacionada con el comportamiento o las responsabilidades del objeto en cuestión.

   La salida del Ejemplo PruebaParvulo1 se muestra en la siguiente figura. Asegúrese de comprender lo descrito hasta aquí antes de continuar.

Salida del Ejemplo PruebaParvulo1.

   Métodos con argumentos.
   En esta sección se presenta una versión ligeramente distinta del Ejemplo Parvulo1, en el cual se presentó el envío de mensajes sin argumentos. Tómese el tiempo necesario para comparar el Ejemplo Parvulo2 de esta sección con el Ejemplo Parvulo1 de la sección anterior, y compruebe que son esencialmente iguales.

   El parámetro nombre en el método mensaje constituye la diferencia de los ejemplos anteriormente mencionados. En el Ejemplo Parvulo2 el método mensaje (línea 5) define ahora la capacidad de recibir un argumento de tipo cadena (un objeto de la clase String) referido por el objeto nombre. El método mensaje imprime en la salida estándar un cadena conformada por un texto predefinido (línea 6) y la cadena referida por nombre.

   Por otro lado, el Ejemplo PruebaParvulo2 muestra la clase de prueba para el Ejemplo Parvulo2, la cual es también similar a la del Ejemplo PruebaParvulo1 excepto en la forma en que se envía el mensaje al objeto parvulo (línea 10 del Ejemplo PruebaParvulo2). Observe que el mensaje enviado tiene ahora una cadena como argumento, la cual es referida por el objeto nombre del método mensaje (línea 5 del Ejemplo Parvulo2) en el momento en que se le envía el mensaje mensaje al objeto parvulo.

   Asegúrese de realizar una labor analítica al comparar línea a línea tanto las clases Parvulo1 y Parvulo2, como las clases PruebaParvulo1 y PruebaParvulo2 así como de comprender sus diferencias en base a lo que se ha descrito hasta ahora.

   La salida del Ejemplo PruebaParvulo2 se muestra en la siguiente figura:

Salida del Ejemplo PruebaParvulo2.

   Métodos y atributos.
   Por el principio de ocultación de información es conveniente que únicamente se tenga acceso a los atributos de una clase a través de su interfaz. La interfaz de un objeto está representada por sus métodos públicos (public).

   El Ejemplo Parvulo3 hace uso de dicho principio al definir, con un acceso restringido o privado (private), el atributo nombre (línea 6) para la clase Parvulo3. Observe que dicha clase define también tres métodos públicos, los cuales establecen la interfaz de los objetos que sean instanciados:
  1. estableceNombre: este tipo de métodos son utilizados comúnmente para ajustar o establecer el valor de un atributo; y al menos en principio, debería haber un método de este tipo por cada atributo que contenga la clase y que se requiera manipular desde el exterior. Este tipo de métodos son comúnmente referidos como métodos de tipo set.
  2. obtenNombre: este tipo de métodos son utilizados comúnmente para recuperar u obtener el valor de un atributo, y al igual que antes, debería haber, al menos en principio, un método de este tipo por cada atributo que contenga la clase y que se requiera visualizar desde el exterior. Este tipo de métodos son comúnmente referidos como métodos de tipo get.
  3. mensaje: este ha sido descrito con anterioridad. Note que el método está definido como el del Ejemplo Parvulo1 (sin argumentos), pero funciona como el del Ejemplo Parvulo2. Asegúrese de comprender ésto antes de continuar.
   Observe que el método mensaje (líneas 16-18) se vale del método obtenNombre (línea 17) para acceder al atributo nombre pero no necesariamente tiene que ser así, ya que un método puede acceder directamente a los atributos de la clase, siempre y cuando ambos estén definidos dentro de la misma clase (encapsulamiento). Por otro lado, si el atributo tiene un nivel de acceso protegido (protected), los métodos de las clases derivadas por herencia también podrían acceder a los atributos de la clase de la que derivan (clase padre o super clase).

   Los métodos de tipo set sólo deben trabajar sobre un atributo, por lo que habitualmente sólo reciben un argumento, mismo que se corresponde con la clase (tipo) del atributo a modificar (String para el caso del Ejemplo Parvulo3). De manera análoga, los métodos de tipo get no reciben ningún tipo de argumento, y la clase de objetos que regresan está directamente relacionada con la clase del atributo al que accederán (String para el caso del Ejemplo Parvulo3).

   Es importante hacer notar también que la clase Parvulo3, a diferencia de las anteriores, establece ya una característica representada y definida por el atributo nombre, de tal forma que los objetos derivados de ella (párvulos) compartirán dicha característica (un párvulo es un niño pequeño en edad preescolar), aunque cada uno poseerá su propia identidad (nombre).

   El Ejemplo PruebaParvulo3 muestra la clase de prueba para la clase Parvulo3. Al igual que en los ejemplos anteriores para las clases de prueba, observe que en la línea 7 se define y crea el objeto parvulo.

   Las líneas 9 y 11 hacen uso del método obtenNombre a través del envío del mensaje correspondiente, mientras que la línea 10 envía el mensaje estableceNombre con un argumento específico. Finalmente, la línea 12 muestra el envío del mensaje mensaje, el cual debería resultarle ya familiar al lector.

   La salida del Ejemplo PruebaParvulo3 se muestra en la siguiente figura. Observe que inicialmente el atributo nombre del objeto parvulo no está definido, de ahí que se imprima null en la salida estándar, el cual es el valor por omisión en Java para las referencias a objetos que no han sido instanciados, es decir, cuando los objetos todavía no existen y por consiguiente no han sido inicializados.

Salida del Ejemplo PruebaParvulo3.

   Métodos y constructores.
   Un constructor es un método especial que se invoca implícitamente cuando se crea o instancia un objeto por medio de la cláusula new.

   La cláusula new genera la memoria necesaria para representar al objeto correspondiente y lo inicializa por medio de un constructor. En este sentido, puede haber más de una forma de inicializar un objeto y, en consecuencia, más de un constructor.

   El identificador o nombre de los métodos constructores debe coincidir con el identificador o nombre de la clase que los define; en base a lo anterior se tiene que puede haber más de un constructor compartiendo el mismo identificador. El mecanismo que tienen los constructores para distinguirse entre sí, es a través del número y clase o tipo de los parámetros que definen, constituyendo con ello una forma de polimorfismo comúnmente conocida como sobrecarga (la sobrecarga se trata con un poco más de detalle en la siguiente sección). En la creación del objeto, se invoca el constructor que coincida con el número y tipo de argumentos proporcionados a la cláusula new.

   Las líneas 9-11 del Ejemplo Parvulo4 muestran la definición del constructor Parvulo4, observe cómo el nombre del constructor coincide exactamente con el nombre de la clase. Dicho constructor define un único parámetro n el cual es un objeto de la clase String.

   La inicialización que hace el constructor Parvulo4 consiste únicamente de la asignación del objeto n al atributo representado por el objeto nombre.

   Es importante mencionar que la labor de inicialización de un constructor en particular puede ser un proceso mucho más elaborado que el descrito hasta ahora, y que estará en función directa de las responsabilidades de inicialización con que se quiera dotar al constructor y de la problemática en particular que se esté resolviendo por medio del objeto en cuestión.

   Los elementos restantes de la clase Parvulo4 han sido previamente abordados en las secciones anteriores por lo que no se repetirán aquí.

   El Ejemplo PruebaParvulo4 presenta la clase de prueba para el Ejemplo Parvulo4. Observe que a diferencia de los ejemplos anteriores, en la línea 8 se proporciona un argumento al constructor Parvulo4, lo cual hace que desde la creación del objeto parvulo se le esté definiendo un nombre.

   Observe también cómo la secuencia de mensajes subsecuentes coincide con las del Ejemplo PruebaParvulo3, excepto que en la línea 11 del Ejemplo PruebaParvulo4, se envía el mensaje estableceNombre al objeto parvulo para asignarle el nombre completo (con apellidos) al párvulo.

   Asegúrese de comprender antes de continuar, que la siguiente figura muestra la salida correspondiente a la ejecución del Ejemplo PruebaParvulo4:

Salida del Ejemplo PruebaParvulo4.

   Sobrecarga.
   La sobrecarga (overload) es un tipo de polimorfismo que se caracteriza por la capacidad de poder definir más de un método o constructor con el mismo nombre (identificador), siendo distinguidos entre sí por el número y la clase (tipo) de los argumentos que se definen.

   El Ejemplo Parvulo5 muestra la sobrecarga de constructores. Note que las líneas 8-10 definen el mismo constructor que el del Ejemplo Parvulo4 excepto por el nombre, y que se ha añadido o sobrecargado un nuevo constructor (líneas 12-14), el cual recibe tres argumentos que representan el nombre (n), el primer apellido (a1), y el segundo apellido (a2) de un párvulo.

   La sobrecarga de constructores se da porque ambos constructores tiene el mismo identificador (Parvulo5) pero distinto número de parámetros.

   No puede existir sobrecarga para constructores o métodos con el mismo identificador y el mismo número o clase (tipo) de parámetros; tiene que haber algo que los distinga entre sí, ya que en otro caso habría ambigüedad.

   Los métodos restantes del Ejemplo Parvulo5 ya ha sido comentados con anterioridad en otras secciones de esta misma entrada.

   La clase de prueba para el Ejemplo Parvulo5 se muestra en el Ejemplo PruebaParvulo5, la cual es también muy similar a las clases de prueba anteriormente explicadas. Únicamente cabe resaltar la creación del objeto parvulo en la línea 7. Note que ahora se le proporcionan tres argumentos al constructor, lo cual hace que el constructor utilizado sea el definido en las líneas 12-14 del Ejemplo Parvulo5.

   La salida del Ejemplo PruebaParvulo5 aparece en la siguiente figura. Compare dicha salida con la de la figura anterior y asegúrese de comprender la diferencia.

Salida del Ejemplo PruebaParvulo5.

3 de febrero de 2017

Paradigma.

   El concepto de paradigma resulta fundamental en la comprensión del paradigma (valga la redundancia) orientado a objetos.

   Antes de proporcionar la definición que se adoptará en el blog, se describirán algunas de las definiciones que existen de paradigma [wordreference]:
  • paradigma m. Ejemplo o ejemplar: esa chica es el paradigma de la paciencia.
  • paradigma ling. Cada uno de los esquemas formales a los que se ajustan las palabras, según sus respectivas flexiones: paradigma de la conjugación verbal.
  • paradigma ling. Conjunto de elementos de una misma clase gramatical que pueden aparecer en un mismo contexto: paradigma de las preposiciones.
  • paradigma ejemplo o modelo. En todo el ámbito científico, religioso u otro contexto epistemológico, el término paradigma puede indicar el concepto de esquema formal de organización, y ser utilizado como sinónimo de marco teórico o conjunto de teorías.
   Pero entonces, ¿qué entender por paradigma de programación?

   La palabra paradigma irrumpió en la ciencia y el vocabulario moderno a través del influyente libro "The Structure of Scientific Revolutions" del historiador de la ciencia Thomas Samuel Kuhn.

   Thomas Kuhn utilizó el término en la forma de la última definición: un paradigma es un modelo para describir un conjunto de teorías, estándares y métodos que en conjunto representan una forma de organizar el conocimiento, esto es, una forma de ver el mundo.
 
   Con base en a lo anterior, a lo largo del blog se entenderá como paradigma de programación al modelo de programación utilizado, el cual está descrito y definido por un conjunto de teorías, estándares y métodos que en conjunto, representan una propuesta de solución por software hacia una problemática determinada.

   Kuhn utilizó la ilusión óptica de la siguiente figura para ilustrar el concepto de paradigma. En dicha figura puede verse un conejo o un pato dependiendo de la perspectiva que se utilice:

Ilusión óptica del conejo-pato creada por Joseph Jastrow.
 
    El paradigma orientado a objetos cambió la perspectiva respecto del enfoque estructurado, el cual era el paradigma dominante hasta entonces.

Una perspectiva diferente.
   El concepto sobre el que subyace la esencia de la orientación a objetos es la abstracción. Por lo que, al pensar en este paradigma, se debería tener en mente una perspectiva basada en los siguientes conceptos:
  1. Entidades: agentes u objetos en interacción, donde cada uno de ellos tiene un rol y características denotadas por atributos.
  2. Responsabilidades: cada objeto proporciona un conjunto de servicios o lleva a cabo acciones que son utilizadas por otras entidades u objetos. Las responsabilidades determinan el comportamiento del objeto.
  3. Mensajes: en la POO la acción es iniciada por la transmisión de un mensaje a un objeto responsable de dicha acción. En respuesta al mensaje, el objeto receptor llevará a cabo un método para satisfacer la solicitud que le fue realizada.
   Los tres elementos anteriormente mencionados constituyen los fundamentos primordiales de la orientación a objetos. A lo largo del blog se desarrollarán de manera progresiva y se ejemplificarán con programas.
 
   Mensajes, procedimientos/funciones y métodos.
   Los mensajes son solicitudes específicas de alguno de los servicios o responsabilidades asignadas a un objeto. Los mensajes tienen un receptor específico, por lo que son enviados a un objeto en particular y pueden contener lista de argumentos.

   Los mensajes son llevados a cabo por métodos, los cuales son algoritmos asociados a un objeto (o a una clase de objetos), cuya ejecución se desencadena tras la recepción de un mensaje. En este sentido, tanto los métodos como los procedimientos o funciones son un conjunto de pasos bien definidos que llevan a cabo una acción; sin embargo, los mensajes y los procedimientos o funciones se distinguen esencialmente por dos aspectos:
  1. En un mensaje hay un receptor designado para dicho mensaje. Las funciones o procedimientos son generales, no hay un receptor específico.
  2. La interpretación o método utilizado para responder al mensaje es determinado por el receptor, y puede variar en función del receptor. Las funciones o procedimientos son únicos tanto en sus identificadores como en sus listas de parámetros.