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

28 de marzo de 2019

Composición, Agregación y Asociación.

   Desde mi perspectiva, resulta no trivial el explicar la sutil diferencia que existe entre los conceptos de Composición, Agregación y Asociación. En mi experiencia, existe una tendencia muy común hacia utilizarlos de manera indistinta, sobre todo los dos primeros; por otro lado, el tratar de mostrar uno u otro tipo de relación con ejemplos aislados, en donde sólo me muestre la Composición pero no las otras dos y así con las otras combinaciones representa, al menos para mí, una tarea no muy sencilla.

   El siguiente diagrama de clases UML presenta los cuatro tipos de relaciones más comunes que se dan entre clases:
  1. Herencia (entre las clases Persona y Científico, estudiada en la entrada Herencia / Generalización).
  2. Composición (entre la clase Persona y las clases Pierna, Cabeza y Tórax entre otras tantas posibles).
  3. Agregación (entre las clases Persona y Ropa).
  4. Asociación (entre las clases Persona y Tarjeta de Crédito).
Diagrama de Clases UML para mostrar tres tipos de relaciones: Composición, Agregación y Asociación.

    Probablemente he abusado de la simplicidad para el ejemplo, pero he tratado de utilizar elementos conocidos y asequibles en lo general para evitar lo rebuscado o complejo que puede llegar a ser el modelado de un sistema más "real", en el que con toda seguridad aparecerán, de manera natural, este tipo de relaciones. Ahora bien, la generación de esta clase de diagramas se obtiene como resultado del análisis, la experiencia, y la previa comprensión de las diferencias (muchas veces sutiles e incluso subjetivas) que pretendo clarificar.

   La principal diferencia entre las relaciones radica fundamentalmente en dos cosas:

  1. En el tiempo de vida de los objetos que se deriven de las clases.
  2. El grado de dependencia o cohesión entre los objetos.
Herencia.
   Quizá la relación más simple y más común de las mostradas en el diagrama de clases anterior, sea la de la herencia: un Científico es una Persona, un Científico posee todas las características de una Persona (modeladas por sus atributos) más algunas características particulares a un Científico. Lo mismo sucede con sus responsabilidades y comportamiento. Para ampliar un poco más los detalles, puede el lector revisar la entrada Herencia / Generalización.

Composición.
   La Composición es un tipo de relación de alto grado de dependencia entre la clase contenedora (Persona) y las clases que la componen (Cabeza, Tórax, Pierna, etc.), es decir, cuando se crea una instancia de la clase contenedora, deben crearse, como parte de su conformación, instancias de los objetos que la componen, ya que no tendría sentido, desde el punto de vista de la instancia de Persona, conformar una persona sin cabeza o sin un tórax; por otro lado, durante toda la vida del objeto de la clase Persona debe existir el objeto de la clase Cabeza por la misma razón que antes. En este mismo sentido, tampoco tendría congruencia el tener una instancia de la clase Cabeza que nunca haya estado relacionada con una Persona.

   Algún lector inconforme podría argumentar que sí es posible que un objeto de la clase Persona deje de contener a uno de la clase Pierna por ejemplo, sin afectar la existencia de primero, y en efecto: es posible. Bastaría entonces con analizar si conviene más que la clase Pierna se modele mejor como Agregación, pero finalmente esto es sólo un ejemplo y la idea principal considero que ha sido plasmada, lo último sería más cuestión de análisis y de la conveniencia dada para un sistema determinado tal y como sucede en la vida real.

   Mi perspectiva y determinación personal sería: que se quedara como Composición.

Agregación.
   La Agregación, por otro lado, es un tipo de relación con un bajo grado de dependencia. Así por ejemplo, una instancia de la clase Persona, puede tener o no, durante su tiempo de vida (pero no es preciso que lo tenga desde su creación), un atributo de la clase Ropa sin que ello afecte su propia existencia; al mismo tiempo que un objeto de la clase Ropa podría existir independientemente de si es agregado a una Persona o a un Maniquí (clase que no aparece en el diagrama), por ejemplo.

   En este tipo de relación, la existencia de los objetos involucrados es independiente, lo mismo que su tiempo de vida: un objeto de la clase Ropa podría seguir existiendo más allá del tiempo de vida del de una Persona y viceversa, sin que ninguno de los dos se vea afectado.

Asociación.
   Una Asociación es aún menos dependiente en relación y tiempo. Espero que el lector coincida conmigo en que si bien la ropa no es imprescindible para la existencia de una persona, sí es necesaria; mientras que una tarjeta de crédito podría ser útil, en el mejor de los casos necesaria, pero en definitiva prescindible, es decir, una Persona podría pasar toda su vida sin tener la necesidad de ninguna Tarjeta de Crédito, mientras que otras podría tener muchas de ellas.

   Finalmente la relación de Asociación presentada en el diagrama, muestra que una Tarjeta de Crédito está asociada a una Persona, y que una Persona tiene ninguna (0) o varias (*) Tarjetas de Crédito.

    Finalmente, se recomienda revisar también la información expuesta en la entrada Consideraciones adicionales, en donde se presentan y contrastan ejemplos relacionados con lo aquí expuesto.

11 de mayo de 2018

Características fundamentales de la POO.

   Alan Curtis Kay, quien es considerado por muchos (yo entre ellos) como el padre de la POO, definió un conjunto de características fundamentales para el paradigma orientado a objetos.

   Con base en lo propuesto por Kay, en la Programación Orientada a Objetos:
  1. Todo es un objeto.
  2. El procesamiento es llevado a cabo por objetos:
    1. Los objetos se comunican unos con otros solicitando que se lleven a cabo determinadas acciones.
    2. Los objetos se comunican enviando y recibiendo mensajes.
    3. Un mensaje es la solicitud de una acción, la cual incluye los argumentos que son necesarios para completar la tarea.
  3. Cada objeto tiene su propia memoria, misma que está compuesta de otros objetos.
  4. Cada objeto es una instancia de una clase. Una clase representa un grupo de objetos similares.
  5. La clase es el repositorio del comportamiento asociado con un objeto.
    1. Todos los objetos que son instancias de la misma clase llevan a cabo las mismas acciones.
  6. Las clases están organizadas en una estructura jerárquica de árbol denominada jerarquía de herencia.
    1. La memoria y el comportamiento asociados con las instancias de una clase, están automáticamente disponibles para cualquier clase asociada con la descendencia dentro de la estructura jerárquica de árbol.
   En un intento de complementar la visión de Alan Kay, se presenta a continuación un compendio de conceptos que definen también y refuerzan las características principales de la POO:
  • La abstracción denota las características esenciales de un objeto.
    • El proceso de abstracción permite seleccionar las características relevantes del objeto dentro de un conjunto e identificar comportamientos comunes para definir nuevos tipos de entidades.
    • La abstracción es la consideración aislada de las cualidades esenciales de un objeto en su pura esencia o noción.
  • La modularidad es la propiedad que permite subdividir una aplicación en partes más pequeñas (llamadas módulos), cada una de las cuales debe ser tan independiente como sea posible de la aplicación en sí, y de las partes restantes.
    • La modularidad es el grado en el que los componentes de un sistema pueden ser separados y reutilizados.
  • El encapsulamiento tiene que ver con reunir todos los elementos que pueden considerarse pertenecientes a una misma entidad al mismo nivel de abstracción. Esto permite aumentar la cohesión de los módulos o componentes del sistema. La encapsulación es quizá el concepto más importante del paradigma, ya que permite agrupar las funcionalidades (métodos) y el estado (datos) de los objetos de forma cohesiva. Los métodos proporcionarán los mecanismos adecuados para modificar el estado, y en algunos casos también serán la puerta de acceso a éste.
  • El principio de ocultación de información (information hiding) se refiere a que cada objeto está aislado del exterior; es un módulo independiente y cada tipo de objeto presenta una interfaz a otros objetos, la cual especifica cómo es que pueden interactuar con él.
    • El aislamiento protege a las propiedades de un objeto contra su modificación por quien no tenga derecho a acceder a ellas; solamente los propios métodos internos del objeto pueden acceder a su estado. Lo anterior asegura que otros objetos no puedan cambiar el estado interno de un objeto de manera accidental o intencionada, eliminando así efectos secundarios e interacciones inesperadas.
  • El polimorfismo está relacionado con el aspecto referente al de qué tipo de comportamientos diferentes asociados a objetos distintos, pueden compartir el mismo nombre.
    • El polimorfismo es la capacidad que tienen los objetos de naturaleza heterogénea, de responder de manera diferente a un mismo mensaje en función de las características y responsabilidades del objeto que recibe dicho mensaje.
  • La herencia organiza y facilita el polimorfismo y el encapsulamiento, permitiendo a los objetos ser definidos y creados como tipos especializados de objetos preexistentes.
    • Las clases no están aisladas, sino que se relacionan entre sí formando una jerarquía de clasificación.
    • Los objetos heredan las propiedades y el comportamiento de todas las clases a las que pertenecen. Así, los objetos pueden compartir y extender su comportamiento sin tener que volver a implementarlo.
    • La herencia múltiple se da cuando un objeto hereda de más de una clase.
   Es sumamente importante en lo subsecuente, tener todos estos conceptos vigentes y presentes, estudiarlos, analizarlos y comprenderlos; la memorización no es recomendable, ya que el memorizar conceptos no implica necesariamente su asimilación y mucho menos su comprensión. Por otro lado, si un concepto es comprendido, es posible entonces el poder explicarlo y deducir en consecuencia su definición.

   Mi deseo es que el lector reflexione sobre este aspecto y que, con un poco de paciencia, sume a su repertorio de conocimientos el conjunto de conceptos descritos hasta aquí, los cuales se pondrán en práctica eventual y progresivamente en entradas subsecuentes del blog.


4 de julio de 2017

POO (Consideraciones adicionales).

Respecto al envío de mensajes.
   La sintaxis general en Java para el envío de mensajes a un objeto es la siguiente:

objeto.mensaje(lista_de_argumentos);

donde objeto es un objeto de una clase previamente definida, y mensaje es uno de los métodos públicos definidos para dicha clase. La lista_de_argumentos es una lista de argumentos separada por comas, en donde cada argumento puede ser un objeto, o un tipo de dato primitivo.

Respecto a la sobrecarga de operadores.
   Algunos lenguajes de programación soportan un concepto relacionado con sobrecarga de operadores. La idea general del concepto de sobrecarga se ha planteado en la entrada POO (mensajes y métodos). El lenguaje de programación Java no soporta la sobrecarga de operadores, y por consiguiente, se ha omitido su descripción; sin embargo, es importante que el lector conozca que el concepto de sobrecarga no es exclusivo de los métodos o constructores, ni mucho menos de un lenguaje de programación en particular.
 
    La sobrecarga de operadores en C++ sí existe. En la sección correspondiente a las consideraciones adicionales para las colas de espera, se proporcionan un par de ejemplos al respecto. Para comprenderlos se requiere tener claros los aspectos relacionados con la implementación de la herencia, el funcionamiento de las colas de espera, y su respectiva especialización en la forma de colas de prioridad.

Respecto al paradigma.
   El establecimiento de niveles de acceso como private para los atributos de una clase, así como el uso de métodos de tipo set y get están directamente relacionados con el principio de ocultación de información (information hiding).

   Ahora bien, es probable que el lector haya notado que en la descripción del Ejemplo PruebaHerencia, en distintas ocasiones se hizo referencia a los objetos personacientífico como si fueran en sí mismos personas o entidades existentes. Lo anterior se hizo de manera deliberada, ya que como se comentó en la entrada referente al paradigma orientado a objetos, ésto eleva el nivel de abstracción y permite que se haga referencia a las entidades fundamentales del paradigma (los objetos), como elementos comunes de nuestro lenguaje natural, lo cual permite que los problemas se puedan expresar, al menos en principio, de una manera más natural e intuitiva.

   En este sentido, al dotar a los objetos de una personalidad propia con características y responsabilidades, en lugar de pensar en términos de datos, variables, y funciones o procedimientos que operen sobre dichos datos, se eleva el nivel de abstracción, facilitando con ello el análisis y la comprensión, ya que el problema y su solución pueden ser expresados y analizados en términos de su propio dominio, y no en el del medio (lenguaje de programación) de la solución. Ésta es una de las formas en la que las personas abstraemos, procesamos y utilizamos la información.

   Es sumamente importante que el lector tenga presente que en el paradigma orientado a objetos no se piensa en términos de datos, sino en términos de entidades con características y responsabilidades específicas, por lo que, cuando defina una clase, puede resultar útil el plantearse al menos un par de preguntas que le permitan determinar si los objetos derivados de su clase tienen o no sentido. Adicionalmente, las preguntas pueden ayudar también a establecer o coadyuvar en la meta de mantener una alta cohesión como parte del proceso de diseño e implementación:
  1. ¿Los atributos representan características o propiedades, o definen un estado para los objetos que serán instanciados?
  2. ¿La clase representa en sus métodos servicios, comportamiento, acciones o responsabilidades inherentes a los objetos que deriven de ella?
   Cabe mencionar que las preguntas propuestas son sólo una guía y una sugerencia al lector, no pretenden ser de ninguna manera una lista completa y absoluta. Con estas dos sencillas preguntas, además de validar y verificar su diseño de clases, estará reforzando también el concepto de encapsulamiento.

Respecto al polimorfismo.
   El polimorfismo es la cualidad de objetos heterogéneos de responder de distinta manera a un mismo mensaje. El tipo de polimorfismo más común se da en la herencia, pero no es el único.

   El Ejemplo Heterogéneo es un conjunto de clases (Automóvil, Motocicleta, Perro, Planta, Plomero, Profesor) heterogéneas que contienen un método de servicios. La idea es que, al menos en principio, cada una de dichas entidades ofrecen servicios distintos en función de su constitución y comportamiento general.

   Por otro lado, el Ejemplo Composición contiene tres clases: Libro, Publicación y Revista, las cuales presentan un comportamiento de polimorfismo en la forma en que se auto describen (imprimen), a través del método toString( ).

   En directa relación con el párrafo anterior, el tipo de polimorfismo más común se presenta en el Ejemplo Herencia, el cual contiene también las tres clases Libro, Publicación y Revista pero con un enfoque de polimorfismo basado en la herencia para la forma en que se auto describen (imprimen) a través del método toString( ).

   Existe un concepto en programación denominado latebinding, dynamic bindig o dynamic linkage, el cual se refiere a un mecanismo de programación en el cual el método que responderá al mensaje (el método que será invocado) es determinado en tiempo de ejecución. Como un ejemplo muy simple de esto, tómese el tiempo de comparar y analizar el Ejemplo PruebaHerencia (descrito en la entrada POO (Herencia)) y el Ejemplo PruebaPolimorfismo, en donde se muestra dicho concepto de programación, así mismo, es importante comprender tanto las diferencias de dichos ejemplos como los comentarios que aparecen en el código del Ejemplo PruebaPolimorfismo.

   Considere ahora el siguiente Ejemplo de late binding y tómese el tiempo que considere necesario para comprenderlo con base en el concepto de polimorfismo y  la comprensión de todos y cada uno de los ejemplos anteriores.
 
    Finalmente, una vez que se tengan madurados y comprendidos los ejemplos enunciados y su relación con el polimorfismo, se recomienda revisar también el tema de clases abstractas e interfaces (Java) para complementar dicho concepto.




21 de abril de 2017

Colas de prioridad.

   La cola de prioridad es una estructura de datos en la que el ordenamiento intrínseco de los elementos, determina el resultado de la aplicación de sus operaciones básicas o primitivas.

   En este sentido, existen dos tipos de colas de prioridad:
  1. Cola de prioridad ascendente.
  2. Cola de prioridad descendente.
   Ambas estructuras de datos redefinen la forma de operación convencional respecto de una cola de espera, por lo que serán estudiadas de manera separada.

Cola de prioridad ascendente.
   La cola de prioridad ascendente es un tipo de estructura de datos en el que la inserción de los elementos se realiza de manera convencional, pero la eliminación se realiza en base al menor de los elementos almacenados en ella. Para que ésto sea posible, los elementos que almacena la estructura de datos deben tener una relación de orden; es decir, deben poseer algún mecanismo que permita compararlos entre sí.

   La siguiente figura muestra la relación de clases en UML para una cola de prioridad ascendente con la redefinición o sobre escritura (override) del método elimina. Observe cómo se ha instrumentado la redefinición de dicho método por medio del mecanismo de la herencia, y que las relaciones previamente existentes se conservan (compare con el diagrama de clases presentado para las colas de espera en la entrada Colas de espera (definición)).
 
Diagrama de clases UML para una cola de prioridad ascendente con la redefinición (override) del método elimina.

    Otro tipo de representación para la cola de prioridad ascendente, consiste en mantener ordenados los elementos de manera ascendente durante la inserción y conservar la operación de eliminación de la forma convencional. Dicha representación se expresa en UML como se muestra en la siguiente figura:
 
Diagrama de clases UML para una cola de prioridad ascendente con la redefinición (override) del método inserta.

    En resumen, en una cola de prioridad ascendente los elementos se recuperan (eliminan) en orden ascendente respecto de la relación de orden que guarden entre sí. Ahora bien, para objetos con una relación de orden inherente a ellos, como un objeto de la clase Integer o de la clase Float por ejemplo, la comparación es posible pero, ¿qué ocurre con objetos de la clase String del API de Java, o con las clases Persona y Cientifico definidas en la entrada POO (herencia) por ejemplo?

   Implementación.
   En la orientación a objetos existe un concepto relacionado con la herencia: la herencia múltiple. La herencia múltiple básicamente es la capacidad de una clase de heredar los atributos y métodos de más de una clase; sin embargo, el lenguaje de programación Java no incluye en su gramática dicha capacidad, aunque por otro lado, incorpora un mecanismo que permite que una clase se comprometa, a través de una especie de contrato, a implementar en métodos las operaciones declaradas o establecidas en una interfaz (interface). La interfaz Comparable del API de Java compromete u obliga a las clases que la implementan, a establecer una relación de orden entre los objetos que la implementen. Dicha relación de orden es arbitraria y está en función únicamente del interés o de las necesidades específicas de la clase en cuestión.

   Para ilustrar lo anterior, considere el Ejemplo ColaAscendente el cual implementa una cola de prioridad ascendente sobre escribiendo o redefiniendo (override) el método inserta tal y como se propone en el diagrama de clases UML de la figura anterior. Note el uso de la interfaz Comparable. Observe con detenimiento la línea 6 la cual, en el contexto de lo anterior, podría interpretarse de la siguiente manera:
La clase ColaAscendente es una subclase, clase hija o clase derivada de la clase Cola, gestiona objetos genéricos T que definan o establezcan una relación de orden a través de la implementación del método compareTo declarado en la interfaz Comparable.
   Observe que el mensaje o la invocación del método compareTo ocurre en la línea 21 del Ejemplo ColaAscendente, y que la idea general del método inserta (líneas 15-35) consiste en recorrer la secuencia de nodos (líneas 21-24) mientras haya nodos por procesar y no se haya encontrado el lugar correspondiente para el elemento a insertar (línea 21). En este sentido, el método compareTo compara el objeto receptor del mensaje con el objeto proporcionado como argumento; dicho método regresa uno de tres posibles valores (consulte en el API de Java la interfaz Comparable para ampliar y complementar la información al respecto):
  1. Un entero negativo (< 0) si el objeto receptor del mensaje es menor que el objeto proporcionado como argumento.
  2. Cero (0) si el objeto que recibe el mensaje es igual al objeto proporcionado como argumento.
  3. Un entero positivo (> 0) si el objeto receptor del mensaje es mayor que el objeto proporcionado como argumento.
   Es importante que el lector comprenda que el método compareTo es definido en la clase que quiera establecer una relación de orden para sus objetos, es decir, los objetos de la clase genérica T deberán tener la definición (código) de dicho método.

   Continuando con la explicación del método inserta del Ejemplo ColaAscendente, note que el método hace uso de objetos auxiliares (líneas 16, 18 y 19), para poder realizar el ajuste de las referencias correspondientes en las líneas 26-34. Aquí cabe mencionar que, aunque dichos objetos pudieron haber sido definidos como atributos de la clase ColaAscendente, en realidad no representan una característica o cualidad inherente a los objetos que se deriven de dicha clase, sino que más bien son entidades útiles para la manipulación de la estructura de datos dentro del método, por lo que de ser atributos, aunque la implementación trabajaría de la misma manera, el enfoque sería inapropiado, esto es: sería un diseño inadecuado. El análisis y los detalles del ajuste de las referencias de las líneas 22-23 y 26-34 se dejan como ejercicio para el lector, a quien se insta amablemente a comprender completamente su funcionamiento antes de continuar.

   Por último, la clase de prueba para la cola de prioridad ascendente del Ejemplo ColaAscendente se muestra en el Ejemplo PruebaColaAscendente.

   Tómese el lector el tiempo que considere necesario para comparar el Ejemplo PruebaColaAscendente con el Ejemplo PruebaCola y advierta que son, esencialmente iguales.

   Note también que los objetos a almacenar en la cola de prioridad ascendente son objetos de la clase Integer (línea 6) por lo que, para que no haya ningún problema en la compilación, dicha clase deberá tener la implementación (implements) de la interfaz Comparable, y en consecuencia, la definición del método compareTo. En este sentido, se invita al lector para que realice dicha comprobación en el API de Java antes de compilar y ejecutar el Ejemplo PruebaColaAscendente.

   Con base en lo anterior, todos los objetos que se deseen almacenar en la cola de prioridad ascendente definida en el Ejemplo ColaAscendente, deberán implementar dicha interfaz, así como definir el comportamiento requerido (código) por el método compareTo.

   Por último, observe que a diferencia del Ejemplo PruebaCola, el Ejemplo PruebaColaAscendente inserta intencionalmente nueve números de manera desordenada, ya que la implementación de cola de prioridad propuesta (Ejemplo ColaAscendente) los mantiene ordenados dentro de la estructura de datos, mientras que la eliminación (líneas 18-26) se realiza de manera convencional. La salida del Ejemplo PruebaColaAscendente se muestra en la siguiente figura:

Salida del Ejemplo PruebaColaAscendente. 
 
Cola de prioridad descendente.
   La cola de prioridad descendente es análoga en lo general a la cola de prioridad ascendente.

   La cola de prioridad descendente es un tipo de estructura de datos en el que la inserción de los elementos se realiza también de la manera convencional, pero la eliminación se realiza en base al mayor de los elementos almacenados en ella. Al igual que para la cola de prioridad ascendente, para que ésto último sea posible, es necesario que los elementos que almacena la estructura de datos tengan una relación de orden, es decir, es preciso que incorporen algún mecanismo que les permita compararlos entre sí.

   La siguiente figura muestra la relación de clases en UML para una cola de prioridad descendente con la redefinición o sobre escritura (override) del método elimina. Una vez más, note cómo se ha instrumentado la redefinición de dicho método por medio del mecanismo de la herencia, y que las relaciones (compare con el diagrama de clases presentado para las colas de espera en la entrada Colas de espera (definición)) previamente existentes se conservan.
 
Diagrama de clases UML para una cola de prioridad descendente con la redefinición (override) del método elimina.

    Al igual que para la cola de prioridad ascendente, otro tipo de representación para la cola de prioridad descendente consiste en mantener ordenados los elementos de manera descendente durante la inserción y conservar la operación de eliminación de la forma convencional, tal y como lo sugiere la representación del diagrama de clases UML de la siguiente figura:
 
Diagrama de clases UML para una cola de prioridad descendente con la redefinición (override) del método inserta.

    Por último, recuerde que en una cola de prioridad descendente los elementos se recuperan (eliminan) en orden descendente respecto de la relación de orden que guardan entre sí. Los detalles de la implementación, se dejan como ejercicio para el lector.

20 de abril de 2017

Colas de espera (definición).

Definición.
   Una cola de espera o simplemente cola, es un conjunto ordenado de elementos del que se pueden eliminar dichos elementos de un extremo (llamado inicio de la cola), y en el que pueden insertarse elementos en el extremo opuesto (llamado fin de la cola).

   El primer elemento insertado en una cola es el primer elemento en ser eliminado, mientras que el último elemento insertado es el último en ser eliminado. Por esta razón, se dice que una cola es una estructura de datos de tipo FIFO (First In First Out).

   En el mundo real abundan ejemplos de este tipo de estructuras:
  • Líneas aéreas.
  • Líneas de autobuses.
  • Colas de espera en los supermercados.
  • Colas de espera en los bancos.
  • Etcétera, etcétera, etcétera.
   La siguiente figura muestra en (a) una representación de una cola de espera que permite visualizar los elementos almacenados en la misma:

Inserción y eliminación de elementos en una cola de espera.

   La eliminación de uno de los elementos (A) se muestra en la figura anterior en (b), mientras que la inserción de los elementos D y E se muestra en (c). Observe también cómo las referencias al inicio y fin de la cola son modificadas en cada inserción y eliminación respectivamente.

Operaciones primitivas.
   Se definen tres operaciones primitivas sobre una cola de espera:
  1. La operación inserta agrega un elemento en la parte final de la cola. También se dice que esta operación forma o encola un elemento en la estructura de datos.
  2. La operación elimina suprime un elemento de la parte inicial de la cola. Esta operación despacha o atiende al elemento que se encuentra al inicio de la cola.
  3. La operación estaVacia (esta operación fue definida como opcional o deseable para la pila. Para el caso de la cola dicha operación es ahora una primitiva, por lo que su implementación es requerida) regresa verdadero o falso dependiendo de si la cola de espera contiene o no elementos respectivamente.
   Una operación deseable para una cola de espera, sería la de imprimir todos los elementos de la cola, la cual se denotará como imprime.

Representación.
   La representación mostrada en la siguiente figura es una abstracción de la implementación que se realizará en la entrada correspondiente a la implementación:

Abstracción de una cola de espera como una secuencia de nodos.
 
    Observe cómo la figura anterior luce muy similar a la de la pila. Sin embargo, esta figura muestra el uso de dos referencias: inicio y fin, las cuales denotan, respectivamente, el primero y último de los elementos almacenados en la cola de espera. Note también que, para el caso de un solo elemento, dichas referencias harán referencia, valga la redundancia, al mismo nodo.

   Por otro lado, los diagramas de clases UML siguientes muestran el diseño de clases para la cola de espera que se desea implementar. Dicho diseño complementa, en consecuencia, la definición de dicha estructura de datos. A su vez, los diagramas de clases muestran también la relación que existe entre las clases más importantes que se involucrarán durante la implementación de la estructura de datos en cuestión.

Diagrama de clases UML para la cola de espera (Java).


Diagrama de clases UML para la cola de espera (C++).

    En este sentido valdría la pena hacer una aclaración: lo más deseable sería generar un diagrama de clases que representara la idea general del diseño de la estructura de datos con independencia del lenguaje de implementación; sin embargo, con la finalidad de enfatizar las diferencias a considerar en la implementación, se ha optado por manejar dos diagramas de clases que, aunque esencialmente iguales, especifican diferencias sutiles con respecto a los lenguajes de programación utilizados para los ejemplos.

22 de marzo de 2017

Consideraciones adicionales respecto a las ED y los ADT.

   Las nociones de la programación orientada a objetos fueron construidas sobre las ideas y bases teóricas de los tipos de datos abstractos (ADT).
 
   Un ADT es una abstracción sumamente útil, y está estrechamente relacionada con los principios de la orientación a objetos en el sentido en que puede ser definida en términos de las características (modeladas en atributos) y los servicios (representados por los métodos) que ofrece.

   La importancia más significativa en la idea de los ADT es la separación de las nociones de interfaz (servicios) e implementación.

   Por otro lado, la definición y operación de una estructura de datos está en directa relación con la definición de un ADT, por lo que en las secciones de las entradas siguientes, además de definir las estructuras de datos más usuales y convencionales, se presentará un tipo de implementación particular para ellas; sin embargo, es importante que el lector tenga presente que existe más de una posible implementación para una definición de un ADT determinado y que, si en la práctica debe utilizar alguna para resolver algún problema real, lo mejor será recurrir a las estructuras de datos del API o de la biblioteca estándar del lenguaje que utilice. En este blog, la implementación de dichas estructuras persigue un objetivo estrictamente didáctico, y constituyen más un medio para ilustrar el paradigma, que una implementación eficiente.

   Por último pero no menos importante, es menester mencionar que, además de la implementación de las estructuras de datos más comunes, se presentarán también algunas de las aplicaciones tradicionales más representativas, con la principal intención de poner en práctica de manera combinada, tanto los conceptos orientados a objetos, como las de las estructuras de datos propiamente dichas; al mismo que tiempo de que se somete a prueba su implementación.

21 de marzo de 2017

Herencia / Generalización

   Uno de los conceptos del paradigma orientado a objetos más importantes es el de herencia, ya que dicho mecanismo de abstracción permite la re utilización de código de una manera sumamente conveniente, además de habilitar las capacidades del polimorfismo a través de la sobre escritura (override) de métodos.

   Abstracción.
   La ejemplificación del concepto de herencia/generalización estará basada en los Ejemplo Persona y el Ejemplo Cientifico para Java, y en el Ejemplo Herencia para C++; pero antes de poder describirlos, considero pertinente presentar primero en un diagrama de clases los detalles de la relación de generalización que se quiere mostrar con la finalidad de elevar el nivel de abstracción. Con el diagrama de clases propuesto, se pretende llevar el concepto de herencia/generalización a la forma en que la mayoría de las personas comprendemos y analizamos las cosas, para posteriormente profundizar con más conocimiento de causa en los detalles de la implementación de dicho concepto en un lenguaje de programación.

   El diagrama de clases UML (Unified Modeling Language) del que partirá el análisis se muestra en la siguiente figura:
 
Diagrama de clases UML para mostrar la relación de generalización / herencia entre Científico y Persona.

    Los detalles completos de la explicación de un diagrama de clases UML quedan fuera de los alcances de este blog, por lo que sólo se describirán los aspectos más relevantes que ayuden al lector a visualizar de mejor manera, en caso de que el lector no cuente con experiencia en UML, la relación de generalización y herencia.

   Un diagrama de clases UML está compuesto, grosso modo, por clases y las relaciones entre dichas clases. En este sentido, cada clase se representa con un recuadro dividido en tres partes:
  1. Identificador de la clase.
  2. Listado de atributos con la especificación de su clase (tipo) y sus niveles de acceso correspondientes.
  3. Listado de métodos con la especificación de la clase (tipo) de sus argumentos y valor o clase de retorno, así como los niveles de acceso correspondientes para los métodos.
   Tanto para el caso de los atributos como para el de los métodos, los niveles de acceso están representados por un signo de más para un acceso público (+), un signo de menos para un acceso privado (-), y un signo de gato para un nivel de acceso protegido (#).

   Con base en lo anterior, puede observarse de nuestro diagrama que la clase Persona, y por lo tanto las instancias (objetos) que se deriven de ella, tendrán las siguientes características (atributos) comunes a una persona: un nombre, una edad, y una nacionalidad (podrían definirse mucho más características u otras diferentes a las descritas aquí, pero no es la intención del ejemplo representar todas las características completas y comunes a una persona). Observe también que se ha definido un conjunto de operaciones, acciones, responsabilidades o comportamiento comunes a una persona, mismas que se encuentran definidas por los métodos establecidos (al igual que para los atributos, no se ha pretendido modelar por completo el comportamiento o las responsabilidades representadas en los métodos, sino sólo una representación muy general).

   Con base en las aclaraciones previas y lo descrito hasta aquí, es posible decir que una persona promedio está representada de manera muy general por la clase Persona.

   Ahora bien, un científico es (recuerde la idea de división en especializaciones: relación is-a presentada en la entrada Orientación a objetos (conceptos)) una persona, y por lo tanto comparte las características o atributos así como las acciones o el comportamiento inherentes a una persona; y es precisamente este tipo de relación de compartir la que se refiere a la herencia, ya que se dice que en la herencia una clase hereda (comparte) los atributos (características) y métodos (acciones) a otra.

   La herencia en UML se representa por medio de una flecha como la de la figura anterior (diagrama de clases). Es importante señalar que el sentido de la flecha es sumamente significativo, ya que tal y como aparece en nuestra figura, el diagrama indica que la clase Científico hereda las características y el comportamiento de la clase Persona (y no al revés). Otra forma de verlo es que la clase Persona es una generalización de la clase Científico en el sentido de que esta última agrega cierto nivel de especificación respecto de la primera.

   Note también que la clase Científico define un atributo adicional (especialidad), mismo que se añade a todos los atributos que implícitamente ya tiene, mismos que fueron heredados de la clase Persona. Observe también que se han definido cuatro métodos para la clase Científico los cuales tienen las siguientes características:
  1. estableceEspecialidad: es un método de tipo set para el atributo especialidad definido en la clase Científico.
  2. obtenEspecialidad: es un método de tipo get para el atributo especialidad definido en la clase Científico.
  3. mensaje: este método ya estaba definido en la clase Persona, pero al ser redefinido en la clase Científico, se dice que sobre escribe (override) al primero, lo cual significa que un objeto instanciado de la clase Científico, responderá al mensaje mensaje con la definición de su propio método y no con la definición del método mensaje definido en la clase Persona.
  4. mensajeEspecial: este es un nuevo método particular y específico a las instancias derivadas de la clase Científico.
   Es fundamental que el lector se asegure de comprender la descripción hasta aquí realizada respecto a las clases PersonaCientífico. Es también muy importante entender la relación existente entre estas clases antes de continuar a la siguiente sección, en donde entre otras cosas, se abordarán los aspectos relacionados con la implementación de ellas en el lenguaje de programación correspondiente.

Abstracción de estructuras de datos.

El estudio de las estructuras de datos implica, en general, dos propósitos complementarios:
  1. Identificar y desarrollar tanto entidades como operaciones útiles relacionadas con dichas entidades. En este rubro es preciso determinar el tipo de problemas que se solucionan utilizando tales entidades y operaciones.
  2. Determinar representaciones para dichas entidades abstractas, así como implementar las operaciones abstractas en representaciones concretas.
   El primero de estos dos propósitos considera a un tipo de datos de alto nivel como un instrumento que se usa para solucionar problemas. Este propósito está también en estrecha relación con la definición de tipos de datos abstractos (ADT).

   El segundo propósito considera la implementación del tipo de dato o ADT como un problema que se va a resolver utilizando objetos o elementos existentes por medio de la herencia (relación es-un (is-a)) o la composición (relación tiene-un (has-a)).
 
    Como ya se ha mencionado, en ocasiones ninguna implementación tanto de hardware como de software puede modelar en su totalidad un concepto matemático (un tipo de dato entero o real por ejemplo), por lo que es importante conocer las limitaciones de una implementación particular, ya que una consideración fundamental de cualquier implementación es no sólo su efectividad sino también su eficiencia.

Clases autorreferidas.
   La implementación de las estructuras de datos que se desarrollarán en el blog estarán basadas en un concepto muy importante: la autorreferencia.

   En el contexto de la orientación a objetos, la autorreferencia es la capacidad que tienen los objetos de una clase de almacenar explícitamente una referencia a objetos de su misma clase, es decir, a objetos de su mismo tipo.

   Considere el Ejemplo Nodo Java (Ejemplo nodo C++), el cual muestra una clase autorreferida con dos atributos:
  1. dato: el atributo que representa el elemento a almacenar en el nodo.
  2. siguiente: el cual es una referencia a un objeto cuya clase es la misma que lo define (Nodo), y por lo tanto, es una autorreferencia.
   En el área de las estructuras de datos, un nodo es la unidad mínima de almacenamiento dentro de la estructura de datos, y puede ser tan simple o elaborado como se requiera.

   Para el caso del Ejemplo Nodo, el nodo únicamente almacena enteros primitivos (int), pero como se discurrirá más adelante, es posible representar y almacenar entidades más elaboradas que un número entero.

   Considere la creación de un objeto nodo instanciado de la clase Nodo (Java) y nodo (C++):

Nodo nodo = new Nodo(1974);    // Java: declaración y definición de "nodo"
Nodo nodo(1974);    // C++: declaración y definición de "nodo"

y su correspondiente representación mostrada en la siguiente figura:

Representación de un objeto de la clase Nodo (Ejemplo Nodo).

   En la figura anterior puede apreciarse que un objeto es en realidad una referencia a una instancia (entidad) con características definidas por la clase de la que deriva, que para el caso específico del Ejemplo en turno están dadas por los atributos dato y siguiente.
 
    En este punto quizá valga le pena recordar algo sutil pero sumamente importante: la diferencia entre declaración y definición. Para no extenderse innecesariamente de más, se tomará el caso de Java:
 
Nodo nodo;    // Declaración de "nodo" (cuadro izquierdo en la representación gráfica)
.
.
.
nodo = new Nodo(1974);    // Definición de "nodo" (objeto con dos campos en la representación gráfica)
 
es habitual, pero no necesario, que la declaración y la definición del objeto vayan juntas (como se hizo al mostrar el ejemplo de instanciación líneas arriba). Sin embargo, también es posible declarar las variables que harán referencia a los objetos, para posteriormente crear explícitamente los objetos (new) mismos que serán referidos por la variable a la que se asignan (como lo hacen los constructores). En Java no existe el concepto explícito de referencia (apuntador); sin embargo una variable cuyo tipo es una clase, en realidad es una referencia al inicio de la memoria de la instancia de una clase, por lo que cuando hablamos de un "objeto", nos valemos de una abstracción que se refiere a la instancia propiamente dicha y a la variable que la refiere.

   Ahora bien, observe la relación entre el Ejemplo Nodo y la figura anterior de su representación, y note cómo en el constructor el objeto es inicializado con el valor recibido como argumento, y con null (nullptr) para la referencia siguiente, lo cual ha sido representado en la figura anterior con una diagonal (\) para enfatizar que la referencia representada por el atributo siguiente no hace referencia inicialmente a ningún otro objeto. Observe también que la clase o tipo del objeto nodo es la misma que la del atributo siguiente, lo cual constituye la autorreferencia que es el concepto que se desea ilustrar. Asegúrese de comprender lo anterior antes de continuar.

Implementación.
   Con base en lo descrito en la sección anterior, es posible decir que las estructuras de datos consisten, de manera general, en un conjunto de nodos ordenados en una secuencia lógica como la que se muestra en la siguiente figura:

Secuencia de nodos formados con objetos autorreferidos.

   Las estructuras de datos estudiadas en las entradas siguientes tendrán en general la forma o estructura representada por esta figura.
 
   Por otro lado, el mecanismo utilizado para la inserción y la eliminación de nodos en la estructura de datos, estará en función directa de las reglas especificadas por la definición de la estructura de datos.

   Finalmente, la especificación de las características de la estructura de datos se implementarán a través de sus atributos, mientras que la especificación de las reglas de operación o de comportamiento serán implementadas por medio de sus métodos.

13 de marzo de 2017

Tipos de datos abstractos (ADT).

   Desde el punto de vista de la programación, es importante que los programadores puedan definir sus propias abstracciones de datos, de tal forma que dichas abstracciones trabajen de manera parecida a las abstracciones o a las primitivas de datos proporcionadas por el sistema subyacente.

   Un tipo de dato abstracto o ADT (Abstract Data Type) es definido por una especificación abstracta, es decir, permite especificar las propiedades lógicas y funcionales de un tipo de dato.

   Desde el punto de vista de los ADT, un tipo de dato es un conjunto de valores y un grupo de operaciones definidas sobre dichos valores. El conjunto de valores y el de las operaciones forman una estructura matemática que se implementa usando una estructura de datos particular de hardware o de software.

   El concepto de ADT está relacionado con la concepción matemática que define al tipo de datos por lo que, al definir un ADT como concepto matemático, no interesa la eficiencia del espacio o del tiempo (los cuales son aspectos relacionados con la implementación del ADT), sino las propiedades y características inherentes a él. La definición de un ADT no se relaciona en lo absoluto con los detalles de la implementación; de hecho, tal vez ni siquiera sea posible implementar un ADT particular en ningún tipo de hardware o software (piense por ejemplo en los números reales y en la propiedad de la densidad de los números reales por ejemplo).

   Un ADT consta de dos partes: una definición de valor y una definición de operador, mismas que se describen a continuación:
  1. La definición del valor establece el conjunto de valores para el ADT y consta a su vez de dos partes:
    1. Una cláusula de definición.
    2. Una cláusula de condición. Para la definición de un ADT racional por ejemplo, una cláusula de condición estaría relacionada con la restricción de que el denominador de un número racional no puede ser cero.
  2. En la definición del operador cada operador está definido como una operación abstracta con condiciones previas opcionales, y las condiciones posteriores o postcondiciones.
   Por otro lado, para la implementación de un ADT, es necesario tomar en cuenta los siguientes aspectos:
  • Hacer disponible (pública) la definición del nuevo tipo.
  • Hacer disponibles un conjunto de operaciones que puedan ser utilizadas para manipular las instancias del tipo definido (métodos públicos de servicios).
  • Proteger los datos asociados con el tipo de dato que está siendo definido (atributos privados), de tal forma que dichos datos sólo puedan ser accedidos por medio de los métodos proporcionados para ello.
  • Poder generar múltiples instancias del tipo definido, preferentemente, con más de una opción de inicialización, siempre que ésto no entre en contradicción con la definición o restricciones del tipo.
   Es importante resaltar que, asociado con un ADT particular, puede haber una o más implementaciones distintas.

   En resumen, los ADT son un mecanismo de abstracción sumamente importante y, dado que la programación orientada a objetos (POO) se fundamenta en la abstracción, es posible decir que constituye uno de los pilares de la orientación a objetos.

Especificación del ADT racional.
   Existen varios métodos para especificar un ADT, desde notaciones matemáticas hasta descripciones detalladas hechas en lenguaje natural. Lo importante de la especificación es que sea clara y no ambigüa.

   Esta sección, debido a las limitaciones de formato inherentes al blog, hará uso de algo parecido a una notación matemática apoyándose del lenguaje natural para la especificación del ADT racional.

   Sea r un número racional (Q). Se define r como el cociente de dos números enteros, es decir:


r = p / q

donde p, q son números enteros (Z) y q distinto de cero (!=0).

   Las operaciones aritméticas de suma, resta, multiplicación y división de dos números racionales se especifican a continuación.

   Sean r1 y r2 son dos números racionales (Q) definidos como:

r1 = p1 / q1 con p1, q1 números enteros (Z) y  q1 != 0

r2 = p2 / q2 con p2, q2 números enteros (Z) y  q2 != 0
entonces:
  1. r1 + r2 = (p1 / q1) + (p2 / q2) = [ (p1 * q2) + (p2 * q1) ] / (q1 * q2).
  2. r1 - r2 = (p1 / q1) - (p2 / q2) = [ (p1 * q2) - (p2 * q1) ] / (q1 * q2).
  3. r1 * r2 = (p1 / q1) * (p2 / q2) = (p1 * p2) / (q1 * q2).
  4. r1 / r2 = (p1 / q1) / (p2 / q2) = (p1 * q2) / (q1 * p2).

   Observe la especificación de los valores para el ADT racional, y la restricción que existe sobre el valor del denominador representado por q1 y q2 respectivamente.

   Finalmente, note que se ha proporcionado también la especificación de las operaciones aritméticas asociadas al ADT.


13 de febrero de 2017

Programación orientada a objetos.

   En entradas anteriores se discurrió en los elementos fundamentales de la orientación a objetos:
   La intención de dichas entradas es la de proporcionar al lector un panorama general del paradigma orientado a objetos sin asociarlo necesariamente con la programación, y mucho menos con algún lenguaje de programación en particular.

   Un lenguaje de programación es sólo un medio para el paradigma, no el paradigma en sí. Por otro lado, el ejercicio o labor de la programación es la forma de aplicar los conceptos asociados al paradigma.

   Las entradas subsecuentes irán detallando al lector los conceptos presentados pero desde la perspectiva de la programación y su aplicación en algún lenguaje de programación. Sin embargo, es importante aclarar que el objetivo del blog no es enseñar los lenguajes de programación, sino el de utilizarlos como un medio para hacer tangibles los conceptos de orientación a objetos.

   El blog proporciona entradas particulares que introducen a algunos de los aspectos de los lenguajes de programación Java y C++ que podrían ser de utilidad para el lector, pero de ninguna manera pretende cubrir los elementos completos de dichos lenguajes, sino solamente presentar un panorama general:
   Además de lo anterior el blog presenta las bases del paradigma orientado a objetos en el contexto de su aplicación a la programación utilizando al lenguaje de programación Java y C++.

   La intención principal de las entradas que siguen este enfoque que se acaba de mencionar, es la de concretizar en algún lenguaje de programación los conceptos más distintivos del paradigma orientado a objetos, para que en entradas posteriores se puedan aplicar al desarrollo de las estructuras de datos, y mejorar así tanto la comprensión de los conceptos, como la experiencia del lector.


10 de febrero de 2017

Ejercicios selectos (Orientación a objetos).

  1. Investigue más acerca de la historia y desarrollo de la programación orientada a objetos.
  2. Xerox es actualmente una compañía que desarrolla equipo de foto copiado e impresión entre otras cosas. Investigue cuál era el sistema operativo que utilizaban en los años de Xerox PARC (Palo Alto-California Research Center) que por cierto, ya incluía una GUI (Interfaz Gráfica de Usuario) pionera de las GUI que actualmente se utilizan.
  3. Investigue la historia y la ideología del lenguaje de programación Smalltalk. Aproveche para conocer el nombre de su(s) creador(es).
  4. Investigue la historia y la ideología del lenguaje de programación Eiffel. Aproveche para conocer el nombre de su(s) creador(es).
  5. Investigue la historia del lenguaje de programación C++. Aproveche para conocer el nombre de su(s) creador(es).
  6. Investigue el papel del Dr. Alan Curtis Kay en la concepción y desarrollo de la programación orientada a objetos.
  7. Investigue qué otros paradigmas de programación existen.
  8. Investigue qué lenguajes de programación actuales soportan el paradigma orientado a objetos, cuáles lo soportan de manera nativa y cuáles como una extensión.


8 de febrero de 2017

Orientación a objetos y modularidad.

   La modularidad no está exclusivamente relacionada con los procedimientos o funciones de la programación estructurada, sino con el grado en el que los componentes de un sistema pueden ser separados y reutilizados.

   En función de lo anterior, tanto los métodos como los objetos son en sí mismos módulos de una aplicación determinada y en consecuencia, las clases de las que se derivan constituyen los módulos del sistema, por lo que de aquí en adelante se hará referencia a la modularidad de manera indistinta tanto para clases, como para los métodos de las clases.

   La modularidad ayuda también a hacer el código más comprensible, y esto a su vez hace que en consecuencia, al menos en principio, el código sea más fácil de mantener. Sin embargo, sin las debidas y pertinentes consideraciones, la modularidad tiene también sus consecuencias negativas, mismas que están en función directa de dos conceptos fundamentales en el desarrollo de software en general, y en el paradigma orientado a objetos en particular:
  1. Cohesión.
  2. Acoplamiento.
Cohesión y Acoplamiento.
   La cohesión está relacionada con la integridad interna de un módulo. Es el grado o nivel de relación o integridad entre los elementos que componen un módulo.

   El nivel de cohesión determina qué tan fuerte están relacionados cada unos de los elementos de funcionalidad expresados en el código fuente de un módulo.

   Por otro lado, el acoplamiento describe qué tan fuerte un módulo está relacionado con otros, es decir, es el grado en que un módulo depende de cada uno de los otros módulos que componen un sistema.

   El acoplamiento también puede ser referido o entendido como dependencia, lo cual ayuda a recordar que lo que se desea es mantener un bajo nivel de dependencia entre los módulos, es decir un bajo acoplamiento.

   En general, se desea que los módulos de un programa o sistema tengan, un alto nivel de cohesión y un bajo nivel de acoplamiento. El paradigma orientado a objetos persigue y enfatiza dichos objetivos.


7 de febrero de 2017

Orientación a Objetos (conceptos).

Objetos.
   Los objetos son esencialmente abstracciones. Son entidades que tienen un determinado estado, un comportamiento (determinado por sus responsabilidades), y una identidad.
  • El estado está representado por los datos o los valores que contienen los atributos del objeto, los cuales son a su vez otros objetos o variables que representan las características inherentes del objeto.
  • El comportamiento está determinado por las responsabilidades o servicios del objeto los cuales son definidos por los métodos, mismos que se solicitan a través de mensajes a los que dicho objeto sabe responder.
  • La identidad es la propiedad que tiene un objeto que lo distingue o hace diferente de los demás. La identidad está representada por un identificador.
   Un objeto es una entidad que contiene en sí mismo, al menos en principio, toda la información necesaria que permite definirlo, identificarlo, y accederlo respecto a otros objetos pertenecientes a otras clases, e incluso respecto a objetos de su misma clase. La forma de acceder a un objeto es a través de su interfaz. La interfaz de un objeto es el conjunto de servicios públicos que ofrece el objeto, mismos que se solicitan a través de mensajes o solicitudes realizadas a dicho objeto.

   Los objetos se valen de mecanismos de interacción llamados métodos que favorecen la comunicación entre ellos. Dicha comunicación favorece a su vez el cambio de estado en los propios objetos. Esta característica define a los objetos como unidades indivisibles en las que no se separa el estado del comportamiento.

   Orientación a objetos vs. enfoque estructurado.
   La orientación a objetos difiere del enfoque estructurado básicamente en que en la programación estructurada los datos y los procedimientos están separados y sin relación explícita, ya que lo único que se busca en la programación estructurada es el procesamiento de los datos de entrada para obtener los datos de salida.

   La programación estructurada utiliza en primera instancia un enfoque basado en procedimientos o funciones, y en segunda instancia, las estructuras de datos que dichos procedimientos o funciones manejan, cumpliendo así con la ecuación planteada por Niklaus Wirth:

Algoritmos + Estructuras de Datos = Programas

   Por otro lado, un programa en un enfoque OO solicita estructuras de datos (las cuales son otros objetos) para llevar a cabo un servicio.

   La perspectiva OO también define programas compuestos por algoritmos y estructuras de datos esencialmente; sin embargo, lo hace desde un enfoque diferente. En la orientación a objetos la descripción del objeto se da en términos de responsabilidades y características; y así, al analizar un problema en dichos términos, se eleva el nivel de abstracción.

   Lo anterior permite una mayor independencia entre los objetos, lo cual es un factor crítico en la solución de problemas complejos. Cabe mencionar por último en este sentido, que al conjunto completo de responsabilidades asociadas a un objeto se le refiere comúnmente como protocolo.

Objetos y clases.
   Todos los objetos son instancias de una clase (categoría). Esta relación de un objeto con una clase, hace que los objetos tengan las siguientes características:
  • El método invocado por un objeto en respuesta a un mensaje es determinado por la clase del objeto receptor.
  • Todos los objetos de una clase determinada utilizan el mismo método en respuesta a mensajes similares.
  • Las clases pueden ser organizadas en una estructura jerárquica de herencia como la que se muestra en la figura de abajo.
  • Una clase hija o subclase heredará todas las características de la clase de la que deriva (clase padre clase madre o súper clase).
  • Una clase abstracta es una clase de la que no se derivan instancias directamente, sino que es utilizada únicamente para crear subclases.
  • La búsqueda del método a invocar en respuesta a un mensaje determinado inicia en la clase del receptor. Si no se encuentra el método apropiado, la búsqueda se realiza en la clase padre, si no se encuentra ahí, se busca en la clase padre de la clase padre y así sucesivamente hasta encontrar el método correspondiente.
  • Si se encuentran métodos con el mismo nombre dentro de la jerarquía de clases, se dice que el método procesado sobre escribe (override) el comportamiento heredado.

Diagrama de ejemplo de jerarquía de clases.

   La figura anterior presenta una posible jerarquía de clases para un Ser Vivo. Más que una clasificación o taxonomía completa, la figura muestra el concepto de herencia a través de un árbol. En la figura puede observarse que los elementos que se derivan comparten características (atributos) y comportamientos (métodos) semejantes.

   Así por ejemplo, es posible decir que Flipper es una instancia particular de todos los posibles delfines que podrían existir. A su vez, un Delfín comparte características comunes con una Ballena en cuanto a que ambos son Cetáceos pero difieren en otras (si un delfín y una ballena coincidieran en todo (características y comportamiento) serían de la misma clase).

   Un Delfín es un Cetáceo, y un Cetáceo es un Mamífero. En muchas ocasiones a éste tipo de relaciones se le denomina "es un" (is a), y es una característica útil para identificar herencia pero no es la única.

   Existe otro tipo de relación y se denomina "tiene" (has-a). Estas relaciones son dos formas importantes de abstracción en la orientación a objetos:
  1. La idea de división en partes (has-a): un automóvil tiene un motor, tiene una transmisión, tiene un sistema eléctrico, etc.
  2. La idea de división en especializaciones (is-a): un automóvil es un medio de transporte, es un objeto de cuatro ruedas, es un objeto que se dirige con un volante, etc.
   Finalmente, la figura anteriormente presentada muestra que Flipper es un Delfín, que un Delfín es un Cetáceo, y que éste a su vez es un Mamífero; que un Mamífero pertenece al Reino Animal, y que el Reino Animal es parte de los seres vivos representados por la súper clase de todas las clases o clase base Ser Vivo.

2 de febrero de 2017

Orientación a Objetos (orígenes).

   Las características principales de lo que actualmente se denomina Programación Orientada a Objetos (POO) surgen en el siglo XX alrededor de 1960; y aunque algunos autores difieren en sus orígenes, comparto la idea de que los conceptos de la POO tienen su inicio en Simula 67, un lenguaje diseñado en el centro de cómputo noruego en Oslo. Simula --dicho sea de paso-- es un lenguaje para simulaciones creado por Ole-Johan Dahl y Kristen Nygaard.

   Posteriormente, en Agosto de 1981, se publica en la revista Byte la descripción del lenguaje de programación Smalltalk, el cual refinó algunos de los conceptos originados con el lenguaje Simula. Smalltalk fue desarrollado en Xerox PARC (Palo Alto-California Research Center).

   Lo anterior dio pie a que en la década de 1980 los lenguajes de programación Orientados a Objetos (OO) tuvieran un rápido auge y expansión, por lo que la POO se fue convirtiendo en el estilo de programación dominante a mediados de los años ochenta del siglo pasado. Con todo, este modelo de programación continúa vigente hasta nuestros días.

   La POO fue una de las primeras propuestas de solución para ayudar a resolver la denominada, aunque no generalmente aceptada, "crisis del software". En este sentido es importante decir que, si bien las técnicas OO pueden facilitar la creación de complejos sistemas de software a través de mecanismos alternativos de abstracción, no son la panacea universal ni las "balas de plata".

   Programar una computadora sigue siendo una de las tareas más difíciles jamás realizadas por un ser humano. Volverse experto en programación requiere, no sólo de saber manejar herramientas y conocer técnicas de programación, sino que además es preciso contar también con:
  • Talento.
  • Creatividad.
  • Ingenio.
  • Lógica.
  • Habilidad para construir y utilizar abstracciones.
  • Experiencia
  • Etcétera.
   Por lo anterior, hacer un uso efectivo de los principios OO requiere de una visión del mundo desde una perspectiva distinta; sobre todo si se parte de la base de resolución de problemas a través de un enfoque estructurado.

   Es importante señalar y tener presente desde este momento, que el uso de un lenguaje de POO no hace, por sí mismo, que se programe OO, ya que se podría tener en el mejor de los casos, un programa o sistema implementado con un enfoque estructurado pero programado en un lenguaje orientado a objetos.
 
   La POO requiere en primera instancia de la comprensión del paradigma orientado a objetos, por lo que se debería iniciar el aprendizaje con un recorrido conceptual partiendo por el concepto de paradigma que se utilizará a lo largo del blog.