Software y Aplicaciones Web

Blog de desarrollo de software y aplicaciones web

Comentarios Recientes

Comment RSS

MSDN Home Page (Argentina)


C# Corner


AspAlliance.com

Declaración

Las opiniones en este blog se proporcionan "TAL CUAL", sin garantías,  no confieren derechos y no reflejan, necesariamente, la opinión de quienes me contratan.
Algunas cuestiones que se comentan en el blog no son reales, cualquier similitud con alguna persona viva o muerta no es más que una coincidencia, tampoco significa que necesite terapia, soy asi.

© Copyright 2007-2010

Propaganda

Este sitio implementa publicidad basada en intereses
Aug
10.
2009

  Estructura de Datos y Programación Orientada a Objetos


Introducción

En esta publicación voy a presentar la relación entre Estructura de Datos y Programación Orientada a Objetos; está dirigida a alumnos comienzan sus estudios en carreras vinculadas a la informática, el enfoque es sobre Estructura de Datos sin embargo hay que comentar aspectos de otras áreas de la Informática lo que voy a intentar hacer de la manera más simple que se pueda sin incursionar demasiado en dichas áreas dado que no corresponden al estudio de Estructura de Datos y seguramente las estudiarán en profundidad en otras asignaturas o por su cuenta.

 

El paradigma orientado a objetos nació en 1969 cuando Kristin Nygaard intentaba escribir un programa que describiera el movimiento de los barcos en las complicadas costas de Noruega; descubrió que era muy difícil representar las mareas, el movimiento de los barcos y las formas de la costa con los métodos de programación existentes en ese momento. También descubrió que era más fácil de representar y controlar las relaciones de los elementos del entorno (barcos, mareas y costa) y las acciones que podían ejecutar.

Paradigma de programación

La palabra paradigma deriva del Griego y significa "patrón" o "ejemplo"; Thomas Kuhn en "La estructura de las revoluciones científicas" asignó a esta palabra el significado aceptado hoy en día en la comunidad científica.

Kuhn adoptó la palabra "paradigma" para referirse al conjunto de prácticas que definen a una disciplina científica durante un período de tiempo en particular; también identificó el término "cambio paradigmático" como el proceso y resultado de un cambio en las hipótesis establecidas por las teorías científicas reinantes. Es decir, un "cambio paradigmático" se origina cuando los científicos encuentran anomalías que no pueden explicar con el paradigma aceptado en ese momento.

Cuando ocurren algunas anomalías significativas la disciplina científica entra en crisis, es entonces cuando se prueban nuevas y viejas ideas.

Eventualmente se forma un nuevo paradigma, que indudablemente entrará en conflicto con el actual enfrentando a los seguidores de cada corriente.

Si se produce la transición entre el antiguo paradigma y el nuevo se dice que ha sucedido un "cambio paradigmático" o "revolución científica".

 

Robert W. Floyd en "The Paradigms of Programming" en total acuerdo con la visión de Kuhn sobre las revoluciones científicas, concluye que la probabilidad de que se produzca un avance continuo en la programación requerirá de una invención continua además de la elaboración y colaboración de nuevos paradigmas.

La Programación Orientada a Objetos es el resultado de un proceso de "crisis" en el que se aplicaron nuevas y viejas ideas, en síntesis es el resultado de un cambio paradigmático. El siguiente esquema muestra esta situación.

Es oportuno comentar que tanto la programación modular como la programación estructurada se presentan como paradigmas, ocurre que en su momento (cuando fueron enunciadas, todavía no se hablaba de paradigmas), no es el objetivo de esta publicación discutir sobre esto.

Lo que si hay que comentar es que la programación modular es aquella que considera módulos o subprogramas, en los principios de la programación todo se hacía de con una programación lineal salvo la instrucción de salto (goto o jmp) que permite saltar de un lugar del código a otro y continuar la ejecución a partir de ese punto. Cuando los desarrolladores observaron que había porciones de código que permanentemente hacían lo mismo, se les ocurrió incorporar una instrucción o mecanismo con la capacidad de saltar a otro lugar del código (gosub o call), ejecutar las instrucciones que allí se encuentran y mediante otra instrucción o mecanismo regresar al punto desde donde se había saltado.; con esto surgió la programación modular pero en ese momento no había estructuras de control de ejecución de programas. Las primeras versiones del lenguaje de programación Basic no tenían las sentencias mientras o según ... que se enseñan en los cursos básicos de programación.

Las estructuras de control de ejecución de programas tan conocidas actualmente surgieron al evolucionar las técnicas de programación dando lugar a la programación estructurada. Es importante tener en cuenta que hoy en día se realizan programas utilizando ambas "técnicas", sería inaceptable hacerlo de otro modo, pero en realidad son independientes.

Los conceptos de tipo de dato abstracto se incorporan a estas técnicas y permiten gracias a la evolución de los lenguajes de programación el surgimiento de la programación orientada a objetos; lo que se denomina paradigma orientado a objetos va más allá de la simple programación orientada a objetos, seguramente los estudiantes de carreras informáticas verán en otras asignaturas los conceptos que fundamente el análisis y diseño de productos de software bajo este paradigma.

Desde el campo de las estructuras de datos se construyen herramientas que van a ser utilizadas por los programas; este campo está interesado en la construcción de artefactos concentrándose en la mecánica de los mismos, en sus herramientas y engranajes que en la mayoría de los casos no son visibles al desarrollador usuario del artefacto. El campo de las estructuras de datos investiga la operabilidad de estos artefactos y su mejora al modificar las estructuras de datos que se van a encontrar dentro de los mismos.

Es indiscutible que el campo de las estructuras de datos tiene un mejor desempeño si se realiza en el modo orientado a objetos. Al incorporar las estructuras de datos en un artefacto y hacer público sólo lo que es necesario para el uso apropiado del mismo, el campo de las estructuras de datos puede desarrollar los artefactos cuyo funcionamiento no se vea comprometido por una manipulación innecesaria.

Programación orientada a objetos

Aunque los equipos informáticos (computadoras) operan con bits los desarrolladores no pensamos en esos términos, sabemos que un entero es una secuencia de bits pero preferimos considerar al entero como una entidad con su propia individualidad la que se refleja en las operaciones que pueden realizarse con ellos sin pensar como el o los enteros utilizan los bits.

También sabemos que otras entidades pueden usar a los enteros por ejemplo como sus elementos atómicos, estas entidades las conocemos como tipos de datos que están integrados a los lenguajes de programación, pero ciertos tipos de datos que necesitamos en nuestros programas no están, y necesitan estarlo, de manera que deben ser definidos y son los que se conocen como tipos de datos definidos por el desarrollador.

Estos nuevos tipos de datos tienen una estructura distintiva una configuración de sus elementos, y un comportamiento determinado. Desde un punto de vista estricto en el estudio de las estructuras de datos se tiene la tarea de explorar las nuevas estructuras e investigar su comportamiento en términos de requisitos de tiempo y espacio.

La programación orientada a objetos (OOP - Object Oriented Programming) gira en torno al concepto de un objeto, que se crea utilizando una definición denominada clase, que resulta ser una plantilla que incluye una especificación de datos y funciones o procedimientos que operan sobre esos datos. Las funciones o procedimientos definidos en una clase se llaman métodos y las variables que manipula pueden ser variables de clase o variables de instancia.

En el paradigma imperativo, se escribe código que manipula datos y la documentación del programa dice que procesos se ejecutan sobre qué datos; en cambio en el paradigma orientado a objetos son los objetos quienes hacen la conexión entre los datos y los métodos.

Abstracción de datos y programación orientada a objetos

Antes de escribir un programa el desarrollador debe tener una idea bastante clara de cómo lograr la tarea que está programando, consecuentemente es fundamental tener un esquema que detalle los requisitos que deben codificarse; mientras más grande y complejo es el proyecto más detallado debe ser el esquema. Lo importante es que los detalles de la implementación deben postergarse, en especial los detalles de las estructuras de datos que se vayan a utilizar.

Desde el inicio, debemos preocuparnos más por lo que el programa debe hacer, no por cómo puede hacerlo. El comportamiento del programa es más importante que el engranaje del mecanismo para lograrlo; más tarde, en la etapa de implementación se decide cuál estructura de datos debe utilizarse para hacer la ejecución más eficiente en términos de tiempo y espacio.

Un elemento especificado en términos de las operaciones se llama tipo de dato abstracto. En los lenguajes orientados a objetos un tipo de dato abstracto se llama interface.

Las interfaces son parecidas a las clases, pero solamente pueden contener constantes y prototipos de métodos; es decir las especificaciones de los nombres de los métodos, los tipos de parámetros y los tipos de valores de regreso. La tarea de definir los métodos se hace cuando se escribe una clase que implementa la interface. En otras palabras, una interface es como un "contrato", las clases que implementan una interface "se comprometen" a realizar todo lo que la interface especifica. De esta manera los desarrolladores pueden postergar los detalles específicos de implementación para una etapa posterior del proceso de desarrollo.

Objeto

En el mundo real, las personas identifican los objetos como cosas que pueden ser percibidas por los cinco sentidos. Los objetos tienen propiedades específicas, como posición, tamaño, color, forma, textura, entre otras de manera todas ellas definen su estado. Los objetos también poseen cierto comportamiento que los hacen diferentes de otros objetos.

Si imaginamos el motor de un vehículo, el estado del motor puede ser apagado o funcionando, entre sus propiedades se puede considerar su potencia, velocidad máxima, velocidad actual, temperatura, entre otras. El comportamiento puede incluir acciones para arrancar y parar el motor, obtener la temperatura, cambiar la velocidad. La identidad se basa en el hecho de que cada instancia de un motor es única, tal vez haya un número de serie que las diferencie. En el diseño de un programa orientado a objetos se crea una abstracción del motor basado en las propiedades y el comportamiento que son útiles para resolver el problema.

Un mensaje es una instrucción que se envía a un objeto y cuando se recibe ejecuta sus acciones. Un mensaje incluye un identificador que contiene la acción que ha de ejecutar el objeto junto con los datos que necesita el objeto para realizar su trabajo.

En el caso del motor, cuando se desea aumentar la velocidad seguramente se utilizará un mensaje que diga algo como "acelerar al doble", donde acelerar es el mensaje y al doble es el dato que necesita el objeto para hacer su tarea.

Un método es la implementación de un mensaje, en otras palabras es la porción de programa necesaria para que cuando un objeto recibe un mensaje ejecute las acciones necesarias para cumplir o realizar el comportamiento esperado.

Clase

En términos prácticos, una clase es un tipo definido por el desarrollador. Las clases son los bloques de construcción fundamentales de los programas orientados a objetos. Una clase contiene la especificación de los datos que describen un objeto junto con la descripción de las acciones que un objeto puede ejecutar.

Estas acciones se conocen como servicios o métodos. Una clase también incluye todos los datos necesarios para describir los objetos creados a partir de la clase. Estos datos se conocen como atributos, variables o variables instancia. El término atributo se utiliza en análisis y diseño orientado a objetos y el término variable instancia o campo se suele utilizar en programación orientada a objetos. Los lenguajes de programación orientados objetos facilitan la implementación de propiedades para acceder a las variables de instancia, esto contribuye al encapsulamiento.

Antes que un programa pueda manipular objetos se debe definir la clase correspondiente, esto significa que se debe dar un nombre a la clase, nombrar los elementos que almacenan sus datos y describir los métodos que realizan las acciones necesarias para representar el comportamiento.

Mediante el operador "new" se crea un objeto y el sistema operativo devuelve una referencia al objeto creado, la que se asigna a una variable del tipo de la clase. Para poder hacer esto, todas las clases tienen al menos un método denominado constructor, este método se encarga de inicializar el objeto cuando es creado y en general los desarrolladores suelen implementar diferentes "versiones" del constructor. Un objeto permanece "vivo" siempre que al menos una variable dentro del programa lo esté referenciando.

Asignación de memoria en programación orientada a objetos

Para visualizar y comprender mejor esta cuestión que un objeto tiene un ciclo de vida o que un objeto permanece "vivo" siempre que exista al menos una variable que lo referencie, es necesario ver algunos conceptos sobre asignación de memoria cuando un programa está ejecutándose.

En programación imperativa, por ejemplo con el lenguaje COBOL (las viejas versiones de este lenguaje) solamente se podía declarar y utilizar variables del tipo "global", aquellas que se pueden ver y manipular desde cualquier lugar del programa.

Este modelo de asignación de memoria se corresponde a dos segmentos (porciones de memoria) por cada proceso, uno para el código y otro para los datos.

De esta manera desde cualquier lugar del código se puede acceder o a los datos.

En este modelo solo se permite la programación lineal.

Al avanzar los lenguajes de programación se incorpora un mecanismo que permite invocar subprogramas (procedimientos o funciones) y pasar parámetros a los mismos; además se permite declarar y utilizar variables conocidas como "locales", aquellas que solamente se pueden utilizar dentro de un subprograma.

La arquitectura de estos lenguajes de programación utiliza un modelo de asignación de memoria que incorpora un tercer segmento de memoria donde se ponen los parámetros antes de ejecutar un subprograma y también es en ese segmento donde se crean temporalmente las variables locales.

Con la incorporación del tercer segmento de memoria, se produjo un avance realmente importante en las capacidades de los lenguajes de programación, a la par se incorpora la posibilidad de solicitar memoria adicional al sistema operativo logrando de esta manera que los programas puedan utilizar la cantidad de memoria justa para lo que tienen que hacer; al conjunto de estas técnicas se las conocía como programación con memoria dinámica.

Para que un programa pueda gestionar la memoria de manera dinámica se utiliza un tipo de dato conocido como puntero o apuntador, cuyo contenido es una dirección de memoria. De esta manera el desarrollador puede declarar punteros y solicitar al sistema operativo una cantidad de memoria para hacer algo. En la medida que el desarrollador "cuide" esa dirección de memoria podrá utilizar la memoria que se le ha asignado.

Este mecanismo de programación, el de utilizar punteros y solicitar memoria al sistema operativo, es muy eficiente pero también muy peligroso. Una programación descuidada agota la memoria del sistema, dado que no "devuelve" las porciones de memoria que solicita y "pierde" las direcciones. El siguiente esquema muestra un caso típico.

Se trata de un programa, que tiene subprogramas y uno de estos subprogramas declara y utiliza una variable puntero donde guarda la dirección de memoria del bloque solicitado.

El problema se presenta cuando finaliza la ejecución del subprograma y el desarrollador no tuvo la precaución de "devolver" la memoria al sistema operativo o en todo caso de pasarla a una variable donde se la pueda seguir accediendo.

Cuando el subprograma finaliza, las variables que se crearon en el segmento de pila "desaparecen" y se pierde la dirección del bloque de memoria sin poder acceder al mismo.

Este inconveniente fue solucionado con una técnica de asignación de memoria que utiliza lo que se llama heap o montón que es un espacio de memoria de donde se reserva y asigna las solicitudes de los programas.

Para el ejemplo anterior, se presenta el bloque de memoria solicitado por el subprograma asignado dentro del heap, que seguramente se halla en un segmento extra.

Cuando el subprograma finaliza su ejecución, la variable local que apuntaba o referenciaba al bloque de memoria desaparece porque así es como funcionan las variables en el stack. El bloque de memoria queda sin ninguna variable que haga referencia a esa dirección de memoria, aparentemente el problema persiste, solo que este modelo de asignación de memoria cuenta con un proceso que se llama Garbage Collector (recolector de basura) que inspecciona el heap buscando aquellos bloques de memoria que no son referenciados y libera la memoria.

Los lenguajes de programación orientados a objetos ocultan la utilización de punteros, simplemente clasifican o caracterizan a los distintos tipos de datos como Data Type aquellos tipos de datos cuyas variables se almacenan en el stack (clásico modelo de asignación de memoria) y los Reference Type aquellos tipos de datos que utilizan una variable en el stack (lo que en el modelo no orientado a objetos es el puntero) y una porción de memoria en el heap (espacio necesario para los objetos de la aplicación); por supuesto que todos estos lenguajes implementan el Garbage Collector, caso contrario se terminaría agotando la memoria del equipo impidiendo la ejecución del programa. Indudablemente la implementación del Garbage Collector requiere de características especiales en el sistema operativo que se ejecuta en el equipo; debe permitir la ejecución de varios procesos ya sea en paralelo o bajo algún mecanismo de paralelismo aparente.

Visibilidad

En programación orientada a objetos se utiliza el término visibilidad para tratar con un principio fundamental de este paradigma que es conocido como "ocultación de la información", lo que significa que a determinados elementos del interior de una clase no se puede acceder por métodos externos a la clase. El mecanismo principal para ocultar datos o métodos es ponerlos dentro de una clase y declararlos "privados", con lo que se está diciendo que a esos elementos solamente se los puede manipular o "ver" desde dentro de la misma clase. Lo contrario sería declararlos "públicos" con lo cual cualquier método de cualquier clase podría manipularlos. Existen otras posibilidades como por ejemplo declarar que un elemento esta "protegido", con lo cual se logra que desde fuera de la clase nadie pueda acceder al elemento pero se permite que las clases derivadas de esa clase puedan manipular dichos elementos (derivada es un concepto de C++, en Java y C# se utiliza el concepto de subclase, que no es otra cosa más que un clase que hereda las propiedades de otra).

Dependiendo de la plataforma en la que se desarrolle, las definiciones de clases suelen reunirse en librerías de clases que se conocen como paquetes (package) o ensamblados (assemblies), existen modificadores que permiten el acceso a las clases de la misma librería, esto lo podrán ver practicando con un lenguaje en particular.

Constructores y destructores

Un constructor es un método que se ejecuta automáticamente cuando se crea un objeto de una clase. El método constructor tiene el mismo nombre que la clase, no se puede indicar un valor de retorno (al menos en los lenguajes orientados a objetos que conozco), tampoco devuelve algo. Pero si puede tomar cualquier número de argumentos, esto es mediante la sobrecarga de métodos (la vemos más adelante).

Un constructor que no tiene parámetros se conoce como "constructor por defecto" y generalmente inicializa los elementos del objeto con valores por defecto (siempre que estos elementos tengan valores por defecto). Este constructor no hace falta escribirlo a menos que necesitemos hacer algo en particular, o inicializar los elementos del objeto con determinados valores. Hay que revisar la documentación del lenguaje que utilizan porque en algunos lenguajes cuando se implementa un constructor distinto del constructor por defecto, éste último ya no se genera automáticamente y muchas veces es necesario el constructor por defecto

Suponiendo que existe una definición de una clase llamada "Persona", entonces para declarar y crear un objeto de esta clase se escribe la siguiente sentencia

Persona alguien = new Persona();

En este caso "alguien" es una variable cuyo tipo es "Persona" y se crea el objeto mediante el operador "new" invocando el constructor de la clase, en este caso el constructor por defecto.

Supongamos que se ha implementado un constructor que permite asignar el nombre de la persona, entonces la sentencia podría ser:

Persona alguien = new Persona("Juan");

Ahora el constructor recibe un parámetro, consecuentemente no es el constructor por defecto (que no hace falta implementar). Cuidado en C# si se implementa algún constructor y luego se necesita el constructor por defecto también debe implementarse.

Esta posibilidad que brindan ciertos los lenguajes (en general todos los lenguajes orientados a objetos), la de tener un método que puede recibir distintos parámetros en cantidad y tipo, es lo que se denomina sobrecarga de métodos. Es una facilidad que brindan los lenguajes para que bajo el mismo nombre (de método) el desarrollador pueda implementar el mismo código o semejante (se espera que haga lo mismo) para distintas posibilidades de acuerdo a la cantidad y tipo de parámetros.

El destructor es un método que en general no hace falta implementar, se supone que el recolector de basura busca las porciones de memoria en el "heap" que no están referenciadas y las libera. Eventualmente puede ser necesario que un objeto realice ciertas acciones antes que se libere la memoria que se le había asignado; también se considera la implementación y uso de los destructores en programación avanzada y para aplicaciones que requieran altos niveles de prestación.

Valor null

Todos los desarrolladores sabemos que cuando se declara una variable por ejemplo de tipo numérico, esta variable tiene un valor por defecto (puede ser que distintos lenguajes pongan distintos valores); por ello es que una buena práctica de programación es inicializar las variables antes de utilizarlas, esto es indiscutible cuando las variables de utilizarán como contadores o acumuladores.

Las variables que corresponde a objetos también tiene un valor por defecto; este valor es "null" lo que significa nulo dado que el objeto no existe, no fue inicializado. Cualquier intento de enviar mensajes mediante una variable cuyo valor es "null" provocará un error en tiempo de ejecución.

Pseudovariable - this

La palabra reservada "this" se utiliza como si fuese una variable, pero en realidad no lo es porque el desarrollador no la define ni crea; está palabra se utiliza para referirse al objeto que está ejecutando una porción de código.

En programación orientada a objetos, la ejecución de un programa es en realidad el envío y recepción de mensajes entre objetos de manera que cada uno de ellos realice el comportamiento que tienen. Consecuentemente, para que los objetos puedan enviarse mensajes entre ellos hace falta que se conozcan, no es posible enviarle un mensaje a un objeto desconocido (puede desconocerse el tipo de objeto, pero el objeto en sí mismo debe ser conocido). Del mismo modo hace falta una forma de "conocer" al mismo objeto por eso es que existe esta pseudovariable.

Elementos static

Ya se comentó que la ejecución de un programa orientado a objetos es el resultado del envío de mensajes entre objetos, también se indicó que estos objetos deben existir para que puedan enviar o recibir los mensajes que realizan su comportamiento, por esa razón es que existe un método constructor, aquella porción de código que crea e inicializa un objeto.

Sin embargo algunas aplicaciones necesitan que ciertos elementos existan desde el momento mismo en que la aplicación comienza a ejecutarse, es como decir que ese elemento se crea automáticamente y se puede utilizar. Para ello los lenguajes de programación orientada a objetos facilitan la palabra reservada "static" que sirve para indicar que "un elemento se crea automáticamente" y la aplicación lo puede utilizar sin necesidad de crear un objeto. Lo que se debe tener en cuenta es que una aplicación puede crear tantos objetos de un tipo en particular como la aplicación lo necesite, pero no puede crear aquellos elementos u objetos que se clasificaron como "static".

Tanto en C# como en Java, cuando se realiza una aplicación de consola (aquella que se ejecuta en la consola del sistema operativo que tienen en la computadora) se puede ver que el método "Main" (en C#) o "main" (en Java) están clasificados como "static", lo que significa que no hace falta crear un objeto para invocar dicho método, simplemente existe como parte de la definición de la clase que lo contiene. Este tipo de elementos de las clases se conocen como variables de clase, a diferencia de los otros elementos conocidos como variables de instancia, porque para poder acceder a ellos primero se debe crear una instancia de la clase o sea un objeto.

Jerarquía de clases

En programación orientada a objetos todo es un objeto, de esta manera es posible organizar una aplicación como la interacción entre objetos. Para lograr esto, los lenguajes orientados a objetos cuentan con lo que se llama una "jerarquía de clases" que es donde cada objeto-clase que el lenguaje facilita y los que los desarrolladores implementan se encuentra.

La primer definición (clase) de la jerarquía se conoce como "object", a partir de esta definición se implementan todos los objetos de una aplicación.

Es importante leer la documentación del lenguaje de programación que utilicen para ver que particularidades tiene esta clase. En general tendrá unos métodos que son comunes a todo objeto como ser "Equals", "HashCode" y "ToString", para que sirven y cómo se utilizan se verá a medida que se desarrollen estructuras de datos con énfasis en la programación orientada a objetos.

 


Para aquellos que les interese aquí esta el documento: Estructura de Datos y Programación Orientada a Objetos.pdf (197,39 kb) 

Espero que les sirva.

 








Comments (1) -

nico Spain

Saturday, November 14, 2009 11:03 PM

nico

muy bueno

Add comment




  Country flag
biuquote
  • Comment
  • Preview
Loading