Fundamentos de la programación orientada a objetos




Antes de realizar cualquier tipo de codificación (o ver el cursor parpadeante en la pantalla de nuestro ambiente de desarrollo) sin tener en mente lo que vamos a realizar, tenemos que comprender el problema, luego planificar la solución, y luego comenzar con la construcción de la pieza de software.


Metodologías de desarrollo

¿Hay una metodología que me permita de manera óptima la realización de un programa?.
La respuesta a la pregunta anterior es no, ya que existen actualmente muchas metodologías de desarrollo para diversas necesidades. Recordar que no existe sólo un tipo de software.

La primera metodología usada fue la llamada “en cascada”, la cual consistía en que pasábamos a la siguiente etapa de desarrollo, sólo si la etapa actual estaba realizada. Lamentablemente esta metodología actualmente no funciona. Los requerimientos del cliente van en constante cambio, lo que sugiere volver a etapas anteriores y así sucesivamente.



Por otro lado, tenemos el enfoque ágil e iterativo que actualmente es el más utilizado. En cada uno de ellos haremos análisis, diseño y programación. Esto lo repetiremos varias veces en cada etapa.




Lenguajes orientados a objetos

Antes de la aparición de los lenguajes orientados a objetos, existían lenguajes de bajo nivel los cuales se estructuraban por código línea a línea. A medida que los programas se hicieron más y más grandes, estos lenguajes se veían limitados.

En los lenguajes orientados a objetos cada objeto posee sus propios datos y su propia lógica. Estos objetos se comunican entre sí para crear grandes proyectos con una mayor simplicidad.

Algunos de estos lenguajes son C++, C#, Perl, PHP, Python, Java, Ruby, VB.net y muchos más.


¿Qué es un objeto?

Para responder esta pregunta debemos analizar que es un objeto en el mundo real.
Los objetos poseen características inherentes que los hacen únicos. Una taza de café puede estar llena o vacía, una manzana puede ser verde o roja, una copa de vino puede ser grande o pequeña. Estas características son el estado de un objeto, como su color, tamaño, forma, etc. Cada objeto posee un estado que lo define y que es distinto al de otro objeto. Si apagamos una ampolleta, esto no significa que apagaremos todas las ampolletas del mundo.

Además cada objeto posee un comportamiento (acciones que pueden realizar), un perro ladra, una cuenta de banco permite realizar un depósito, un balón se mueve, un televisor se enciende y apaga, etc.

Los objetos no necesariamente deben ser objetos físicos.
Los objetos no necesariamente deben ser visibles.


Clase

Las clases describen como serán nuestros objetos. Es una especie de plano el cual es usado para construir nuestro objeto.




Instancias

El proceso de creación de objetos se denomina instanciación. Cada objeto puede poseer ilimitado números de instancias las cuales representan entidades diferentes. En este caso podemos crear N objetos Persona los cuales poseerán diferentes características que los hará únicos.



Muchas clases ya han sido creadas para satisfacer necesidades simples como generar una fecha, conectarse a una red, etc. Están clases están almacenadas en librerías de clases, más conocidas como frameworks de desarrollo, las cuales poseen funciones adicionales.
·         Java Class Library
·         .NET Framework
·         C++ Standard Library


Es momento de hablar sobre las características principales de la programación orientada a objetos.


Abstracción

Nos centramos en las características genéricas de un objeto y no en un ejemplo concreto.
Por ejemplo si decimos auto, nuestra mente visualiza un auto verdad?. Esta imagen abstracta no está ligada a ninguna propiedad en particular. No hemos dicho el auto rojo, o el auto de 4 puertas, solo hemos dicho auto. Eso es lo que hace la abstracción. Nos centramos en las características generales y no entidades definidas.


Encapsulamiento

Ocultamiento de información. Cada objeto sólo debe comunicarse con otro objeto sólo mediante métodos, y prohibir el acceso a propiedades directamente. Esto nos da mayor modularidad y mayor seguridad en nuestra aplicación.

Los atributos de clases deben ser siempre privados. 


Herencia

Re utilización de código. Una clase hereda de otra y al mismo tiempo hereda todo lo que lleva consigo, propiedades y métodos. Además puede agregar nuevas características a la subclase.

Clase > Subclase
Clase padre > Clase hija



Podemos afirmar que una persona es un mamífero, y que un perro es un mamífero.


Polimorfismo

El término de polimorfismo se refiere a que una clase puede tomar diferentes formas. O sea, este mecanismo permite invocar operaciones idénticas con respecto a su interfaz padre, pero con una implementación diferente en la clase hija.

Como dijimos anteriormente, una subclase puede heredar todo lo que tiene su clase padre, y a su vez, modificar éstos últimos. Este y los demás temas hablados en esta entrada del blog, serán vistos con mayor profundidad en la sección de Java. 


Proceso de diseño de Software

Para realizar un trabajo correcto, debemos seguir los siguientes procedimientos:
  • Tomar requerimientos del cliente (lo que debe hacer nuestra aplicación)
  • Describir la aplicación
  • Identificar los objetos principales
  • Identificar las interacciones entre nuestros objetos
  • Crear nuestro diagrama de clases

Requisitos funcionales

Se refiere a lo que la aplicación en sí debe hacer, definiendo las características y capacidades del software a desarrollar.


Requisitos no funcionales

Se refiere a temas no realizadas con el desarrollo, ya sea el cumplimiento de leyes, que soporte se le dará y cuan a menudo, cual será su rendimiento óptimo, que se hará en caso de fallas, etc.


UML (Lenguaje unificado de modelado) 

Este lenguaje se encarga de conceptualizar nuestras clases, objetos, paquetes, etc. en un diagrama que es mucho más entendible para un trabajo más rápido. Serán nuestros planes que nos servirán para decirnos que debemos hacer.


Casos de uso

Los casos de uso sirven para representar la interacción del usuario con el sistema, y como realiza diferentes acciones dentro del mismo.

Los diagramas de casos de uso están estructurado con los siguientes elementos:
  • Caso de uso
  • Actor
  • Relaciones 
Caso de uso: Es una tarea específica que se realiza mediante un agente externo, ya sea el propio actor, un actor de otro caso de uso, o incuso un sistema propiamente tal.

Posee un titulo (cual es el objetivo), un actor (quién lo ejecuta), y un escenario (cómo llegar al objetivo).

Actor: Es un rol que un usuario desempeña en el sistema. No necesariamente debe ser una persona, sino que es un ejecutante genérico de algún caso de uso.

Relaciones: Para unir nuestros actores con sus respectivos casos de uso debemos generar una relación que determine qué y cómo se ejecutará dicha operación.


Escenarios

Al determinar los escenarios de nuestros casos de uso, debemos hacer énfasis en el objetivo del actor en el sistema, y obviar escenarios que no correspondan a ello.

Por ejemplo, tenemos una aplicación mobile para pedir comida rápida a domicilio. Para llegar a realizar un pedido debemos de registrar nuestros datos para que los dueños del restaurant sepan donde vivimos y realicen el trayecto con éxito. ¿Cuál es el objetivo del usuario, registrar sus datos o pedir comida a casa?. 

No debemos crear casos de uso que no contribuyan con el objetivo principal del usuario.


Diagrama de casos de uso

Es un tipo de diagrama UML encargado de ilustrar los diferentes casos de uso y actores, mostrando una perspectiva global de los miembros y sus interacciones.



Historias de usuario

Una historia de usuario es más corto que un caso de uso. Se describe solo un pequeño escenario centrado en el usuario y su objetivo. Se escriben en tarjetas con contenido breve.


Estructura:

·         Cómo… (tipo de usuario)
·         Quiero… (objetivo)
·         Para… (razón o fin)

Como usuario
Quiero ver el listado de productos disponibles
Para poder comprar una camisa 


Modelo conceptual del sistema

Tenemos que identificar los objetos principales, refinarlos, y por último dibujarlos en un diagrama.

Nos enfocaremos en la construcción orientada a objetos.
  •  Identificar los sustantivos escritos en nuestro escenario de caso de uso
  •  Crear una lista con los sustantivos seleccionados y eliminar duplicidades o sustantivos los cuales sean propiedades de un objeto y no un objeto en sí
  • Luego creamos las clases con los sustantivos seleccionados

Definir responsabilidades

Éstos serán los comportamientos que se convertirán en métodos. Buscaremos los verbos para recoger todas las responsabilidades. 

Las responsabilidades deben estar distribuidas entre las diferentes clases. Una clase que realiza una acción, no significa que sea responsable de esa acción.

Hay que evitar las clases dios, o clases sistema.


Tarjetas CRC

Las tarjetas CRC definen cuáles serán las responsabilidades de una clase y sus clases colaboradoras. Son creadas en papel y se discuten entre el equipo de desarrollo.

Podemos tomar las tarjetas creadas y ponerlas en un mesón para detectar sus interacciones.



Diagramas de clase


Evitar estructuras de datos planas al escribir demasiadas propiedades sin métodos (responsabilidades) que los completen y/o utilicen.

Miembros estáticos

Son variables o métodos compartidos que no dependen de un objeto en particular, sino que depende de la clase.

Ejemplo: private static final cargo = 25000;

Esta variable puede ser accedida por distintos objetos y su valor será igual para todos. Existirá solo una copia de ésta.

Lenguajes compilados e interpretados



Siempre se ha recurrido a la comparación entre estos dos tipos de lenguajes, en su mayoría para ver cual es el mejor y más eficiente. Presentaremos ventajas y desventajas de cada uno, y su utilización en casos particulares donde la elección es primordial a la hora del éxito de un proyecto.

Los lenguajes compilados, como su nombre lo dice, necesitan un compilador para convertir nuestro código de programación a código máquina (el cual es ejecutable), y que éste, sea interpretado satisfactoriamente por la computadora. De esta manera, nuestro código fuente que escribimos queda totalmente fuera del alcance de los usuarios finales, los cuales sólo poseen el archivo ejecutable.

Los lenguajes interpretados no necesitan ningún tipo de compilador. A medida que la computadora los lee, los interpreta y ejecuta automáticamente, lo cual es un proceso mucho más lento que leer directamente un código máquina. Con respecto a su distribución, tendríamos que compartir nuestro código fuente si se requiere la utilización de mi programa en otra computadora o plataforma (código público).


Compilados Interpretados
Ventajas Desventajas Ventajas Desventajas
Preparados para ejecutarse No son multiplataforma Son multiplataforma Se requiere un intérprete
A menudo más rápidos Poco flexibles Son más sencillos de probar A menudo más lentos
El código fuente es inaccesible Se requiere un paso extra (compilación) Los errores se detectan fácilmente El código fuente es público


Definir que el código fuente debe ser accedido sólo por el programador toma un juicio personal, el cual no pretendo imponer a ninguno de ustedes.


Además de éstos tipos anteriormente mencionados, existe hoy en día un combinación de ambos, denominada aproximación intermedia. 

Lo que hacemos es compilar nuestro código de programación a bytecode (lenguaje intermedio), el cual se encuentra entre el lenguaje máquina y nuestro código fuente. Este código intermedio se puede distribuir enviándolo a diferentes personas, y cada persona lo compila para la plataforma en que lo necesite, ya sea Windows, MacOS o distribuciones Linux. Este proceso de compilación para una plataforma en particular es llamado Just in time.



Así funciona el proceso Just in time en Java

Como podemos observar, nuestro código fuente (.java) es compilado a bytecode (.class) que puede ser distribuido a cualquier plataforma o sistema operativo. ¿Pero como se logra esto?. ¿Quien interpreta el famoso bytecode en la máquina final?. La encargada de la interpretación es conocida como JVM (máquina virtual de Java). 

Entonces, teniendo instalada nuestra JVM podemos ejecutar nuestro programa en la computadora que queramos, en el sistema operativo que queramos.

"Write once, run anywhere"
"Escribelo una vez, ejecutalo donde quieras"

De esta forma Java utiliza Just in time para poder ser distribuido de una manera más flexible (como lo hacen los lenguajes interpretados), y de una manera más rápida (como lo hacen los lenguajes compilados).


Código máquina



Todo el código escrito en algún lenguaje de programación, debe finalmente ser compilado a un conjunto de bytes conocidos como bytecode o código máquina. Este conjunto de bits interpretado por la máquina es el encargado de comunicarnos directamente con el microprocesador. Utiliza un sistema binario encargado de realizar dichas funciones.

¿Qué es el sistema binario?

Actualmente nuestro sistema de conteo está en decimal (base 10), o sea, los valores para representar cualquier número que queramos, será posible utilizando 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. En cambio el sistema binario utiliza dos únicos valores, 0s y 1s (base 2).

Para escribir el número 22 en base decimal haremos lo siguiente:

2 * (10^1) + 2 * (10^0) = 22

Recordar que se multiplica nuestro numero por 10^ a cualquier número de 0 a 9.

En código binario sería algo muy distinto:

10110 = 22

Hay diferentes formas de realizar este cálculo, optaremos por el más fácil según mi opinión.

Cada byte es igual a 8 bits, que como dijimos anteriormente posee dos estados (0 y 1). El menor número posible es 0 (00000000), y el mayor número posible es 255 (11111111), lo cual nos deja con un número de 256 combinaciones posibles, ya que tomamos en cuenta el numero 0.

0 a 255 = 256

Ahora definimos la estructura y rango de bytes, cuyos valores serán el doble al anterior:

256 128 64 32 16 8 4 2 1

Y por último vemos cuales números harán posible que nuestro resultado sea 22, y ponemos un 1 bajo él.

256 128 64 32 16 8 4 2 1
0       0    0   0    1  0  1 1 0

Si sumamos los números escogidos nos debe dar el número planteado.

16 + 4 + 2 = 22

Introducción a la Programación


¿Qué es programar?

"Un programa de computadora es una serie de instrucciones". 

Esta definición la he encontrado en muchos libros sobre este tema, pero quisiera analizarlo en mayor profundidad para que no nos quedemos simplemente con la visión general del concepto.

Cuando hablamos de programar nos referimos a interactuar con nuestro sistema operativo para crear software, y que éste, actúe de la manera que nosotros queremos o que realice las funciones que estimemos pertinentes. Ésta interacción entre programador y la unidad central de procesos (CPU) se logra mediante la codificación de diferentes lenguajes de programación a código máquina (binario).

Los lenguajes de programación fueron creados para facilitar el trabajo al programador, los cuales si no existieran, solo tendríamos que manipular ceros y unos, lo que realmente sería un gran dolor de cabeza.

Existen diferentes categorías de lenguajes. Lenguaje ensamblador, de bajo nivel y alto nivel. 

Además poseen características que los hacen únicos. Hay lenguajes de tipo compilado, y otros de tipo interpretado.

Para la creación de software necesitamos escribir código que siga una estructura con N pasos para abarcar el o los objetivos del programa. Esta estructura o secuencia de pasos es llamado Algoritmo. Un algoritmo es una serie de pasos para solucionar un problema.



En este diagrama de flujo mostramos un algoritmo sencillo, el cual nos entrega el área de un triangulo.

Como estudiante uno comienza aprendiendo a desarrollar software igual o muy similar a éste, con una estructura básica y pequeña que a medida que pasa el tiempo y nuestros conocimientos se amplían, éstos programas se vuelven mas robustos y con una complejidad significativamente superior que contempla la utilización de miles o millones de usuarios. En la actualidad (definiendo límites reales), se puede crear cualquier tipo de software.