Download Introducción a la Programación

Document related concepts

Programación funcional wikipedia , lookup

J (lenguaje de programación) wikipedia , lookup

Lisp wikipedia , lookup

APL wikipedia , lookup

Evaluación de cortocircuito wikipedia , lookup

Transcript
00_IntrodProg.qxd
23/7/07
20:01
Page 1
Introducción a la
Programación
Preparado para rendir la estrella
inicial del examen DCE
por José
María Selesan
00_IntrodProg.qxd
23/7/07
20:01
Page 2
TÍTULO > Introducción a la Programación
AUTOR > José María Salesan
FORMATO > 13,5 x 19 cm
PÁGINAS > 96
Copyright © GRADI S.A. 2007.
Hecho el depósito que marca la ley. Reservados todos los derechos de autor.
Prohibida la reproducción total o parcial de esta publicación por cualquier medio
o procedimiento y con cualquier destino.
Primera impresión realizada en agosto de 2007.
Kollor Press, Capital.
Todas las marcas mencionadas en este libro
son propiedad exclusiva de sus respectivos dueños.
Selesan, José María
Introducción a la programación. - 1a ed. - Banfield Lomas de Zamora : Gradi S.A., 2007.
v. 11, 96 p. ; 19x14 cm. (Pocket users)
ISBN 978-987-1347-45-2
1. Informática. I. TítuloCDD 005.3
00_IntrodProg.qxd
23/7/07
20:01
Page 3
Introducción
El objetivo del libro es brindar al lector un panorama general sobre el desarrollo de software para computadoras
y servir como punto de partida, tanto
para rendir el examen cero del programa Desarrollador Cinco Estrellas de
Microsoft, como para adentrarse en esta fascinante profesión. A lo largo de
los cinco capítulos que lo integran, nos
introduciremos en algunos de los principales temas que todo programador
debe conocer.
El primer capítulo trata sobre los fundamentos del software y los algoritmos.
Si bien pueden parecer un tanto triviales, son de fundamental importancia,
porque un programador debe estar preparado y capacitado para pensar cualquier solución a un problema en forma
de algoritmo. Estudiaremos, también,
distintas maneras de expresar algoritmos, necesarias como herramientas de
comunicación y documentación.
En los capítulos dos y tres, abordaremos temas relacionados con la programación estructurada. A lo largo de
ellos, aprenderemos muchos conceptos, la mayoría de los cuales se utilizan
a diario, porque forman la base de
cualquier programa. Aprenderemos los
conceptos de variable, estructuras de
control, buenas prácticas de codificación, y más. Es importante que el lector medite en profundidad cada uno
de estos temas, realice prácticas y consulte más material para asimilar correctamente cada uno de ellos y establecer una base de conocimiento sólida que le permita enfrentar cualquier
tipo de desarrollo.
Luego, el capítulo cuatro presenta una
introducción a la Programación Orientación a Objetos (POO). Este paradigma, si bien no es nuevo, presenta un
avance importante en la manera de desarrollar software, y recién en los últimos años se lo está considerando como
el paradigma por defecto para todo
nuevo desarrollo. Si bien este capítulo
no ahonda en detalles, tiene como objetivo familiarizar al lector con cada
uno de los conceptos clave de la POO.
Finalmente, el capítulo cinco introduce algunos de los conceptos del Lenguaje Unificado de Modelado (UML).
UML se basa fuertemente en los conceptos de POO, como clases y herencia, por lo que será necesario comprender los temas del capítulo cuatro para
aprovechar al máximo éste.
3
00_IntrodProg.qxd
23/7/07
20:01
Page 4
CONTENIDO ‹‹
Tipos de datos
Capítulo 1
Sentencias
FUNDAMENTOS
¿Qué es el software?
8
Un poco de historia
9
Operadores y expresiones
Tipos de operadores
Estructuras de control
Conclusiones
34
Capítulo 3
ELEMENTOS DE PROGRAMACIÓN
Modularización
Los lenguajes de programación
11
Los lenguajes en la actualidad
13
Compilación e interpretación
14
Algoritmos
15
Expresión de algoritmos
Conclusiones
36
Procedimientos y funciones
Librerías
44
Arreglos y matrices
45
15
18
Capítulo 2
PROGRAMACIÓN ESTRUCTURADA
Mejorar el código
20
Hacia la programación estructurada
Conceptos de programación
estructurada
22
Arreglos
Matrices
El estilo de programación
50
Tabulaciones
Comentarios
Conclusiones
54
Capítulo 4
PROGRAMACION ORIENTADA
A OBJETOS
Los objetos
4
Identificadores y palabras clave
Origen
Variables
Ventajas de la POO
56
00_IntrodProg.qxd
23/7/07
Clases y objetos
20:01
Page 5
58
Pensar en objetos
60
Patrones de diseño
Relaciones entre clases
Capítulo 5
UML
Propiedades y métodos
63
Relación de uso
¿Qué es UML?
76
Diagrama de clases
77
Clases
Relaciones entre clases
Relación de agregación
Diagrama de secuencia
83
Elementos del diagrama
de secuencia
Diagramas de casos de uso
85
Conclusiones
86
Apéndice
Herencia
66
Redefinir comportamiento
Extender clases
Tipos de herencia
Clases abstractas
EL EXAMEN
Algoritmos
88
Lenguajes de programación
88
Programación estructurada
89
Programación orientada
Interfaces
Polimorfismo
72
Conclusiones
74
a objetos
91
Desarrollador 5 Estrellas
92
5
00_IntrodProg.qxd
23/7/07
20:01
Page 6
Prólogo
Cuando un músico escucha una melodía que acaba de componer o cuando un escultor retoca el último detalle
de su obra maestra, está ante un logro
personal, pero que fue hecho pensando en los demás. Está ante el fruto de
su trabajo, que tuvo que realizar para
pasar de una idea o modelo que estaba
sólo en su imaginación, a algo perceptible por los otros. Y ese logro causa
una gran satisfacción.
El desarrollo de software –al igual
que la música, la escultura o la pintura– es una actividad creativa y, hasta si
se quiere, artística. Es una actividad en
la que una persona (el programador)
debe plasmar una idea en un programa
que alguien usará luego. Y es muy probable que esa idea sea algo totalmente
novedoso, algo que nadie antes ha visto. Y ver esa idea traducida en software produce una sensación que sólo un
programador puede entender.
Ser programador no es fácil, pero es
divertido. Desarrollar software es, definitivamente, una tarea compleja. Deberemos ser capaces de interpretar al
usuario, de entender qué es lo que necesita (aunque muchas veces él mismo
no sabe qué es lo que necesita). Pasaremos largas horas pensando un algoritmo que resuelva el problema de nues6
tro cliente de la mejor manera. Muchas veces nos iremos a casa y seguiremos pensando en ese dichoso algoritmo. Nos iremos a dormir (probablemente tarde, porque habremos dedicado parte de nuestro tiempo libre a
aprender nuevas tecnologías) y despertaremos pensando en el algoritmo o,
incluso, con la solución en mente. Pero cuando escribamos la última línea
del código que implementa ese algoritmo y lo veamos funcionando, obtendremos una gran satisfacción. Una satisfacción que sólo puede entender
otro programador.
Cuando escribí mi primera línea de
código, debo haber tenido unos 11
años. Ese día, al ver que una secuencia
de caracteres casi ilegible dibujaba un
círculo en la pantalla del televisor (la
época de las PCs recién comenzaba),
me di cuenta de que eso era lo que quería hacer el resto de mi vida. Creo que
cada persona que quiere dedicarse al
desarrollo de software, antes de nada,
debe experimentar qué sensación le
produce ver su programa en ejecución.
Aquel que no sienta nada jamás podrá
ser un programador, porque la mayor
recompensa es, justamente, esa sensación, aunque sean las 4 de la madrugada y llevemos 20 horas codificando.
01_IntrodProg.qxd
23/7/07
20:04
Page 7
CAPÍTULO 1
Fundamentos
En este primer capítulo, veremos los fundamentos
básicos del software, su funcionamiento, su uso
y su creación. A su vez introduciremos los principales
conceptos sobre la programación moderna.
ATENCIÓN AL LECTOR > [email protected]
01_IntrodProg.qxd
23/7/07
20:04
Page 8
FUNDAMENTOS
¿QUÉ ES EL SOFTWARE?
> Desde siempre, las computadoras han sido máquinas con la única capacidad
de llevar a cabo instrucciones, como imprimir un texto en un dispositivo de salida o sumar dos números. Un programa es un conjunto de instrucciones y datos que juntos y, de manera sistemática, permiten resolver problemas. Podemos
entonces definir el software como el conjunto de programas que funcionan en
una computadora y que permiten realizar una o varias tareas específicas.
Es importante resaltar que, al hablar de computadoras, no nos referimos sólo a
las personales (PC), sino a cualquier dispositivo capaz de leer instrucciones de
una memoria, y ejecutarlas. Por lo tanto, podemos encontrar software en un lavarropas, en un respirador artificial y hasta en automóviles modernos.
Según su uso, el software se puede clasificar en dos grandes grupos: el software
de sistema y el software de aplicación. El software de sistema es el conjunto de
programas básicos para el funcionamiento de la computadora, como por ejemplo el sistema operativo (Windows o Linux), los drivers, etcétera., mientras que
el software de aplicación son los programas para realizar tareas específicas, como un procesador de texto, un juego o un compilador.
Los términos software y sistema se utilizan para referirse a lo mismo. Sin embargo, la palabra sistema por sí sola no tiene nada que ver con el software. Un sistema es un conjunto de elementos que interactúan de alguna manera, como puede ser el sistema digestivo o el sistema solar. Otro ejemplo de sistema, son los sistemas de información, sin que esto tampoco implique un software. Por ejemplo,
un sistema contable es un conjunto de métodos y de herramientas que permiten
mantener la información sobre los movimientos económicos y los bienes de una
empresa, pero se pueden utilizar libros en papel para alcanzar el objetivo. Finalmente, un sistema de información basado en computadora es la implementación
› EL HARDWARE
El otro componente importante de una
computadora es el hardware, que incluye
todos los elementos físicos o materiales
que la conforman. La diferencia entre el
8
hardware y el software es que este último es intangible (lo vemos, pero no podemos tocarlo), mientras que el hardware es totalmente palpable.
01_IntrodProg.qxd
23/7/07
20:04
Page 9
› ¿QUÉ ES EL SOFTWARE?
con herramientas computacionales (programas y datos) de un sistema de información. Por lo tanto, como sinónimo de software podemos utilizar sistema de
información basado en computadora. Por otro lado, los términos programa y
sistema suelen utilizarse indistintamente, pero no está bien. Generalmente, podemos decir que un sistema es un grupo de programas que interactúan para realizar ciertas tareas. Un programa es una unidad mucho más pequeña, independiente y sencilla que un sistema.
UN POCO DE HISTORIA
Muchos autores coinciden en afirmar que la idea de programa como secuencia
de instrucciones se remonta a principios del siglo XIX y no tiene nada que ver con
la computación. Efectivamente, en 1801 un francés llamado Joseph Marie Jacquard ideó un mecanismo de tarjetas perforadas para controlar los dibujos que
formaban los hilos en una máquina para tejer. De esa manera, lograba programar
las puntadas de la máquina para obtener tramas y figuras repetibles.
En 1843, Ada Augusta Lovelace, hija del poeta inglés Lord Byron, planteó la idea
de usar tarjetas perforadas para controlar la Máquina Diferencial de Babbage (ver recuadro) y lograr que repita ciertas operaciones. Unos años más tarde, su idea fue tomada para desarrollar un sistema de cómputo para la oficina de censos de los Estados Unidos. Las tarjetas estaban diseñadas de tal modo que los agujeros representaban la edad, raza, sexo, etcétera. Este desarrollo permitió que el tiempo en obtener
los resultados del censo de 1890 fuera de 5 años menos que el censo anterior.
La idea de lady Ada tuvo tal repercusión que, al día de hoy, se la considera como la primera programadora, y las tarjetas perforadas fueron utilizadas en centros de cómputos hasta no hace mucho tiempo.
› LA MÁQUINA DIFERENCIAL
En el año 1812, Charles Babbage,
preocupado por los errores de cálculo en las tablas matemáticas, pensó
que sería útil poder calcularlos de
forma automática. Ideó, entonces,
una máquina capaz de obtener aproximaciones al resultado de una fun-
ción matemática, mediante un método llamado de las diferencias. Si bien
por limitaciones propias de la época
nunca llegó a terminar su construcción, su denominada Máquina diferencial sentó las bases de la computación moderna.
9
01_IntrodProg.qxd
23/7/07
20:04
Page 10
FUNDAMENTOS
Ya en el siglo XX, el físico estadounidense John Atanasoff, conocedor de las teorías de Babbage y consternado por la cantidad de cálculos que debía realizar, pensó en construir una máquina de cálculo que, a diferencia de las mecánicas, sería
digital, y su funcionamiento se basaría en el sistema binario. Su aparato fue conocido como ABC Atanasoff-Berry-Computer, y por eso es considerado el iniciador de la computación digital.
Luego, durante la Segunda Guerra Mundial, se construyó y comenzó a funcionar en instalaciones militares de los Estados Unidos una máquina llamada
ENIAC (Electronic Numeric integrator and Computer). Su funcionamiento se basaba en tubos de vacío, interruptores y relés para hacer operaciones matemáticas
utilizando el sistema binario. Por su tamaño, ocupaba una habitación entera.
Figura 1. La ENIAC fue una de las primeras
computadoras del siglo XX. Su poder de cálculo era menor
al de una calculadora de bolsillo actual.
A partir de la ENIAC, las computadoras fueron evolucionando año tras año, a un
ritmo cada vez más vertiginoso hasta llegar a las computadoras que conocemos en
la actualidad. Sin embargo, a pesar de esta evolución, las computadoras mantienen
dos características esenciales: están basadas en el sistema binario y necesitan que se
les provea de una secuencia ordenada de instrucciones para poder funcionar.
› LA MÁQUINA DE TURING
Creada por Alan Turing, consistía en
una cinta y un cabezal que podía leer y
escribir caracteres en dicha cinta, como también moverse hacia la izquierda
y hacia la derecha. La cinta podría verse como el conjunto de entradas y salidas de un programa. Turing demostró
10
que su máquina podría resolver cualquier problema representable por un
algoritmo y que, si no era capaz de resolverlo, entonces resultaba, insoluble
en una computadora. Su trabajo se
convirtió en el tema central de estudio
de la Teoría de la Computación.
01_IntrodProg.qxd
23/7/07
20:04
Page 11
› LOS LENGUAJES DE PROGRAMACIÓN
LOS LENGUAJES DE PROGRAMACIÓN
> Ahora que sabemos qué es un programa, podemos decir que la programación
es el proceso de construir programas. Para escribir programas, necesitamos conocer la lista de las posibles instrucciones que debemos proporcionar a la computadora, y cómo combinarlas para lograr los resultados deseados.
De acuerdo con el primer párrafo, para confeccionar programas, debemos escribir una secuencia de instrucciones que puedan ser entendidas por la computadora, pero como vimos anteriormente, hasta las computadoras más modernas trabajan con el sistema binario, por lo que, para programarlas, deberíamos
proporcionarles secuencias de unos y ceros formando las instrucciones de
nuestro programa. Si bien el sistema binario es muy simple, para un ser humano, resultaría imposible recordar las combinaciones de unos y de ceros que
forman cada una de las instrucciones. Para solucionar este problema, existen
los lenguajes de programación, una forma más sencilla y legible de representar las instrucciones.
Sin entrar en detalles formales, podemos decir que un lenguaje de programación es un conjunto de reglas que determinan, de forma clara, precisa y sin ambigüedades, la forma en que se le imparten las instrucciones a una computadora
para construir un programa. Estas reglas se dividen en reglas sintácticas y reglas
semánticas. Las reglas sintácticas especifican cuáles son los caracteres válidos del
lenguaje y cómo se pueden agrupar en palabras también válidas. Las reglas semánticas determinan cuál es el significado de las palabras, es decir, qué se espera que la computadora haga cuando recibe una palabra o una instrucción.
› ARQUITECTURAS CISC Y ARQUITECTURAS RISC
El conjunto de instrucciones que puede entender una computadora depende de su arquitectura (por ejemplo, un procesador Intel tiene un conjunto de instrucciones diferente del
de un procesador Motorola). La cantidad de instrucciones permiten clasi-
ficar a los procesadores en dos grandes grupos: CISC (Complex Instruction Set Computer, computadora con
conjunto de instrucciones complejo) y
RISC (Reduced Instruction Set Computer, computadora con conjunto de
instrucciones reducido).
11
01_IntrodProg.qxd
23/7/07
20:04
Page 12
FUNDAMENTOS
Los lenguajes de programación fueron creados para facilitar la escritura de programas, proveyendo una abstracción de las instrucciones reales de la computadora (formadas por unos y ceros) y reemplazándolas por palabras que fueran más fáciles de recordar por las personas. Según el nivel de abstracción que proveen, los
lenguajes de programación se clasifican en tres niveles (Tabla 1). Cuanto más alto
sea el nivel, más cercanas al lenguaje humano serán sus instrucciones y, generalmente, más poderosas, ya que cada instrucción del lenguaje puede representar
operaciones complejas formadas por muchas instrucciones de la máquina.
TIPOS DE LENGUAJES
…
NIVEL DEL LENGUAJE CARACTERÍSTICAS
Bajo
Las instrucciones del lenguaje están muy relacionadas con las instrucciones
de la computadora, por lo tanto, el programador debe tener un buen
conocimiento del funcionamiento del equipo. Al programar directamente
(o casi) con instrucciones de la máquina, se obtienen resultados eficientes
y se puede lograr cualquier cosa que haga la computadora, aunque
con un esfuerzo más grande que con otros niveles de abstracción.
Como lenguaje de bajo nivel, podemos mencionar el Assembler.
Medio
Si bien algunos autores ignoran este nivel, existe un grupo de lenguajes
con una abstracción un poco más alta que los de bajo nivel,
pero aún bastante cercana al hardware. Su sintaxis es más sencilla
que la de los lenguajes de bajo nivel, pero permiten escribir programas
cercanos al lenguaje de la computadora.
Un ejemplo típico de lenguaje de nivel medio es el lenguaje C.
Alto
Este lenguaje está formado por palabras comunes en algún idioma
(como por ejemplo el inglés), por lo que resultan fáciles de recordar
y de interpretar. Generalmente, son lenguajes más poderosos en cuanto
a expresividad que los de bajo nivel, pero puede ocurrir también
que resulte difícil implementar programas que tengan que hacer un uso
muy específico del hardware.
Como ejemplo de lenguajes de alto nivel, podemos mencionar a C# o Java.
Tabla 1. Clasificación de los lenguajes según su nivel de abstracción.
Es muy importante tener en cuenta que, aunque los lenguajes de más alto nivel puedan parecerse al lenguaje natural de los seres humanos, deberán ser más
12
01_IntrodProg.qxd
23/7/07
20:04
Page 13
› LOS LENGUAJES DE PROGRAMACIÓN
estrictos y limitados, ya que es necesario que permitan definir las instrucciones
de una forma entendible y sin ningún tipo de ambigüedad.
LOS LENGUAJES EN LA ACTUALIDAD
A lo largo del tiempo, los lenguajes han evolucionado y se adaptaron a las
necesidades y a las posibilidades computacionales de cada momento. Por eso,
actualmente disponemos de una gran cantidad de lenguajes de programación
entre los cuales elegir a la hora de comenzar un desarrollo o de aprender una
nueva tecnología.
Para tomar una buena decisión y elegir el lenguaje que más nos conviene, es
importante conocer sus características, sus capacidades y debilidades. Además,
un aspecto fundamental para tener en cuenta es el paradigma o enfoque del lenguaje, es decir, de qué forma hay que pensar y escribir el programa para resolver un problema. Los paradigmas más conocidos son el imperativo, el declarativo y el orientado a objetos. Mediante el paradigma imperativo, debemos indicar explícitamente los pasos por seguir para resolver el problema, es decir, debemos indicar el cómo. Por otro lado, el paradigma declarativo permite escribir el programa describiendo las características del problema, es decir, especificando el qué. Por último, el paradigma orientado a objetos permite modelar
y escribir los programas a partir de la abstracción de los objetos reales que forman parte del dominio del problema. Actualmente, el paradigma declarativo es
usado en el ambiente académico y con fines de investigación, mientras que, en
ambientes productivos y de negocios, se utilizan los lenguajes imperativos y, en
mayor medida, los orientados a objetos.
› ¿QUÉ ES EL ANÁLISIS LÉXICO?
La compilación de un programa es
una tarea bastante compleja, que involucra una serie de pasos conducentes a la generación del código de máquina. El primer paso se denomina
análisis léxico. Consiste en leer el
texto del programa (normalmente almacenado en uno o en varios archi-
vos) y aplicar ciertas reglas para identificar las palabras clave y las expresiones válidas del lenguaje de programación. Durante el análisis léxico, ya
pueden identificarse algunos errores
básicos, frecuentes en la mayoría de
los programas, como por ejemplo, los
identificadores mal formados.
13
01_IntrodProg.qxd
23/7/07
20:04
Page 14
FUNDAMENTOS
COMPILACIÓN E INTERPRETACIÓN
> Cuando escribimos un programa en algún lenguaje, nuestro propósito final siem-
pre será ejecutarlo en una computadora para realizar una tarea específica. Ahora
bien, al utilizar un lenguaje de programación estamos escribiendo instrucciones que
no son exactamente las que la computadora entiende. Para que nuestro programa
pueda ser ejecutado, será necesario traducir las instrucciones que hemos escrito en
forma más sencilla para que la computadora pueda entenderlas. Esta traducción resulta siempre necesaria para poder ejecutar un programa escrito en cualquier lenguaje. El tipo de traducción que se haga, permitirá clasificar los lenguajes en lenguajes
compilados y lenguajes interpretados.
En los lenguajes compilados, la traducción se realiza por única vez, almacenando
las instrucciones ya traducidas a lenguaje máquina en un archivo. De este modo, al
momento de ejecutar el programa, la computadora lee una a una las instrucciones
del archivo generado durante el proceso de compilación, y las ejecuta. Para realizar
la traducción o compilación, se utiliza un programa llamado compilador, que se
encarga de tomar cada una de las instrucciones escritas en un lenguaje de programación y traducirlas a una o más instrucciones del lenguaje de la máquina. Durante la compilación, el compilador además valida que las instrucciones que hemos escrito sean correctas y que se respeten todas las reglas del lenguaje.
Por otro lado, con los lenguajes interpretados, no existe un paso previo de compilación, sino que la traducción se realiza mientras se ejecuta el programa. Para ello,
se utiliza un software denominado intérprete, que a medida que el programa se ejecuta, lee cada una de las instrucciones y las traduce a instrucciones de la máquina,
para que sean ejecutadas. Al igual que los compiladores, los intérpretes deben validar que las instrucciones estén bien escritas para poder traducirlas sin problemas.
Generalmente, usando lenguajes compilados, se logra mayor rendimiento, ya que
el proceso de validación y de traducción se realiza una sola vez (al momento de la
compilación). Luego, una vez que el programa está compilado, cada ejecución se hace sobre una secuencia de instrucciones de la computadora. Al usar lenguajes interpretados, la validación y traducción se realiza cada vez que se ejecuta el programa.
Es más, si una misma instrucción se repite varias veces, el intérprete la validará y traducirá cada vez. Sin embargo, los lenguajes interpretados proveen cierto grado de
flexibilidad a la hora de realizar cambios en nuestro programa ya que, modificando
cualquier instrucción, estará lista para que el intérprete la tome y la traduzca.
14
01_IntrodProg.qxd
23/7/07
20:04
Page 15
› ALGORITMOS
ALGORITMOS
> Según el diccionario, un algoritmo es un conjunto ordenado de operaciones
sistemáticas que permite hacer un cálculo y hallar la solución de un tipo de problemas. Por ejemplo, cualquiera de los métodos que aprendimos en la escuela para multiplicar dos números reales es un algoritmo. El método o la receta para preparar una comida no es un algoritmo, ya que el orden de algunos pasos, muchas
veces, no importa o ni siquiera está claramente especificado (por ejemplo, podemos batir las claras a nieve antes o después de mezclar la harina con él azúcar, y
aún así vamos a poder hacer una torta).
Ahora que conocemos los algoritmos, podemos redefinir el término programa como implementación de un algoritmo determinado en un lenguaje de programación. El conocimiento del concepto de algoritmo es fundamental para todo programador ya que, en la tarea diaria de escribir programas para resolver problemas,
antes debemos descubrir y entender cuál es el algoritmo que los resuelve. Muchas
veces, como programadores, nos encontraremos siguiendo los pasos de un algoritmo con lápiz y papel para entender su funcionamiento o probar su eficacia.
EXPRESIÓN DE ALGORITMOS
Como los algoritmos no están relacionados únicamente con la programación de
computadoras, es necesario contar con métodos independientes para expresarlos
y, por lo tanto, transmitirlos a otras personas. En la actualidad, existen varias formas de expresar un algoritmo, como ser, el lenguaje natural, el pseudocódigo, los
diagramas de flujo, algunos diagramas del lenguaje de modelado UML (que veremos en el último capítulo), etcétera.
En el caso del lenguaje natural, el algoritmo es expresado por medio de oraciones en un idioma determinado. A modo de ejemplo, se puede indicar el al-
› LIBROS ÚTILES
Hay una gran cantidad de libros para
introducirse en el mundo de la programación de software y de los algoritmos. Recomendamos, Metodología de
la Programación. Algoritmos, diagra-
mas de flujo y programas, de Osvaldo
Cairo (ISBN 9701509404). En el cual,
se proponen distintas técnicas de análisis para enfrentar la solución de un
problema y llevarla a un algoritmo.
15
01_IntrodProg.qxd
23/7/07
20:04
Page 16
FUNDAMENTOS
goritmo, en lenguje natural, para ver una película en DVD de la siguiente manera: presione la tecla Open/Close para abrir la bandeja del reproductor; luego,
inserte el disco y presione la tecla nuevamente. En su televisor, seleccione la opción
DVD y por último presione la tecla Play.
Pseudocódigo
Uno de los problemas del lenguaje natural es que suele resultar excesivamente
verborrágico, ya que debe respetar en cierta medida las reglas gramaticales del
lenguaje. Para evitar este problema, se puede utilizar un pseudocódigo, que consiste en una serie de normas sintácticas y gramaticales, parecidas a las de un lenguaje de programación, pero sin tanta rigidez, y con cierta libertad en el uso y en
la combinación de las palabras.
Por ejemplo, supongamos que debemos escribir el algoritmo de Euclides para
encontrar el máximo común divisor entre dos números enteros. Usando pseudocódigo, podemos consignar:
Entrada: A, B enteros
Salida: el máximo divisor común entre A y B
Mientras A > 0 hacer:
Si A > B
› CARACTERÍSTICAS DE LOS ALGORITMOS
El estadounidense Donald Knuth, autor del libro El arte de programar
computadoras, señaló cinco propiedades que deberá tener todo algoritmo:
Finito: un algoritmo debe tener un
número finito de pasos, tras los cuales debe terminar.
Preciso: cada paso debe estar definido con precisión, rigurosidad y sin
ambigüedades.
Entradas: todo algoritmo debe tener
16
cero o más entradas, que son los datos que se le proporcionan como información variable y específica de la
instancia del problema que resuelve.
Salida: un algoritmo tiene una o más
salidas, que son el resultado del problema que intenta resolver, y que dependen de las entradas provistas.
Eficacia: los pasos deben ser suficientes
para lograr el cometido del algoritmo, es
decir, el algoritmo debe ser eficaz.
01_IntrodProg.qxd
23/7/07
20:04
Page 17
› ALGORITMOS
A := A – B
Si no
B := B – A
El Máximo Común Divisor es el contenido de A
Esto significa que la entrada al programa consiste de números enteros (A y B)
y, mientras el valor de A sea mayor que cero, se le asigna a A la resta entre A y B,
si A es mayor que B; si no, a B se la asigna la diferencia entre B y A. El resultado es el valor que queda en A luego de las sucesivas restas.
Como puede verse en el ejemplo, se especifican claramente cuáles son los datos
de entrada y de salida. Además se utilizan algunos símbolos ya conocidos (como
el signo mayor a) y otros propios del pseudocódigo (como el signo := para representar que el elemento de la izquierda toma como valor el resultado de la operación de la derecha).
Diagrama de flujo
Los diagramas de flujo son una representación gráfica de los pasos de un algoritmo. En un diagrama de flujo, cada tipo de figura tiene su significado. Su nombre se debe a que las figuras se conectan con flechas que indican la secuencia o
flujo de operación.
Si bien muchas personas utilizan sus propios símbolos y figuras al momento de
crear sus diagramas de flujo, actualmente está definido de manera clara y estándar cuáles son las figuras válidas y cuál es su significado, de manera que cualquiera que las conozca pueda interpretar el diagrama.
Los símbolos más utilizados son:
• Flecha: indica el sentido del proceso, es decir, hacia dónde hay que dirigirse para encontrar el siguiente paso.
› PARA TENER EN CUENTA
Al hacer un diagrama de flujo, hay que
tener muy presente estos aspectos:
• Debe haber un único punto de inicio
del proceso.
• Debe haber siempre un camino para llegar a la solución.
• Debe haber un único punto de fin
del proceso.
17
01_IntrodProg.qxd
23/7/07
20:04
Page 18
FUNDAMENTOS
• Rectángulo: se usa para representar un paso determinado del algoritmo.
• Rombo: representa un punto de decisión sobre la base de una condición. De
un rombo salen siempre dos flechas: una en un sentido, si se cumple la condición y otra en otro sentido, si la condición no se cumple.
Para ilustrar el concepto, vamos a especificar el algoritmo de Euclides que usamos anteriormente, mediante un diagrama de flujo.
Sí
A>0
No
Sí
Devolver A
A>B
A<A-B
No
B<B-A
Figura 2. Diagrama de flujo del algoritmo de Euclides
■ CONCLUSIONES
Para introducirse en el mundo de la programación y el desarrollo de aplicaciones de software, es muy importante comenzar con una buena base de conocimiento de algoritmos y técnicas de resolución de problemas de distinto
tipo, sin usar un lenguaje específico (podemos usar pseudocódigo). Una vez
que dominemos ciertas técnicas de resolución de problemas y podamos expresar las soluciones como algoritmos, estaremos preparados para aprender lenguajes de programación que nos permitan llevar esos algoritmos a
una computadora que los ejecute por nosotros.
18
02_IntrodProg.qxd
23/7/07
20:05
Page 19
CAPÍTULO 2
Programación
estructurada
En este capítulo, nos adentraremos en los principios
fundamentales de la programación y, al mismo
tiempo, iremos aprendiendo sintaxis de los
dos principales lenguajes de la plataforma .Net.
ATENCIÓN AL LECTOR > [email protected]
02_IntrodProg.qxd
23/7/07
20:05
Page 20
PROGRAMACION ESTRUCTURADA
MEJORAR EL CÓDIGO
> En el Capítulo 1, vimos que un programa es la implementación de un algoritmo
determinado, es decir, una secuencia de pasos o instrucciones, mediante un lenguaje de programación. Cuando escribimos un programa, es normal que necesitemos
alterar la secuencia de instrucciones siguiendo determinadas pautas. Si recordamos
el algoritmo que analizamos en el capítulo anterior, podemos ver que en cada vuelta,
la instrucción por ejecutar depende de si A es mayor que B, o B es mayor que A.
En los primeros lenguajes de programación (como el BASIC original), la única forma que existía de variar las instrucciones por ejecutar según una condición
era mediante un salto a otra parte del programa (generalmente, a otra posición
de memoria u otro número de línea). Es decir, debíamos evaluar la condición y,
dependiendo de si ésta era verdadera o falsa podíamos seguir en la línea de abajo o hacer que la próxima instrucción por ejecutar fuera diferente, ubicada en
otra parte del programa.
Por ejemplo, para resolver el algoritmo de Euclides usando saltos, deberíamos
escribir algo así (con pseudocódigo):
1: Si A <= 0 Ir a línea 7
2: Si A > 0 Ir a línea 5
3: B := B – A
4: Ir a línea 1
5: A := A – B
6: Ir a línea 1
7: Devolver A
Esta implementación tiene dos grandes problemas. El primero es que su escritura se vuelve un poco dificultosa, porque hay que conocer los números de
línea. Por ejemplo, al escribir la línea 1, ya hay que saber que vamos a saltar a
la línea 7. Si bien esto podría solucionarse usando números de línea que vayan, por ejemplo, de 10 en 10 y hacer los saltos a números más bien lejanos,
a medida que necesitemos modificar el algoritmo y escribir más líneas de código (incluso entre dos líneas ya existentes), los números disponibles se irían
acercando más y más, y podría llegar el momento en que necesitáramos renu20
02_IntrodProg.qxd
23/7/07
20:05
Page 21
› MEJORAR EL CÓDIGO
merar las líneas para tener más espacio. El segundo problema reside en la poca claridad del código. Para alguien que lo lee por primera vez, puede resultar
extremadamente difícil entender.
Dados los constantes saltos hacia atrás y hacia adelante a través de las instrucciones del programa, este tipo de código inevitablemente enredado se conoce como
código spaghetti, y la instrucción más famosa para hacer saltos de línea es GOTO
(del inglés go to, ir a).
HACIA LA PROGRAMACIÓN ESTRUCTURADA
Con el paso del tiempo, los programadores vieron que el código spaghetti resultaba muy difícil de mantener, ya sea para agregar una nueva característica al programa
o para corregir un error. Así, en la década del 60, un científico de los Países Bajos,
Edsger Dijkstra, formuló un teorema en el que demostró que cualquier programa
informático puede escribirse usando sólo tres tipos de instrucciones básicas: la secuencia, la evaluación de condiciones y la repetición o bucle de instrucciones, y
sin utilizar instrucciones GOTO. Este teorema, conocido como el Teorema de
Dijkstra, sentó las bases de lo que hoy conocemos como programación estructurada y de una gran familia de lenguajes de programación basados en este concepto.
Al escribir un programa utilizando las tres estructuras propuestas por Dijkstra,
se obtienen programas mucho más claros, fáciles de entender y de mantener ya
que, en todo momento, la lógica está a la vista, y resulta fácil encontrar cuál es la
siguiente instrucción por ejecutar.
Iteración
{
Mientras A > 0 hacer:
Si A > B
Condicional
A: = A - B
Si no
B: = B - A
Imprimir A
Imprimir B
}
Secuencia
Devolver A
Figura 1. En esta figura, vemos las tres estructuras
de control propuestas por Dijkstra en el algoritmo de Euclides.
21
02_IntrodProg.qxd
23/7/07
20:05
Page 22
PROGRAMACION ESTRUCTURADA
CONCEPTOS DE PROGRAMACIÓN
ESTRUCTURADA
> En las siguientes secciones, veremos los conceptos básicos de la programación estructurada, junto con su implementación utilizando lenguajes de la plataforma .Net.
IDENTIFICADORES Y PALABRAS CLAVE
Como vimos antes, un programa está formado por instrucciones. En los lenguajes de programación, sobre todo en los de nivel medio y alto, las instrucciones están formadas por palabras que pueden pertenecer o no a algún idioma humano. Como también vimos, los lenguajes de programación son más estrictos
que los lenguajes naturales y no se permite el uso de cualquier palabra ni tampoco cualquier combinación de ellas. Toda palabra que se pueda usar en un programa, ya sea parte del lenguaje de programación o definida por nosotros, recibe el
nombre de identificador. En todos los lenguajes de programación, los identificadores deben respetar ciertas reglas para que sea más sencillo el trabajo del compilador o del intérprete. La forma más común de representar un identificador es
por una secuencia de uno o más caracteres consecutivos (no se permiten los es-
› C# Y VB.NET
Los dos lenguajes más importantes
en .Net (y los únicos provistos por Microsoft desde la discontinuidad de J#)
son C# y Visual Basic .Net.
C# es un lenguaje orientado a objetos, que fue diseñado especialmente para la plataforma .Net (incluso
gran parte de las clases del framework están escritas en C#). Como
su nombre lo indica, C# está basado
en el leguaje C, del que se han eliminado algunos componentes para
hacerlo más seguro en cuanto al
22
modelo de ejecución y de administración de memoria.
Visual Basic .Net es una adaptación
para .Net del conocido lenguaje Visual Basic (que a su vez es una adaptación del BASIC original de los años
60) y como tal conserva muchas de
las características (y problemas) del
leguaje original. Al igual que C#, VB.Net (como también se conoce a Visual Basic .Net) está basado en el paradigma de Orientación a Objetos que
estudiaremos en el Capítulo 4.
02_IntrodProg.qxd
23/7/07
20:05
Page 23
› CONCEPTOS DE PROGRAMACIÓN ESTRUCTURADA
pacios ni los saltos de línea) seguidos de caracteres o de números. Generalmente, no se permite que los identificadores comiencen con números.
Hay una familia de identificadores que forman parte del lenguaje de programación y que sólo pueden usarse con los fines para los que fueron diseñados. Estos
identificadores reciben el nombre de palabra clave. Por ejemplo, el lenguaje C#
tiene la palabra clave using, con una semántica determinada, y al ser palabra clave, no podremos usarla en otro contexto.
VARIABLES
En cualquier programa que hagamos, necesitaremos manejar datos: el nombre
del usuario, el saldo de una cuenta corriente, la cantidad de veces que iteramos
sobre una línea de código. Y es natural que estos datos cambien de valor durante la vida del programa. Así, nos encontramos con uno de los conceptos más básicos de la programación, el concepto de variable. Una variable es una referencia a un dato cuyo valor puede cambiar durante la ejecución de un programa.
Siendo un poco más formales, podemos decir que una variable es una posición
en la memoria de la computadora, donde reside un dato significativo para el programa. Para que resulte más fácil la lectura y la comprensión de los programas, las
variables reciben nombres formados por identificadores válidos. Así, por ejemplo,
› LA PLATAFORMA .NET
Microsoft .Net es una plataforma para
desarrollo y ejecución de software preparada para enfrentar los nuevos desafíos en el mundo de la programación
de aplicaciones. Esta plataforma consiste de una familia de lenguajes de
programación, una librería de clases
común a todos los lenguajes de la plataforma y un entorno de ejecución controlado. Una de las características más
importantes de .Net es que el código
no se traduce directamente a instrucciones máquina, sino a un lenguaje in-
termedio que luego, durante le ejecución del programa, es traducido a código máquina. Esto brinda la posibilidad
de escribir programas independientes
de la plataforma de hardware y del sistema operativo. Además, la biblioteca
de clases comunes, junto con una especificación común a todos los lenguajes (conocida como CLS o Common Laguage Specification, Especificación de
Lenguaje Común), permite una perfecta y fácil interacción entre diferentes
lenguajes de la plataforma.
23
02_IntrodProg.qxd
23/7/07
20:05
Page 24
PROGRAMACION ESTRUCTURADA
en el caso de la implementación del algoritmo de Euclides que hemos estudiado,
usamos dos variables, denominadas A y B. Es una buena práctica de programación dar nombres significativos a las variables, de manera de hacer evidente la intención que tenemos al momento de definirla. Si en un programa llamamos a una
variable c, alguien que lea luego el programa (incluso podríamos ser nosotros mismos) no podrá entender rápidamente cuál es el dato que representa; mientras que
si usamos nombreCliente, está claro que, en la ubicación de memoria referenciada
por la variable, estamos almacenando el nombre de un cliente.
Figura 2. Según Google Trends, existe una tendencia a usar más
el lenguaje C# que VB.Net, además de haber más noticias en
Internet sobre C# (fuente www.google.com/trends?q=visual+basic%2Cc%23).
TIPOS DE DATOS
Un tipo de dato es el conjunto de valores que toma una variable, junto con las
operaciones que pueden realizarse sobre esos valores. En muchos lenguajes, es necesario asociar una variable con un tipo de dato, de manera que el compilador o
intérprete pueda validar que las operaciones que hemos escrito realmente corresponden al tipo de dato que manejamos. Un ejemplo de tipo de datos es el número entero. El conjunto de valores que puede tomar es el de los números enteros (0, 1, 2, -1, etcétera), y las operaciones que podemos realizar sobre ellos son
la suma, la resta, la multiplicación y la división de enteros.
24
02_IntrodProg.qxd
23/7/07
20:05
Page 25
› CONCEPTOS DE PROGRAMACIÓN ESTRUCTURADA
Los lenguajes que exigen que definamos el tipo de dato de las variables antes de
usarlas se denominan fuertemente tipados. Desde el punto de vista de la detección
de errores, estos lenguajes presentan una gran ventaja frente a los no tipados ya que
permiten detectar, durante la compilación (en el caso de lenguajes compilados), algunos errores muy habituales, como intentar asignar a una variable un dato que no
le corresponde. Por ejemplo, si definimos la variable numerador como de tipo entero, pero más adelante le asignamos el texto Hola Mundo, el compilador de un lenguaje fuertemente tipado detectará el problema y nos lo informará antes de
compilar. Si el lenguaje no es fuertemente tipado, esto no se detectará y podría ocurrir que, luego, cuando queramos hacer una multiplicación entre la variable numerador y el valor 5, la operación resulte en un error. Al no ser detectado por el compilador, este error surgirá durante la ejecución del programa.
En la plataforma .Net, C# es un lenguaje fuertemente tipado, mientras que en
VB.Net existe la posibilidad de indicarle al compilador que exija o no la declaración de los tipos de datos de las variables.
SENTENCIAS
Las sentencias describen acciones que serán ejecutadas por la computadora como parte del funcionamiento del programa. Éstas se diferencian de las instruc-
› LOS COMENTARIOS EN C# Y EN VB.NET
Los comentarios son un caso especial de sentencia no ejecutable que
permiten escribir un texto libre con el
objetivo de aumentar la legibilidad del
código. En cada lenguaje, la forma de
escribir comentarios varía, como así
también los tipos de comentarios que
se pueden escribir (de una sola línea
o de muchas líneas).
En C#, los comentarios de una sola
línea se escriben comenzando con
los caracteres // (por ejemplo: //esto es un comentario), mientras que
los comentarios de muchas líneas
se escriben comenzando con los caracteres /* y finalizando con */. Entre ambos caracteres, puede haber
texto y saltos de línea.
En VB.Net, los comentarios de una
sola línea se escriben comenzando
con la comilla simple (‘), y no existen los comentarios múltiple línea
(aunque se los puede simular escribiendo líneas consecutivas de comentarios, todas comenzadas con
comilla simple).
25
02_IntrodProg.qxd
23/7/07
20:05
Page 26
PROGRAMACION ESTRUCTURADA
ciones en que una sentencia puede ser traducida por el compilador o por el intérprete en cero o más instrucciones de máquina. Es decir, una sentencia posee
mayor abstracción y expresividad que una instrucción.
Las sentencias pueden clasificarse en ejecutables y no ejecutables. Las ejecutables son las que se traducen en instrucciones de la máquina y se utilizan para realizar acciones concretas (como sumar dos números o imprimir un texto en la
pantalla del monitor). Por el contrario, las sentencias no ejecutables no se traducen a instrucciones, y su única funcionalidad reside en aumentar la legibilidad
del código fuente, ya que se pueden usar como comentarios para explicar detalles de la implementación.
Al mismo tiempo, las sentencias pueden ser clasificadas en simples y compuestas. Una sentencia simple es una sentencia en sí misma y no contiene a ninguna
otra. Por ejemplo, la sentencia de asignación utilizada para asignar un valor a una
variable es una sentencia simple, ya que comprende únicamente la acción de asignar el valor a la variable. Por otro lado, las sentencias compuestas están formadas
por dos o más sentencias que se ejecutan de acuerdo con alguna estructura de
control, tal como veremos más adelante.
OPERADORES Y EXPRESIONES
Anteriormente, vimos que un tipo de datos define los posibles valores que puede tomar una variable con el conjunto de operaciones o de acciones que se realizan sobre esos valores. Podemos definir un operador como el símbolo utilizado
para indicar una operación sobre elementos llamados operandos. Por ejemplo,
para el tipo de dato número entero, está definida la operación de suma, que puede representarse mediante el signo +. Así, si escribimos 2 + 3, indicamos que queremos aplicar la operación de suma a los valores (operandos) 2 y 3. Del mismo
modo, si escribimos saldo + 100, mostramos que queremos sumar 100 al valor
almacenado en la variable identificada como saldo. Cuando unimos dos o más
constantes o variables mediante un operador, definimos una expresión.
Las expresiones pueden ser simples o compuestas. Una expresión simple es
aquélla sólo formada por un operador con los operandos necesarios. Una expresión compuesta está formada por dos o más operaciones simples. En el caso de las expresiones compuestas, es necesario especificar con exactitud el orden en que se resuelven las expresiones simples que las componen. En el caso
de operadores matemáticos, generalmente, el orden en que se resuelven es el
26
02_IntrodProg.qxd
23/7/07
20:05
Page 27
› CONCEPTOS DE PROGRAMACIÓN ESTRUCTURADA
tradicional (es decir, primero el producto y la división, y luego la suma y la
resta). Si al momento de escribir la expresión no tenemos del todo claro cuál
es el orden de prioridad de los operadores, podemos utilizar paréntesis para alterar o hacer implícito el orden de resolución.
TIPOS DE OPERADORES
Para no entrar en detalles muy matemáticos, podemos identificar tres grandes
tipos de operadores: los lógicos, los aritméticos y los relacionales.
Operadores lógicos
Los operadores lógicos relacionan uno o más valores de verdad (es decir, valores
que pueden ser verdadero o falso), y el resultado es otro valor de verdad en función
de los operandos. Dentro de los lenguajes de programación, los operadores lógicos
se utilizan para evaluar condiciones y modificar así el flujo de control de un programa. Los operadores lógicos más conocidos son no (NOT), o (OR), y (AND).
En la Tabla 1, pueden apreciarse los valores devueltos por los operadores lógicos más
conocidos (usamos los nombres en inglés para ir familiarizándonos con los lenguajes de programación). En la jerga de la programación llamamos operadores booleanos a los operadores lógicos, por su relación con el álgebra de Boole.
…
OPERADORES BOOLEANOS
A
F
F
V
V
B
F
V
F
V
NOT A
V
V
F
F
A AND B
F
F
F
V
A OR B
F
V
V
V
A XOR B
F
V
V
F
Tabla 1. Tabla de verdad de los cuatro operadores lógicos más comunes.
Operadores aritméticos
Como hemos visto, los operadores aritméticos se corresponden con las operaciones matemáticas tradicionales: la adición, la sustracción, la potenciación.
Operadores relacionales
Los operadores relacionales, tal como su nombre lo indica, establecen una relación entre sus operandos. Un ejemplo clásico de operador relacional es el ope27
02_IntrodProg.qxd
23/7/07
20:05
Page 28
PROGRAMACION ESTRUCTURADA
rador mayor que, notado con el símbolo >. En
general, los operadores relacionales devuelven un
valor de verdad, por lo que son usados en expresiones lógicas (por ejemplo, el valor de verdad de
la expresión 7 > 9 es falso).
Figura 3. George Boole fue el precursor
en el álgebra de Boole, de donde se tomó
el concepto de Operador lógico o booleano.
Tipos de operadores
Según la cantidad de operadores con los que trabaja un operador puede clasificarse en unario o binario. Un operador unario se aplica sólo a un operador. El ejemplo más común es el signo -, utilizado para indicar números negativos (por ejemplo, -15). Los operadores binarios poseen dos operandos, como por ejemplo, el signo +. El caso de los operadores binarios puede extenderse y generalizarse para una cantidad cualquiera de operadores, en cuyo caso se denominan operador n-arios, donde n es la cantidad de operandos. Como ejemplo, podemos imaginar un operador 4-ario llamado M que devuelve
el máximo entre cuatro números. Una operación con este operador podría escribirse como M 7 2 0 9.
Según el lugar donde están los operadores con respecto a los operandos en
una operación, un operador puede clasificarse en infijo, prefijo o sufijo. Un
operador prefijo se coloca delante de los operadores. El ejemplo que veíamos
antes para el signo -, usado como operador unario, es un caso de operador
› EL LENGUAJE PASCAL
El lenguaje Pascal fue creado por Niklaus Wirth con el objetivo de lograr
un lenguaje fácil de aprender, para
usar en sus clases de programación,
y lo bautizó en honor a Blaise Pascal,
inventor de la primera calculadora
mecánica. Pascal es un lenguaje es-
28
tructurado y fuertemente tipado, además de contar con características que
hacen posible un buen grado de encapsulamiento. Actualmente, muchas
universidades lo siguen utilizando para introducir los conceptos de programación estructurada.
02_IntrodProg.qxd
23/7/07
20:05
Page 29
› CONCEPTOS DE PROGRAMACIÓN ESTRUCTURADA
prefijo. Si el operador se coloca entre los operandos, entonces de denomina
infijo (es el tipo más común entre los operadores matemáticos). Por ejemplo,
el signo de adición (+) es un operador infijo, ya que se lo escribe entre los operandos (2 + 4). Por último, los operadores sufijos son aquellos que se colocan
detrás de los operandos. Un ejemplo no tan conocido es el operador ++ del
lenguaje C#. Este operador devuelve el valor del operador y luego le suma uno
(podemos escribir por ejemplo: a++).
ESTRUCTURAS DE CONTROL
Normalmente, el flujo de ejecución de un programa es secuencial, porque se
ejecuta una sentencia tras otra. Las estructuras de control permiten modificar
el flujo secuencial, alterando el orden de las sentencias bajo determinadas circunstancias. En los lenguajes estructurados, existen dos estructuras de control
básicas: la selección y la repetición o iteración. Según el teorema de Dijkstra,
estas dos estructuras, junto con la secuencia, son suficientes para escribir cualquier programa.
La estructura de selección
La estructura de selección permite determinar si una sentencia (simple o compuesta) será ejecutada o no a partir de la evaluación de una determinada condición o expresión booleana. En la mayoría de los lenguajes actuales, la selección
se realiza mediante el uso de la estructura if (si). La forma más básica de esta estructura es la siguiente (en pseudocódigo):
Si (expresión booleana)
Entonces ejecutar sentencia A
Si no ejecutar sentencia B
La segunda parte (la del si no) es optativa y permite ejecutar una acción alternativa para cuando el resultado de la expresión lógica es falso.
En C#, la estructura de selección se escribe mediante la palabra if seguida de
la expresión entre paréntesis y debajo la sentencia por ejecutar en caso de que la
expresión sea verdadera. En forma opcional, puede haber un else (si no) para especificar una acción en caso de que la expresión lógica sea falsa.
29
02_IntrodProg.qxd
23/7/07
20:05
Page 30
PROGRAMACION ESTRUCTURADA
// C#
if( saldo > montoExtraccion )
RealizarExtraccion(montoExtraccion);
else
MostrarMensaje(“No tiene suficiente saldo”);
En Visual Basic .Net, la estructura de selección es muy similar, aunque con algunas diferencias: la expresión no necesita ir entre paréntesis y debe ser seguida
por la palabra reservada THEN (entonces). Además, VB.Net provee la posibilidad
de escribir la acción en la misma línea del if o en la línea siguiente. En caso que
se escriba debajo, la estructura debe ser cerrada usando end if.
‘ VB.Net
If saldo > montoExtraccion Then
RealizarExtraccion(montoExtraccion)
Else
MostrarMensaje(“No tiene suficiente saldo”)
End If
‘ Tambien podemos hacer esto:
If b > 0 Then c = a / b else c = 0
En muchos lenguajes, existe otra estructura de selección alternativa, denominada
comúnmente case o select case, que permite especificar varias alternativas por ca-
› CLASIFICACIONES DE LOS TIPOS DE DATOS
Los tipos de datos pueden clasificarse en tres grupos: los ordinales, los
no ordinales y los compuestos. Los tipos de datos ordinales son aquellos
cuyos valores pueden ser ordenados
(se establece una relación de orden
entre cualquier par de valores), como
por ejemplo, los números enteros o
30
los caracteres de la tabla ASCII. Los
valores de los tipos no ordinales no
pueden ordenarse, como es el caso
de los números decimales de gran
precisión o los punteros a memoria.
Los tipos de datos compuestos incluyen las cadenas de caracteres y las
estructuras de datos.
02_IntrodProg.qxd
23/7/07
20:05
Page 31
› CONCEPTOS DE PROGRAMACIÓN ESTRUCTURADA
sos, según el valor de una variable o de una expresión. Si bien el select case puede
escribirse perfectamente como una secuencia de estructuras if, en pseudocódigo,
podemos escribir la selección múltiple de esta manera:
SegunEsElValorDe expresión hacer:
C1: sentencia 1
C2: sentencia 2
…
Sino: sentencia n
El funcionamiento de esta estructura es así: se evalúa la expresión, y su valor se
compara con la lista de constantes C1, C2, etcétera. Si el valor de la expresión está comprendido en la lista de constantes, se ejecuta la sentencia correspondiente
(por ejemplo, si el valor de la expresión es C2, se ejecuta la sentencia 2). Si el valor de la expresión no está en la lista de constantes, y hay un sino, se ejecuta la sentencia asociada a éste, caso contrario, la ejecución continúa en la línea siguiente.
En C#, la estructura de selección múltiple cumple la misma función, pero se
denomina switch, y su sintaxis es la siguiente:
// C#
switch(variable)
{
case 10:
HacerAlgo();
break;
case 20:
HacerOtraCosa();
break;
default:
HacerAlgoDistinto();
break;
}
En Visual Basic .Net, la misma estructura selectiva múltiple se denomina Select Case y se escribe así:
31
02_IntrodProg.qxd
23/7/07
20:05
Page 32
PROGRAMACION ESTRUCTURADA
‘ VB.Net
SELECT CASE variable
CASE 10: HacerAlgo()
CASE 20: HacerOtraCosa()
CASE ELSE: HacerAlgoDistinto();
END SELECT
Las estructuras de repetición
Muy a menudo, necesitamos repetir una sentencia más de una vez para completar
el objetivo del algoritmo. Las estructuras de repetición de los lenguajes estructurados permiten ejecutar una sentencia una cierta cantidad de veces o mientras se cumpla una determinada condición. Por ejemplo, si volvemos a mirar el algoritmo del
Capítulo 1, podremos notar que repetimos las sentencias que hacen las restas entre las
dos variables mientras que el valor de la variable A sea mayor que cero.
Como decíamos en el párrafo anterior, la mayoría de los lenguajes estructurados
proveen dos tipos de estructuras de repetición: una para repetir una sentencia mientras se cumpla una condición (es decir, mientras el resultado de evaluar una expresión lógica sea verdadero), y otra para repetir una sentencia una cantidad determinada de veces. La sentencia que se ejecuta repetidas veces se denomina bucle.
La estructura de repetición condicional se denomina comúnmente while
(mientras). En el lenguaje C#, simplemente se la escribe seguida de la expresión
lógica entre paréntesis y debajo la sentencia a ejecutar:
// C#
While( a > 0 )
a = a - b;
En Visual Basic .Net, es muy similar, con la diferencia que se debe marcar el final de la estructura con las palabras clave End While, y no es necesario escribir
la expresión booleana entre paréntesis:
‘ VB.Net
While a > 0
a = a - b
End While
32
02_IntrodProg.qxd
23/7/07
20:05
Page 33
› CONCEPTOS DE PROGRAMACIÓN ESTRUCTURADA
En algunos lenguajes, existe una variante de esta estructura de repetición, consistente en evaluar la expresión lógica al final. La diferencia radica en que, al evaluar la condición al final, la sentencia por repetir se ejecutará por lo menos una vez, mientras
que, si la evaluación se hace al comienzo, la sentencia podría no ejecutarse nunca.
Al momento de usar una estructura while, se debe prestar especial atención a la
expresión lógica que determina el fin del bucle (también conocida como condición de corte), ya que escribirla mal, podría hacer que el bucle nunca finalizara
(bucle infinito). Por ejemplo, si la condición de corte es que el valor de una variable llegue a cero, pero olvidamos modificarlo dentro del bucle, el valor permanecerá constante, y el ciclo nunca finalizará.
La otra estructura de control repetitiva con la que disponemos en la mayoría de
los lenguajes estructurados es la que se conoce como For. Esta estructura permite especificar que se quiere repetir la sentencia una cantidad finita de veces, usando una variable como contador. A cada iteración, la variable contador se incrementa automáticamente (es decir, no necesitamos preocuparnos por actualizarla
como sucede con while). En algunos lenguajes, como Pascal, no se permite modificar la variable contadora dentro del bucle para facilitar la traducción y validación del código durante la fase de compilación.
En C#, la estructura for es un poco difícil de comprender al comienzo, pero
una vez que nos familiarizamos con ella, vemos que tiene una gran capacidad de
expresividad y de flexibilidad para escribir repeticiones complejas. Al escribir un
for en C#, debemos especificar el valor con el que comienza la variable contadora, la condición que determina que el contador ha llegado al valor final y la
expresión que incrementa el contador. Esto último parece contradecir lo que
mencionábamos antes acerca de que no es necesario preocuparnos por modificar el contador. Y es cierto. En ese aspecto, la estructura for de C# es parecida
a la estructura while. Veámosla en un ejemplo:
// C#
for(i=0;i<10;i++)
HacerAglo();
Como se puede ver en el ejemplo, la estructura consta de tres partes fundamentales separadas por punto y coma. La primera especifica el valor del contador con
33
02_IntrodProg.qxd
23/7/07
20:05
Page 34
PROGRAMACION ESTRUCTURADA
el que comienza la iteración. La segunda, la condición para finalizar. Y la última
especifica la expresión que modifica el valor del contador en cada iteración. En
este ejemplo, la sentencia HacerAlgo se ejecutará diez veces. El for de C# soporta algunas variantes que lo hacen más complejo y potente, pero su explicación excede el alcance de este libro.
En Visual Basic .Net, la estructura for es mucho más sencilla e intuitiva, pero
también menos potente. Además, se diferencia de la versión en C# en que debe
ser cerrado con la palabra reservada Next (siguiente). Continuando con el ejemplo anterior, en Visual Basic podemos escribir:
‘VB.Net
For i=1 to 10
HacerAglo()
Next
En este ejemplo, la sentencia HacerAlgo se repetirá diez veces, y, en cada iteración, el valor de la variable i se incrementará automáticamente en 1.
■ CONCLUSIONES
En el momento de su aparición, la programación estructurada significó un importante avance en las formas de construir software, sobre todo por el impacto que causó en los costos de mantenimiento. La facilidad de mantenimiento
se debe a que, con la programación estructurada, conseguimos programas
más fáciles de entender y de depurar (por la ausencia de instrucciones GOTO),
además, los bloques de código son más claros.
34
03_IntrodProg.qxd
23/7/07
20:41
Page 35
CAPÍTULO 3
Elementos de
programación
En el capítulo anterior, hemos introducido los conceptos
fundamentales de la programación estructurada,
tal como fue planteada por Dijkstra en su famoso
teorema. Ahora veremos algunos conceptos interesantes
que, si bien se encuentran en la mayoría de los
lenguajes estructurados de la actualidad, son un poco
más generales y pueden aplicarse, prácticamente,
en cualquier paradigma de programación.
ATENCIÓN AL LECTOR > [email protected]
03_IntrodProg.qxd
23/7/07
20:41
Page 36
ELEMENTOS DE PROGRAMACIÓN
MODULARIZACIÓN
> En nuestro trabajo como programadores, nos encontramos a diario con la
obligación de resolver problemas. A medida que la tecnología avanza y las necesidades de los usuarios son más exigentes, los problemas que debemos resolver se tornan más complejos. Esto hace que la escritura de los programas sea
bastante difícil, y que su complejidad los vuelva inaccesibles. Tarde o temprano, la cantidad de variables y elementos que debemos manejar superará los límites normales de la mente humana.
Una técnica muy común para facilitar la escritura de programas de gran tamaño o que necesiten resolver problemas muy complejos consiste en dividir el problema en problemas más pequeños. De esta manera, cada dificultad será más fácil de entender y, por lo tanto, resultará más sencillo encontrar un algoritmo que
lo resuelva. Así, la construcción del programa se reduce a la construcción de pequeñas unidades o subprogramas que se conectan de alguna manera para resolver juntas el problema original. Esta técnica se conoce como Modularización, y
a cada subprograma se lo denomina módulo.
Un programa escrito usando la técnica de modularización se resume en un
conjunto de subprogramas que se llaman unos a otros. Cada programa incluye
una lista de sentencias (de cualquiera de los tres tipos que estudiamos en el capítulo anterior) y, eventualmente, llamadas a otros subprogramas. Cuando hablamos de llamar o invocar a un subprograma, lo que queremos decir es que,
desde un subprograma (o incluso desde el programa principal), se deriva la ejecución al subprograma llamado. Por ejemplo, en el siguiente pseudocódigo:
Programa Principal:
1
A()
2
B()
3
Fin.
Subprograma A:
3
Imprimir la palabra “Hola”
Subprograma B:
4
36
Imprimir la palabra “Mundo”
03_IntrodProg.qxd
23/7/07
20:41
Page 37
› MODULARIZACIÓN
El programa principal llama o invoca al subprograma A y luego al subprograma B. Y aquí surge uno de los primeros conceptos clave de la modularización: un subprograma o módulo debe ser un algoritmo en sí mismo (de acuerdo con la definición que vimos en el Capítulo 1), es decir, que debe consistir de
una secuencia finita de pasos tras la cual finaliza. Entonces, cuando se hace una
llamada a un subprograma, éste se ejecuta (o sea, se ejecutan sus sentencias) y,
al terminar, el control vuelve al punto desde donde se hizo la invocación. Retornando sobre el ejemplo anterior, concluimos que la secuencia de ejecución
será: comienza el programa, se deriva la ejecución al subprograma A, se imprime la palabra Hola, el control vuelve a la línea 2, se deriva el control al subprograma B, se imprime la palabra Mundo, el control vuelve a la línea 3, y el
programa termina.
PROCEDIMIENTOS Y FUNCIONES
Los módulos se pueden clasificar en procedimientos y en funciones. Las funciones devuelven un único valor al programa o subprograma que hizo la llamada. Los procedimientos, generalmente, no devuelven ningún valor, aunque podrían hacerlo. Las funciones se usan para resolver algún cálculo y devolver el resultado, mientras que los procedimientos se utilizan para realizar una tarea concreta, pero que no involucre devolver nada al programa o subprograma que lo llamó. En la mayoría de los lenguajes de programación, los procedimientos y funciones deben recibir un nombre consistente en un identificador válido según las
reglas léxicas del lenguaje.
Otra de las ventajas de la división en subprogramas reside en que nos permite usar el mismo grupo de sentencias varias veces en el mismo programa sin necesidad de volver a escribirlas. Sin embargo, lo más probable es que en los distintos lugares donde podamos llamar a un procedimiento o función, los datos
› DIVIDE Y VENCERÁS
La técnica de dividir un problema en
problemas más pequeños que sean
más fáciles de resolver se conoce habitualmente como la técnica de Dividir y vencer, en referencia a pensa-
mientos de Nicolás Maquiavelo. En su
libro El Príncipe, Maquiavelo habla
sobre generar discordia entre los
pueblos, de manera que se separen y
así poder dominarlos más fácilmente.
37
03_IntrodProg.qxd
23/7/07
20:41
Page 38
ELEMENTOS DE PROGRAMACIÓN
que necesitamos manejar no sean los mismos. Es necesario, entonces, poder
pasarle información adicional al subprograma. Esto se realiza mediante el uso
de parámetros o argumentos. Un parámetro es una variable que el procedimiento o función recibe como dato de entrada y que puede usar para cambiar
su comportamiento sobre la base del valor recibido (usando, por ejemplo, una
sentencia condicional).
Los parámetros del argumento pueden ser de entrada o de salida. Los de entrada son los que el programa o módulo que llama le pasa al subprograma,
mientras que los parámetros de salida son aquellos que el procedimiento o
función utiliza para devolver información a quien lo llamó. Es importante no
confundir un parámetro de salida con el resultado de una función: los parámetros de salida pueden ser usados tanto en procedimientos como en funciones, y se puede utilizar la cantidad que sea necesaria, mientras que el valor de
resultado sólo se puede usar en las funciones, y no puede haber más de uno
por función.
Para ilustrar el uso de funciones, retomemos el ejemplo del algoritmo de Euclides y convirtámoslo en una función (en pseudocódigo):
Funcion MCM(A,B)
Mientras A > 0 hacer:
Si A > B
A := A – B
Si no
B := B – A
Devolver A
En este caso, la función se llama MCM y posee dos parámetros de entrada, de
tipo numérico, A y B. La función MCM consiste de una única sentencia de repetición que hace restas sucesivas entre A y B y, finalmente, devuelve el valor
de A como resultado de la función.
Para entender el uso de una función, vamos a utilizar la función MCM del ejemplo anterior dentro de un procedimiento:
38
03_IntrodProg.qxd
23/7/07
20:41
Page 39
› MODULARIZACIÓN
Procedimiento MostrarMCM()
Pedir un numero y guardarlo en V1
Pedir un numero y guardarlo en V2
M := MCM(V1, V2)
Mostrar valor de M
En el ejemplo, se pide al usuario que ingrese un número y se lo almacena en la
variable V1. Se hace lo mismo con la variable V2. Luego, a la variable M se le
asigna el resultado de la función MCM llamada con los valores V1 y V2 (los parámetros A y B del ejemplo). Por último, se muestra el valor de la variable M.
Ámbito de variables
El ámbito de una variable es el contexto o porción de programa en el que la
variable está definida, es conocida y accesible para ser usada. Si bien cada lenguaje de programación tiene sus propias reglas de ámbito (es decir, las reglas que definen dónde es accesible una variable definida en algún punto de un programa),
los subprogramas delimitan el ámbito de las variables.
Si el ámbito de una variable incluye sólo un subprograma, entonces se dice que
es una variable local (local al procedimiento o función), mientras que si abarca
todos los módulos, la variable será global. Una variable local sólo existe mientras
se ejecuta el subprograma que la contiene, mientras que una variable global existe durante toda la ejecución, y su valor puede consultarse o modificarse desde
cualquier punto del programa o subprogramas.
Algunos lenguajes de programación como Pascal permiten escribir programas
dentro de subprogramas, lo cual agrega niveles extras de visibilidad o ámbito
de las variables, ya que una variable local a un procedimiento puede ser acce-
› USO DE PARÁMETROS DE ENTRADA Y DE SALIDA
Si bien un procedimiento puede tener
parámetros de salida, esto suele no
resultar en una buena práctica de programación. A menos que no podamos
hacer otra cosa, si necesitamos devol-
ver un valor desde un subprograma,
siempre es mejor usar una función.
Esto dará un único punto de salida al
subprograma, mejorando la claridad y
la legibilidad del código.
39
03_IntrodProg.qxd
23/7/07
20:41
Page 40
ELEMENTOS DE PROGRAMACIÓN
dida desde un procedimiento interno (Figura 1). En C# y Visual Basic .Net, las
reglas de ámbito determinan que las variables pueden ser globales dentro de
una clase (ya veremos qué es una clase en el siguiente capítulo), o locales a un
procedimiento o función.
Programa principal
Declarar variable A
A:=2
Llamar a B()
Al ejecutar el programa, la variable A
tendrá el valor 13, como resultado de
la asignación global que se hace en el
procedimiento C.
Procedimiento (B)
Declarar variable B1
La variable A es global al programa.
B1:=A*2
La variable B1 es local al
Llamar a C()
procedimiento B.
Procedimiento C()
La variable B1 es local al procedimiento
Declarar variable C1
B, pero como el procedimiento C
C1:=B1*3
está dentro de B, la variable B1 se
A:=C1+1
convierte en global para C. La variable
Fin procedimiento C
C1 es local al procedimiento C.
Fin procedimiento B
Figura 1. Algunos lenguajes permiten definir procedimientos
dentro de otros, extendiendo el ámbito de las variables locales.
Un punto importante por tener en cuenta es que el ámbito de las variables
se ve reducido cuando hay un conflicto de nombres. En los casos en que se
produzca este conflicto, el ámbito se resuelve asignando prioridad a la variable que se encuentra definida más adentro en el anidamiento de programa y
subprogramas. En estos casos, decimos que la variable de más adentro oculta
a la de más afuera. Esto significa que, si tenemos una variable visible desde un
procedimiento y dentro de ese procedimiento declaramos una variable con el
40
03_IntrodProg.qxd
23/7/07
20:41
Page 41
› MODULARIZACIÓN
mismo nombre, esta última tendrá prioridad sobre la global, y al asignarle un
valor, se asignará a la variable local.
Programa principal
Mediante esta llamada se pasa el valor de
Declarar variables A y B
B como parámetro A al procedimiento P1.
A:=2
B:=3
Llamar a P1(B)
El procedimiento P1 tiene un parámetro
Procedimiento P1(A)
Declarar variable B
B:=A*2
Imprimir B
llamado A, que por estar en un nivel de
anidamiento más interno ”oculta” a la
variable A del programa principal.
La variable B de P1 oculta a la variable B
del programa principal, por lo que el
procedimiento P1 imprimirá el valor 6.
Figura 2. En este ejemplo, se puede ver el concepto
de ocultamiento con variables y con parámetros.
Parámetros por valor y por referencia
Existen dos formas de pasar parámetros a un procedimiento o función: por valor
o por referencia. Al pasar un parámetro por valor, tal como su nombre lo indica,
lo que le estamos pasando al subprograma es el valor de la variable, es decir, que el
subprograma recibe una copia de la variable original con el valor de ella. Al pasar
› ¡CUIDADO CON LAS VARIABLES GLOBALES!
Muchas veces las variables globales son tentadoras, porque evitan la
necesidad de pasar parámetros de
un subprograma a otro para utilizar
el valor de una variable. Sin embargo, en la práctica, el uso indiscrimi-
nado de variables globales no es recomendado porque dificulta el
mantenimiento del programa, dado
que es difícil determinar en qué
momento una variable global recibió algún valor.
41
03_IntrodProg.qxd
23/7/07
20:41
Page 42
ELEMENTOS DE PROGRAMACIÓN
un parámetro por referencia, lo que se le pasa al subprograma es la dirección de memoria donde se encuentra el valor de la variable. Es decir, se le pasa una referencia
al valor. Dado que se pasa la dirección de memoria donde se encuentra el valor,
cualquier asignación que se le haga al parámetro se efectuará en realidad sobre la
variable original, ya que se escribe en la misma dirección de memoria.
Muchos lenguajes de programación (entre ellos Visual Basic .Net) no tienen el
concepto de parámetro de salida como parte del lenguaje, por lo que es necesario utilizar parámetros por referencia para devolver un valor al programa o subprograma que hizo la llamada.
Procedimientos y funciones en C# y Visual Basic .Net
Obviamente, los dos lenguajes de .Net provistos por Microsoft proveen mecanismos para definir subprogramas.
En C#, el concepto de procedimiento no existe como tal, sino que todo son
funciones. Sin embargo, es posible definir procedimientos como funciones que
no devuelven un resultado, utilizando el tipo de dato void (que es un tipo de datos nulo). En Visual Basic .Net, los procedimientos se definen mediante la palabra clave Sub. Veámoslo con ejemplos.
// C#
void HolaMundo()
{
Console.WriteLine(“Hola Mundo”);
}
‘ VB.Net
Sub HolaMundo()
Console.WriteLine(“Hola Mundo”)
End Sub
En el caso de C#, el procedimiento (que en realidad es una función) comienza
con el tipo de dato devuelto, void, seguido del nombre del procedimiento con la
lista de parámetros entre paréntesis (que en el ejemplo está vacía). La secuencia
de sentencias que compone el procedimiento en C# está delimitado por las lla42
03_IntrodProg.qxd
23/7/07
20:41
Page 43
› MODULARIZACIÓN
ves de apertura y cierre (los caracteres { y }). Por otro lado, en VB.Net, la definición del procedimiento comienza con la palabra clave Sub, seguida del nombre
del procedimiento y la lista de parámetros. A diferencia de C#, en Visual Basic
.Net no se utilizan caracteres para marcar el comienzo de las sentencias del procedimiento, pero sí se utiliza End Sub para marcar el fin.
La forma de definir una función en C# es igual que en el ejemplo anterior,
reemplazando la palabra void por el tipo de dato que se necesite devolver (por
ejemplo, int para enteros). En VB.Net, la definición de una función es sustancialmente diferente, ya que debe utilizarse la palabra reservada Function en lugar de Sub, y se le agrega al final el tipo de dato de salida. Además, como con los
procedimientos, en VB.Net hay que marcar el fin de la función con End Function. Como requisito extra, en ambos lenguajes es obligatorio colocar la palabra
return para devolver el resultado de la función.
// C#
int Sumar(int a, int b)
{
return a + b;
}
‘ VB.Net
Function Sumar(a, b as Integer) As Integer
Return a + b
End Function
› LAS DLLS EN WINDOWS
El sistema operativo Windows está
formado por unas cuantas decenas de
DLLs. Un detalle interesante sobre esto es que esas DLLs poseen las funciones y procedimientos que Windows
utiliza para hacer todo lo que hace, y
están lo suficientemente documenta-
das como para que podamos utilizarlas en nuestros programas cuando
necesitemos hacer algo que el lenguaje no tiene previsto. Este conjunto
de DLLs se denomina API (Application
Programmer Interface o Interfaz para
el programador de aplicaciones).
43
03_IntrodProg.qxd
23/7/07
20:41
Page 44
ELEMENTOS DE PROGRAMACIÓN
LIBRERÍAS
> Una de las grandes ventajas de la modularización reside en que los módulos
pueden utilizarse en distintos lugares. Para ello, resulta muy común organizarlos
en librerías. Una librería o biblioteca es un conjunto de subprogramas compilados en un único archivo.
En algunos lenguajes (sobre todo los más viejos), al compilar un programa se genera un archivo denominado programa objeto, que es el código máquina generado a partir del código fuente del programa, pero sin incluir el código de las librerías utilizadas. Luego, en un segundo paso denominado enlace (linking) se combina el programa objeto con las librerías para producir un único archivo ejecutable.
En los lenguajes de programación que permiten compilar código para ejecutar
en el sistema operativo Windows, es muy común el uso de librerías de enlace dinámico (los conocidos archivos DLL, acrónimo de dynamic link library). El
enlace dinámico consiste en saltear el paso de enlace durante la compilación para hacerlo durante la ejecución de un programa. Así, mientras un programa se
ejecuta, si necesita código que se encuentra en una librería, se coloca ésta en memoria, y se transfiere el control al subprograma necesario dentro de la librería.
Figura 3. En .Net podemos crear librerías de clases,
que se comportarán como librerías de enlace dinámico.
44
03_IntrodProg.qxd
23/7/07
20:41
Page 45
› ARREGLOS Y MATRICES
ARREGLOS Y MATRICES
> Las estructuras de datos son conjuntos de datos relacionados y organizados de
alguna manera (la forma de organizarlos depende justamente de la estructura usada). Desde un punto de vista más formal, las estructuras de datos son tipos de datos (complejos), ya que determinan los valores que pueden contener y las operaciones que se les pueden aplicar. De acuerdo a cómo se administra la memoria para
almacenar la información, las estructuras de datos se pueden clasificar en estáticas
y dinámicas. Las estructuras estáticas ocupan una cantidad fija de memoria durante toda su vida. El tamaño queda establecido al momento de definir la estructura,
es decir, cuando definimos una variable del tipo de la estructura de datos. Las estructuras dinámicas tienen un tamaño inicial, pero pueden crecer durante la ejecución, a medida que se incrementa la cantidad de datos que almacenan.
ARREGLOS
Los arreglos o vectores (Arrays) son estructuras de datos que permiten almacenar
una colección de datos del mismo tipo, de manera que pueden ser accedidos en forma directa. Dicho de otra manera, los arreglos son una sucesión contigua de n datos (donde n es el tamaño del arreglo), cada uno con un índice determinado por la
posición. Así, es posible acceder a un elemento del arreglo mediante su índice. Un
arreglo tiene un único nombre, y éste abarca a todos los elementos que contiene.
50
0
A[0] A[1]
80
30
55
27
10
Pérez
tue
A[6]
El arreglo es una
secuencia de datos contiguos
en memoria.
Luego del arreglo
pueden seguir datos
de cualquier tipo.
Figura 4. Los elementos de un arreglo ocupan posiciones contiguas de memoria.
45
03_IntrodProg.qxd
23/7/07
20:41
Page 46
ELEMENTOS DE PROGRAMACIÓN
Normalmente, los arreglos son estructuras estáticas de datos, pero algunos lenguajes de programación permiten cambiarles el tamaño mientras se ejecutan.
Para entender la utilidad de los arreglos, pensemos que necesitamos almacenar el
registro de lluvias de cada mes para una ciudad. Sin usar arreglos, deberíamos tener
doce variables para contener el registro pluvial de los doce meses del año. Esta alternativa presenta algunas desventajas: es tediosa para programar, ya que hay que declarar cada una de las variables con un nombre diferente. Además, se hace imposible recorrerlas mediante una estructura de repetición. Por último, si queremos hacer
que el usuario ingrese el número de mes y mostrar las lluvias de ese mes, deberíamos
hacer un select case para mostrar la variable correspondiente al mes ingresado.
Si en cambio utilizamos un arreglo, tendremos los doce valores bajo un mismo nombre de variable y podremos acceder al registro pluvial del n-ésimo mes
directamente leyendo el elemento del arreglo ubicado en la posición n. Supongamos que la variable se denomina lluvias; entonces, podemos escribir un algoritmo como el que sigue para mostrar el registro de lluvias de un mes seleccionado por el usuario:
// Pseudocódigo:
Procedimiento MostrarRegistroPluvial
Leer un entero y guardarlo en la variable mes
Mostrar el valor de lluvias[mes]
Aquí vemos una notación sintáctica que hasta ahora no habíamos visto: lluvias[mes]. Esta expresión significa: el dato que se encuentra en la posición mes
› EL PRIMER ELEMENTO
Cada lenguaje de programación tiene diferentes posturas a la hora de
definir las dimensiones de un arreglo, como así también de definir
cuál es el índice del primer elemento. Lenguajes como Delphi permiten
definir los índices de un arreglo comenzando con cualquier número
46
(por ejemplo, podemos indicar que
los índices van del 50 al 100). Otros
lenguajes permiten definir el tamaño del arreglo, y el primer elemento
queda fijado por una convención de
diseño. En .Net, todos los índices de
arreglos y colecciones comienzan
siempre en cero.
03_IntrodProg.qxd
23/7/07
20:41
Page 47
› ARREGLOS Y MATRICES
dentro del arreglo (el valor que está dentro de los corchetes representa el índice
al que se quiere acceder). En el ejemplo, hemos usado una variable como índice del arreglo, pero se puede usar también un valor constante, por ejemplo lluvias[2] para acceder a las lluvias de febrero.
Arreglos en .Net
Obviamente, los lenguajes de .Net proveen soporte para la creación y uso de
arreglos. En C#, los arreglos son estáticos, mientras que, en Visual Basic .Net,
pueden ser dinámicos. En ambos lenguajes, los arreglos se definen declarando su
tamaño y el tipo de los elementos. El primer índice es siempre el cero.
// C# - Defino un arreglo de 2 enteros
int[] arreglo = new int[2];
arreglo[0] = 1;
arreglo[1] = 20;
‘ VB.Net – Defino un arreglo de 2 enteros
Dim arreglo(2) as Integer
Arreglo(0) = 1
Arreglo(1) = 2
MATRICES
Si bien las matrices tienen un origen en la matemática, en el contexto de la programación son estructuras de datos que permiten organizar la información en filas y columnas. Cada elemento de una matriz puede ser accedido por un par de
índices (la fila y la columna). Al igual que en los arreglos, los elementos de una
matriz deben ser todos del mismo tipo de datos.
› ESTRUCTURAS DE DATOS MÁS COMPLEJAS
Los arreglos y matrices son estructuras de datos básicas. En muchas ocasiones, necesitamos trabajar con estructuras más complejas. En .Net, disponemos de tipos de datos para manejar estructuras como listas, pilas o co-
las, entre otras. Una pila es similar a
un arreglo, en donde sólo podemos colocar elementos al principio y sacar del
mismo lugar. En una cola, sólo podemos ubicar elementos al final y quitar
elementos del comienzo.
47
03_IntrodProg.qxd
23/7/07
20:41
Page 48
ELEMENTOS DE PROGRAMACIÓN
7
0
49
18
3
4
99
2
14
1
0
17
Fila
Elemento (3,4)
Columna
Figura 5. Una matriz está organizada en filas y columnas. Cada elemento se accede
mediante un índice compuesto por el número de fila y por el número de columna.
Desde el punto de vista de su almacenamiento en memoria, la matriz puede ser
vista como un arreglo de filas, donde cada fila es un arreglo de elementos. Visto
de esta manera, una matriz es, en definitiva, un arreglo de elementos de tipo de
dato arreglo. Es decir, un arreglo bidimensional.
A la hora de recorrer una matriz, se debe prestar atención a no confundir los índices de los elementos, teniendo bien presente en qué columna y en qué fila está cada
uno de los que necesitamos acceder. Dada su estructura de dos dimensiones, la forma de recorrer una matriz es mediante dos estructuras repetitivas anidadas, una para recorrer las filas y la otra para recorrer las columnas. Dependiendo del orden en
que se quiera hacer el recorrido, se iterará primero sobre las filas y luego sobre las columnas, o primero sobre las columnas y luego sobre las filas. Para saber si hemos implementado correctamente el recorrido, podemos tomar una matriz de ejemplo y se-
› FXCOP
Luego del surgimiento de .Net, Microsoft publicó documentos con estándares de codificación y buenas prácticas
de programación, con el objetivo de
que todos los programadores escribieran código de la misma manera. Existe un programa llamado FxCop, dispo-
48
nible en www.gotdotnet.com/Team/
FxCop/. Este programa analiza ensamblados ya compilados de .Net y genera una lista con las violaciones a los
estándares definidos por Microsoft.
Actualmente, FxCop fue incorporado
en algunas ediciones de Visual Studio.
03_IntrodProg.qxd
23/7/07
20:41
Page 49
› ARREGLOS Y MATRICES
guir con lápiz y papel cada uno de los pasos del algoritmo, verificando el resultado
obtenido (esto se conoce como Prueba de Escritorio).
Matrices en .Net
La definición de matrices en los lenguajes de .Net que estamos estudiando resulta muy similar a la definición de arreglos, con el agregado de la segunda dimensión.
// C# - Defino una matriz de 2 filas y 3 columnas
int[,] matriz = new int[2,3];
matriz [0,1] = 1;
matriz [0,2] = 20;
‘ VB.Net – Defino una matriz de 2 filas y 3 columnas
Dim matriz (2,3) as Integer
matriz (0,1) = 1
matriz (0,2) = 2
En C#, existe una forma alternativa de definir las matrices, basada en la idea de
una matriz como un arreglo de arreglos:
// C# - Defino una matriz de 2 filas y 3 columnas
int[][] mat = new int[2][]{new int[3],new int[3]};
mat [0][1] = 1;
mat [0][2] = 20;
› MÁS ALLÁ DE LAS DOS DIMENSIONES
Como vimos, una matriz es un arreglo de arreglos, lo cual puede verse
como un arreglo de dos dimensiones. Los lenguajes de programación
permiten definir arreglos con la cantidad de dimensiones que necesitemos. Por ejemplo, si necesitamos
almacenar los registros de lluvia de
cada mes, de los últimos 20 años para 10 ciudades distintas, podemos
definir un arreglo de 10x20x12 (10
ciudades por 20 años por 12 meses
al año). En este caso, la estructura
se conoce como Cubo.
49
03_IntrodProg.qxd
23/7/07
20:41
Page 50
ELEMENTOS DE PROGRAMACIÓN
EL ESTILO DE PROGRAMACIÓN
> Una de las premisas fundamentales de la programación consiste en saber que
los programas no se escriben una vez y quedan así para siempre. Cuando se escribe un programa, es normal tener que volver sobre sus instrucciones para hacer una
modificación o corregir un error. Y es común que la persona que deba modificar
el programa no sea la misma que lo escribió. Sin embargo, dada la complejidad
inherente a todo programa, leer el código fuente ya escrito y tratar de entenderlo
para hacer la modificación que necesitamos, puede ser toda una odisea.
Para que la tarea de mantenimiento del software sea un poco más sencilla, es
necesario que, como programadores, tengamos el hábito de escribir el código de
la manera más prolija posible y que logremos un estilo estándar. Para lograr un
buen estilo de programación, debemos tener en cuenta algunos consejos.
TABULACIONES
Las sentencias compuestas pueden estar formadas a su vez por otras sentencias compuestas, así se obtienen anidamientos en el código del programa. Para
poder identificar dónde comienza y dónde termina cada nivel de anidamiento,
es muy útil tabular hacia la derecha cada sentencia que está dentro de otra (es
decir, cada nuevo nivel de anidamiento). Veamos la diferencia entre tabular y
no tabular con un ejemplo:
› ARREGLOS
› MODULARIZACIÓN Y OVERLAYS
Visual Basic .Net permite cambiar el
tamaño de los arreglos. Para ello, provee la palabra clave Redim. Hay que
tener en cuenta, sin embargo, que el
Redim destruye el arreglo original y
crea uno nuevo, con la consiguiente
pérdida de los valores que contenía.
Para evitarlo, se puede utilizar Redim
Preserve, que luego de crear el arreglo, copia los elementos del viejo.
En épocas en que la memoria de las
computadoras era escasa, muchas
veces cargar todo el programa era
imposible. La solución consistía en
colocar los módulos en archivos separados llamados overlays. Cuando
se necesitaba ejecutar un módulo
que estaba en un overlay, se descargaba otro de memoria y se cargaba el
que tenía el módulo necesario.
50
03_IntrodProg.qxd
23/7/07
20:41
Page 51
› EL ESTILO DE PROGRAMACIÓN
// C# - Forma incorrecta de anidar sentencias
if(a>0) {
for(int i=1;i<a;i++) {
if(i<3)
b=a*i;
else
b=a*(i+3);
}}
// C# - Forma correcta de anidar sentencias
if(a>0) {
for(int i=1;i<a;i++)
{
if(i<3)
b=a*i;
else
b=a*(i+3);
}
}
En el primer ejemplo, resulta difícil determinar dónde comienza cada nivel de anidamiento, mientras que, en el segundo, queda perfectamente claro a primera vista.
› THE CODE PROJECT
Aquí podremos encontrar cientos
y cientos de ejemplos de código
fuente en .Net, tanto en VB.Net
como en C#. Los ejemplos están
organizados por categoría y por
nivel (desde Principiante hasta
Avanzado). Además, la mayoría
de los ejemplos están acompañados de un artículo donde se explican sus fundamentos.
51
03_IntrodProg.qxd
23/7/07
20:41
Page 52
ELEMENTOS DE PROGRAMACIÓN
Figura 6. El uso correcto de comentarios, nombres significativos
e indentación favorecen la claridad del código.
COMENTARIOS
Muchas veces, nos encontramos con que hemos escrito una expresión muy
compleja. Al momento de escribirla, tenemos bien claro qué es lo que hace, porque acabamos de idearla, pero luego de un tiempo, cuando volvemos a leerla
puede que no entendamos muy bien qué es lo que quisimos hacer. Para solucionar casos de este tipo, es aconsejable el uso de comentarios y dejar una explicación del razonamiento que hicimos para obtener ese código, o cualquier otro
mensaje que resulte útil para quien lo lea.
Nombres significativos
Cuando creamos un procedimiento o una función, el objetivo es realizar un
cálculo o tarea específica, por lo que el nombre que le demos al subprograma
52
03_IntrodProg.qxd
23/7/07
20:41
Page 53
› EL ESTILO DE PROGRAMACIÓN
creado debe revelar de la mejor manera posible el objeto de su existencia. A la
hora de darles nombres a los procedimientos y funciones, se aconseja usar palabras completas y frases que indiquen cuál es la tarea que el subprograma realiza, evitando las abreviaturas. También es recomendable usar verbos que representen la acción por realizar.
// C# - Forma incorrecta de nombrar subprogramas
void calcRet(string fac)
int MCM(int a, int b)
// C# - Forma correcta de nombrar subprogramas
void CalcularRetenciones(string numeroFactura)
int MaximoComunDivisor(int numeroA, int numeroB)
Consistencia de formato
Quizá lo más importante a la hora de escribir código fácil de mantener sea lograr un estilo consistente y estandarizado, y mantener ese estilo en todo el programa. Esto significa que, si escribimos los nombres de variables locales con minúsculas, las constantes con mayúscula y los nombres de los procedimientos
usando mayúscula para la primera letra de cada palabra, debemos repetir esa convención siempre. Así, si somos consistentes, alguien que esté familiarizado con la
nomenclatura, al leer un identificador, podrá saber fácilmente si se trata de una
variable, una constante, o un procedimiento.
Del mismo modo que es bueno adoptar convenciones de nomenclatura, resulta importante la consistencia en el uso de líneas en blanco, de espacios en-
› COMENTARIOS VS NOMBRES CLAROS
En la actualidad, existe una discusión
sobre si se deben usar o no comentarios para explicar qué es lo que hace
un procedimiento o función. Algunos
sostienen que resulta necesario colocar un comentario, mientras que otros
insisten en que, si se necesita explicar
una porción de código, es porque resulta muy compleja, y aconsejan reescribirla para que sea más sencilla, separándola en otros procedimientos o
funciones si fuera necesario. La segunda postura lleva a un código mucho
más claro y mantenible.
53
03_IntrodProg.qxd
23/7/07
20:41
Page 54
ELEMENTOS DE PROGRAMACIÓN
tre los operandos y los operadores en las expresiones, y en la cantidad de espacios usados para las tabulaciones.
Figura 7. Los entornos de programación modernos permiten definir reglas de codificación y luego
modifican el código automáticamente a medida que escribimos, de manera que se cumplan las reglas.
■ CONCLUSIONES
En este capítulo, hemos aprendido uno de los conceptos fundamentales de la
programación: la modularización. Como programadores, debemos tener
siempre presentes estos conceptos y usarlos al máximo posible. Es preferible
tener muchas funciones y procedimientos a tener grandes bloques de código
de cientos de líneas. Muchos programadores (entre los que me incluyo) tienen
esta regla de oro: si un procedimiento o función no cabe en una página del editor que estamos usando, entonces será necesario dividirlo en procedimientos
y funciones más pequeños, porque se está volviendo demasiado largo y, por lo
tanto, demasiado complejo.
54
04_IntrodProg.qxd
23/7/07
20:47
Page 55
CAPÍTULO 4
Programación
orientada a objetos
La orientación a objetos es una de las técnicas
más utilizadas en la actualidad en el mundo
del desarrollo: en el ámbito académico, como
herramienta de aprendizaje y, en lo profesional,
para solucionar toda clase de problemas.
ATENCIÓN AL LECTOR > [email protected]
04_IntrodProg.qxd
23/7/07
20:47
Page 56
PROGRAMACIÓN ORIENTADA A OBJETOS
LOS OBJETOS
> Si miramos a nuestro alrededor, vemos que estamos rodeados de objetos. Y
cuando debemos desarrollar un nuevo software para resolver un problema, verificamos que los elementos que componen el problema son objetos. Pensemos en
un sistema de gestión de negocios. Al analizar las características del problema,
nos encontramos con que tenemos objetos como productos, facturas, remitos,
depósitos, clientes, etcétera. Y no sólo objetos concretos, sino también abstractos, como ser vencimientos o impuestos.
La programación orientada a objetos es un paradigma basado en la identificación de los objetos inherentes al problema y en la escritura de código que modele esos objetos, sus propiedades, sus comportamientos y la forma en la que se
relacionan para resolver el problema. Visto de esta manera, la programación
orientada a objetos resulta opuesta a la programación imperativa clásica, donde
los programas son secuencias de instrucciones.
ORIGEN
Según algunos autores, el primer lenguaje en usar objetos fue Simula 67, creado en la década del 60 para hacer simulaciones. Cuenta la historia que en el Centro de Cómputo Noruego, en Oslo, trabajaban en simulaciones de barcos, y la
cantidad de combinaciones de cualidades entre los diferentes barcos, y cómo
unas afectaban a otras, produjo gran confusión en los científicos que estaban escribiendo los programas. Entonces, se les ocurrió organizar los diversos tipos de
barcos en clases de objetos; cada clase sería responsable de definir y de administrar sus propios datos y su comportamiento.
Más tarde, el lenguaje Smalltalk fue el primero en introducir el término orientación a objetos para referirse al uso de objetos y de mensajes entre ellos como base
del diseño y la programación de aplicaciones. Sin embargo, el auge de este paradigma comenzó en la década del 80, con el lenguaje C++. La tecnología de objetos fue
agregada a muchos lenguajes ya existentes, pero este agregado artificial condujo a
problemas de diseño y de compatibilidad que no hicieron más que ensuciar los lenguajes existentes. Por otro lado, los lenguajes orientados a objetos puros (como
Smalltalk) no eran muy aceptados, porque carecían de características a las que esta56
04_IntrodProg.qxd
23/7/07
20:47
Page 57
› LOS OBJETOS
ban acostumbrados los programadores del momento. Se hicieron muchos intentos
por crear lenguajes orientados a objetos, pero con algunas características imperativas
y estructuradas; el único que sobrevivió fue el lenguaje Java. En la actualidad, los
lenguajes de la plataforma .Net están diseñados de esta manera, es decir, se basan en
la definición de clases y de objetos, pero conservan características de la programación imperativa.
Figura 1. Smalltalk fue uno de los primeros lenguajes completamente orientados a objetos.
VENTAJAS DE LA POO
La programación orientada a objetos (POO) posee algunas ventajas frente a la programación estructurada clásica. La POO consiste en una evolución de la programación estructurada y cuenta con la experiencia adquirida a lo largo de años de desarrollo de software, lo que constituye su principal ventaja. Además, conduce a un modelo mental del problema mucho más cercano a la realidad, lo que favorece el análisis, la implementación y el posterior mantenimiento del software. Otra ventaja es
que la POO permite lograr un mayor grado de encapsulado, ya que el comportamiento y los datos propios de cada clase quedan contenidos en la definición de ella.
Por último, mediante técnicas que estudiaremos luego, la POO permite reutilizar
mucho código, favoreciendo la escritura de nuevos programas.
57
04_IntrodProg.qxd
23/7/07
20:47
Page 58
PROGRAMACIÓN ORIENTADA A OBJETOS
CLASES Y OBJETOS
> Cuando se analiza un problema para resolverlo mediante las técnicas de programación estructurada, nos enfocamos en identificar los datos que debemos manipular y las transformaciones que sufren como parte de la solución. Con el paradigma
de orientación a objetos, la tarea de análisis se centrará en la identificación de objetos, de sus características y de cómo se relacionan entre sí para resolver el problema.
Ya dentro del contexto formal de la teoría de Orientación a Objetos, podemos
definir un objeto (parafraseando la definición de James Rumbaugh) como un
concepto, abstracción o elemento con significado claro dentro del problema en
cuestión (por ejemplo, un empleado). Como tal, un objeto se caracteriza por tener un estado, que es el conjunto de valores de sus propiedades en un momento
del tiempo (por ejemplo, el nombre de un empleado, o el monto del sueldo que
percibe). Además, todo objeto tiene un comportamiento, es decir, las acciones
que puede realizar y que modificarían su estado (por ejemplo, un empleado puede ascender o completar tareas). Por último, un objeto se caracteriza por tener
identidad propia, esto es, por más que dos objetos tengan el mismo comportamiento y el mismo estado, resultan totalmente diferentes e identificables.
Según el diccionario, una clase es un grupo de elementos de un conjunto que tienen características comunes. Las técnicas de orientación a objetos se centran en la
identificación de esos elementos comunes entre los objetos para poder agruparlos en
clases y, así, manipularlos fácilmente. Podemos decir que una clase es una abstracción de un grupo de objetos, porque no existe por sí sola.
Por ejemplo, pensemos en una mesa. Hay mesas rectangulares, mesas redondas, de
madera, de metal, incluso de diferentes colores. Todas son mesas distintas e identificables, es decir: objetos. Sin embargo, podemos verificar que todas tienen los mis-
› CLASES E INSTANCIAS
En el mundo de la orientación a objetos, hay dos términos muy usados:
clases e instancias. Como vimos, una
clase es una abstracción de un objeto
de la realidad. La clase es un modelo
58
estático, por lo tanto, para poder interactuar con otros elementos, debemos crear instancias que representen
a cada uno de los objetos particulares
que, agrupados, forman la clase.
04_IntrodProg.qxd
23/7/07
20:47
Page 59
› CLASES Y OBJETOS
mos atributos, aunque con distintos valores. Todas tienen un color, un material, una
forma; por lo tanto podemos agruparlas bajo un mismo concepto: la mesa. Desde
el punto de vista de la orientación a objetos, podemos decir que mesa es una clase.
Clase
Mesa
Color
Material
Cantidad de patas
Objeto 1
Objeto 2
Figura 2. Una clase es una abstracción de las principales propiedades de los objetos reales.
PROPIEDADES Y MÉTODOS
Como decíamos, los objetos poseen atributos y comportamiento. Ya desde el
punto de vista de la programación, los atributos de los objetos se traducen en
propiedades de las clases que los modelan. El comportamiento en cambio, está
representado por procedimientos y funciones dentro de la clase. En la jerga de la
programación orientada a objetos, los procedimientos y funciones que creamos
dentro de una clase se denominan métodos, y representan el conjunto de actividades que un objeto puede realizar, es decir, su comportamiento. Los libros más
puristas dicen que los objetos se relacionan intercambiando mensajes, por lo que
el comportamiento de un objeto está determinado por los mensajes que puede
enviar y por los que puede aceptar desde otros objetos. Volviendo a la definición
de método, justamente, podemos decir que los métodos son los encargados de
interceptar y de enviar mensajes desde y hacia otros objetos y, al mismo tiempo,
alterar el estado actual del objeto.
59
04_IntrodProg.qxd
23/7/07
20:47
Page 60
PROGRAMACIÓN ORIENTADA A OBJETOS
PENSAR EN OBJETOS
> La forma de solucionar problemas utilizando técnicas de Orientación a Objetos es muy distinta de la forma tradicional o estructurada.
Cuando nos presentan un requerimiento de una nueva aplicación y queremos
emplear los conceptos de la Programación Orientada a Objetos, como primer
paso nos concentraremos en identificar los objetos que intervienen en el problema. Deberemos, entonces, analizar cuidadosamente el requerimiento, hablar con los usuarios y prestar especial atención a los elementos que mencionan. Una técnica muy usada consiste en leer el requerimiento escrito (que deberá ser lo más claro y completo posible) y subrayar todos los sustantivos, tanto concretos como abstractos. Luego, de entre todos los sustantivos subrayados, descartamos aquellos que no son propios del problema por resolver. Una
vez realizada esta primera actividad, habremos identificado una buena parte de
los objetos del dominio de la aplicación.
Por ejemplo, imaginemos que necesitamos implementar una aplicación de alquiler de autos, y nos presentan el siguiente requerimiento (resumido): La empresa dispone de autos para alquilar a sus clientes. Cuando se alquila un auto, el cliente firma
una póliza de seguro. En un formulario de alquiler, se registra la fecha, el nombre del
cliente, el número de su registro de conductor y el número de tarjeta de crédito.
Si aplicamos la técnica de los sustantivos, podremos identificar rápidamente
tres objetos fundamentales para la aplicación: auto, cliente y formulario de alquiler. Dependiendo del tipo de análisis y diseño que queramos hacer, podríamos
considerar como objetos la tarjeta de crédito e, incluso, el alquiler.
Como decíamos al principio, los objetos poseen propiedades y comportamiento.
Para identificar las propiedades aplicamos una técnica similar a la de los sustanti-
› TOMARLO CON CALMA
Muchas veces, no lograremos identificar con claridad todos los objetos
en una primera lectura. Será necesario hacer una segunda pasada (incluso más) sobre el texto del reque60
rimiento para encontrar objetos
ocultos, despejar ambigüedades o
eliminar falsos objetos que no son
esenciales para la solución concreta
del problema.
04_IntrodProg.qxd
23/7/07
20:47
Page 61
› PENSAR EN OBJETOS
vos, pero buscando cualidades o adjetivos de los objetos que ya hemos identificado. Siguiendo con el ejemplo del alquiler de autos, podemos ver que el formulario
de alquiler tiene propiedades como la fecha, el nombre del cliente, etcétera.
Una vez que hemos identificado los objetos y sus propiedades, estamos en condiciones de definir clases para agruparlos y abstraer todas las posibles instancias
de ellos. Hemos alcanzado así el fin de la primera etapa en el proceso de solución
de problemas mediante técnicas de orientación a objetos.
Autos
Auto
Color
Marca
Año
Clientes
Cliente
Nombre
Número de
Registro
Formularios
Formulario
Fecha
Cliente
Nº de póliza
Figura 3. Con las técnicas de análisis, identificamos los objetos
del mundo real; luego abstraemos sus propiedades para agruparlos en clases.
PATRONES DE DISEÑO
Si bien el proceso de análisis y diseño orientado a objetos se centra en la identificación de los objetos propios del dominio del problema, a menudo es necesario crear objetos ficticios. Éstos colaboran con los objetos reales en la solución del
problema o en proveer mecanismos de flexibilidad, extensibilidad o claridad del
61
04_IntrodProg.qxd
23/7/07
20:47
Page 62
PROGRAMACIÓN ORIENTADA A OBJETOS
código, y ayudan a separar los intereses y responsabilidades de cada objeto. Un
ejemplo muy común de estos escenarios son los requisitos no funcionales.
Para entender un poco mejor este aspecto, retomemos el ejemplo del sistema de
alquiler de autos e imaginemos que se nos exige que el sistema valide la tarjeta de
crédito, pero que además el código esté preparado para adaptar el sistema a nuevos mecanismos de validación. Una posible solución es definir una clase llamada
ValidadorTarjetaCredito, con un método que reciba un número de tarjeta y un
importe, y valide que se puede cobrar ese importe a esa tarjeta. Luego, utilizando la herencia y el polimorfismo (que veremos luego), se pueden crear nuevas clases que empleen diversos mecanismos de validación.
A lo largo de los años, los desarrolladores reconocieron que muchas de las soluciones que encontraban a los problemas no funcionales con orientación a objetos comenzaban a repetirse o tenían elementos en común y que, además, funcionaban (es decir, cumplían su propósito). Actualmente, a estas soluciones probadas a problemas comunes, se las llama patrones. Hay distintas clasificaciones de
patrones según la familia de problemas que atacan, siendo los más comunes los
denominados Patrones de diseño.
El principal objetivo de los Patrones de diseño es el de proveer un catálogo
de elementos de diseño que ayuden a los programadores en su tarea diaria,
evitando que tengan que buscar nuevas soluciones cada vez que se enfrentan
con un problema común. Además, los Patrones de diseño facilitan la comunicación entre los programadores, como así también la comprensión del código
escrito por otro. Obviamente, los patrones no pretenden eliminar el trabajo
del desarrollador, ya que éste debe ser capaz de identificar las características de
cada problema y tener un dominio de los principales patrones para asociarlos
con el problema y aplicarlos.
› LA BANDA DE LOS CUATRO
El tema de los patrones ha generado
gran cantidad de bibliografía. Quizá el
libro más popular a la hora de aprender sobre patrones es Design Patterns. Elements of Reusable ObjectOriented Software. Los patrones que
62
en él se presentan, se conocen como
patrones GoF, sigla de Gang of Four
(la Banda de los Cuatro), en alusión a
sus cuatro autores, autoridades absolutas en este ámbito (Gamma, Helm,
Jonson y Vlissides).
04_IntrodProg.qxd
23/7/07
20:47
Page 63
› RELACIONES ENTRE CLASES
RELACIONES ENTRE CLASES
> En el mundo real, y dentro del contexto de una aplicación que debemos implementar, los objetos no están solos. Todo objeto se relaciona en cierta medida
con alguno de los otros objetos (sea de la misma clase o no). En el ejemplo de alquiler de autos que estudiamos anteriormente, los clientes (que son objetos) se
relacionan con los autos (que también son objetos) mediante la operación de alquiler. Del mismo modo, los formularios de alquiler se relacionan con los autos
y con los clientes. Una buena regla adicional para la identificación de objetos a
partir de un requerimiento es que, si un objeto no se relaciona con ningún otro,
es probable que no sea necesario tenerlo en el sistema o deba ser revisado para ver
si no nos faltó considerar algún aspecto del requerimiento.
Un objeto puede relacionarse con otro de distintas maneras, como por ejemplo,
enviándose mensajes entre sí. Durante el análisis, una vez que determinamos los objetos y definimos las clases, debemos prestar atención a las relaciones entre los objetos, ya que deberán ser modeladas para que luego se encuentren disponibles en cada instancia. Entre las relaciones más importantes, podemos mencionar: uso, agregación y herencia. Veamos las dos primeras ahora y dejemos la herencia para después, ya que es muy importante y merece un tratamiento aparte.
RELACIÓN DE USO
En una relación de uso, un objeto A usa un objeto B, en el sentido de utilizar
algún servicio provisto por B. En cualquier aplicación, es muy común encontrar
que un objeto necesita algún servicio de otro, como pedirle que haga una determinada tarea. Si retomamos el ejemplo de la validación de tarjetas de crédito que
› POO PURA
Muchos puristas de la orientación a
objetos sostienen que Smalltalk y
Eiffel son de los pocos lenguajes
realmente orientados a objetos. El
argumento se basa en que otros lenguajes (como los presentes en la
plataforma .Net) poseen aún muchos conceptos de la programación
estructurada (como por ejemplo las
estructuras de control) y eso atenta
contra la definición formal de orientación a objetos.
63
04_IntrodProg.qxd
23/7/07
20:47
Page 64
PROGRAMACIÓN ORIENTADA A OBJETOS
vimos cuando tratamos el tema de los patrones, podemos observar que el objeto
ValidadorTarjetaCredito brinda un servicio que alguien más, por ejemplo un objeto RegistradorDeAlquiler puede usar para completar su función. En la bibliografía de POO, se suele llamar relación Usa-A a la relación de uso
Hacer Tarea
Objeto B
Objeto A
Tarea lista
Figura 4. Cuando un objeto A solicita una tarea a un objeto B, se dice que el objeto A usa el objeto B.
RELACIÓN DE AGREGACIÓN
Muchas veces, un objeto se compone de otros. Este tipo de relación se denomina agregación. En una relación de agregación entonces, un grupo de objetos (de
la misma clase o de clases distintas) se agrupan para formar un objeto más complejo. Por ejemplo, un carrito de compras se compone de uno o más productos
que el cliente ha comprado. Si bien los objetos producto son independientes, el
objeto carrito necesita de ellos para existir. La relación de agregación puede ser
› RELACIONES
Desde el punto de vista de la implementación, tanto las relaciones de
agregación como las de composición suelen representarse como
propiedades o atributos de las cla64
ses agregadas o compuestas. Por
ejemplo, la clase Carrito puede tener una propiedad que sea la lista
de objetos de la clase Producto que
ella contiene.
04_IntrodProg.qxd
23/7/07
20:47
Page 65
› RELACIONES ENTRE CLASES
recursiva, es decir, un objeto que está compuesto por otros, a su vez, puede ser
parte de un objeto más grande, definiendo así una estructura jerárquica. Por
ejemplo, una universidad puede estar compuesta de facultades, cada facultad se
compone de departamentos, y cada departamento, de profesores y de alumnos.
Composición
La composición es un caso especial de agregación en el que los objetos agregados a otro, sólo pertenecen a él. Por ejemplo, un automóvil está compuesto de un
motor, un chasis, una carrocería y otros elementos, pero cada uno de ellos sólo
puede pertenecer a ese auto. Un motor que está en un auto no puede estar en otro.
La distinción entre agregación y composición depende directamente del problema
por resolver y, en un buen modelo de objetos, debería reflejar exactamente la realidad.
Objeto a
Objeto b1
Objeto c1
Objeto c2
Objeto b2
Objeto c3
Figura 5. En una relación de agregación, los objetos se agrupan para formar un objeto más complejo.
› MÉTODOS Y PROPIEDADES DE CLASE
Normalmente, los métodos y propiedades corresponden a cada instancia de una clase (a cada objeto).
Sin embargo, muchas veces hay características que son comunes a to-
das las instancias. En estos casos,
podemos definir métodos o propiedades compartidos, que pertenecen
a la clase y se pueden invocar directamente sobre ella.
65
04_IntrodProg.qxd
23/7/07
20:47
Page 66
PROGRAMACIÓN ORIENTADA A OBJETOS
HERENCIA
> La herencia es un tipo muy importante de relación entre objetos. En una re-
lación de herencia, una clase recibe las propiedades y comportamiento de otra
como si fuesen suyas y, al mismo tiempo, puede agregar las propias.
Veamos esto con un ejemplo. Supongamos que estamos desarrollando una aplicación que necesita hacer cálculos de superficie sobre distintas figuras geométricas, es decir, vamos a tener objetos como Círculo, Cuadrado, Trapecio, etcétera.
Dado el requerimiento que tenemos, todos los objetos presentarán un comportamiento en común: calcular su superficie. Lo que podemos hacer entonces es
definir una clase llamada Figura y establecer una relación de herencia entre esta
clase y cada una de las figuras particulares. Le asignamos el método de cálculo de
superficie a la clase Figura y, mediante la relación de herencia, las demás clases
tendrán este comportamiento como heredado. Decimos entonces que las clases
Cuadrado, Trapecio, etcétera, heredan de la clase Figura.
Así como el uso define una relación usa-a, y la composición define una relación
se-compone-de, la herencia se conoce como una relación es-un. Siguiendo con el
ejemplo de las figuras, decimos que un círculo es una figura, un cuadrado es una
figura, y así con todos. Cuando una clase B hereda de una clase A, se dice que la
clase A es la clase padre o clase base, y la clase B es la clase hija, heredera o derivada. En el ejemplo anterior, entonces, la clase Figura es la clase base, y las demás, sus clases derivadas.
La relación de herencia no se limita a un solo nivel, es decir, que una clase derivada de otra puede a su vez ser la base de una tercera clase, formando así una estructura jerárquica o de árbol. Además, en escenarios de múltiples niveles, la relación de
› DISEÑAR PENSANDO EN OBJETOS
La herencia tiene una gran potencia
para escribir código reutilizable, flexible y mantenible. Por eso, durante
el análisis del problema y durante el
posterior diseño de las clases, se
debe prestar especial atención a
66
identificar relaciones de herencia
para aprovechar sus virtudes. Conocer algunos patrones de diseño puede ayudar también a identificar relaciones de herencia, aun en clases
no concretas.
04_IntrodProg.qxd
23/7/07
20:47
Page 67
› HERENCIA
herencia es transitiva, esto es, si una clase C hereda de B, que a su vez hereda de A,
todo el comportamiento y las propiedades de A pertenecen también a C.
A partir del ejemplo anterior, podemos apreciar que la relación de herencia provee un mecanismo para lograr uno de los objetivos más importantes de la programación orientada a objetos, la reutilización de código. ¿Cómo se entiende esto? Supongamos que de la clase Figura hereda una clase Cuadrilátero, que tiene
propiedades para representar la longitud de cada uno de los lados. Si de la clase
Cuadrilátero derivan Rombo, Trapecio y Rectángulo, en estas nuevas clases ya no
tendremos que escribir el código de la propiedad, es decir, hemos reutilizado código que ya estaba escrito.
Clase Figura
CalcularSuperficie()
Clase Cuadrilatero
Clase Circulo
LongitudLadoA
LongitudLadoB
LongitudLadoC
LongitudLadoD
Radio
Clase Cuadrado
Clase Rombo
Figura 6. Mediante la herencia, podemos definir una jerarquía de clases, donde cada
clase derivada hereda el comportamiento y las propiedades de su clase base.
Además de la reutilización de código, la herencia provee otras aplicaciones útiles, como son la extensión y la redefinición de clases. Veamos de qué se trata.
67
04_IntrodProg.qxd
23/7/07
20:47
Page 68
PROGRAMACIÓN ORIENTADA A OBJETOS
REDEFINIR COMPORTAMIENTO
Es muy común que una clase que hereda de otra no se comporte exactamente
igual, aun para el comportamiento heredado. Veamos un ejemplo real: entre las
herramientas que provee el Framework .Net para crear aplicaciones basadas en
ventanas, tenemos clases para crear componentes visuales, como botones y listas.
Supongamos que debemos crear botones, pero que sean redondos, y no rectangulares como los botones normales. Si no tuviéramos la herencia, deberíamos crear
una clase BotonRedondo y escribir absolutamente todo el código. Sin embargo,
podemos hacer que nuestra clase herede de la clase Button de .Net y redefina un
método llamado OnPaint (que es el encargado de dibujar el botón en la pantalla)
para dibujarlo como necesitamos. Tendremos así un nuevo tipo de botón que se
dibuja como queremos, pero que posee todo el comportamiento y las propiedades de cualquier otro botón (por ejemplo, reacciona ante un clic con el ratón).
Clase Boton
Dibujar:
Dibuja un rectángulo y
coloca el texto del botón
AtenderClick()
Clase BotonRedondo
Dibujar:
Dibuja un circulo y
coloca el texto del botón
La Clase
BotonRedondo
funciona exactamente
igual que la clase
Boton pero se dibuja
distinto.
Figura 7. Una clase puede redefinir un método heredado
para personalizarlo según su propio comportamiento.
68
04_IntrodProg.qxd
23/7/07
20:47
Page 69
› HERENCIA
EXTENDER CLASES
La extensión es un mecanismo para agregar comportamiento y propiedades a
una clase, es decir, a todas las características que se heredan de la clase padre, se
le agregan otras propias de la clase derivada. Por ejemplo, si tenemos una clase
Persona y de ella derivamos una clase Empleado, podemos aplicar extensión agregando las propiedades Jefe y Sueldo. Si miramos este ejemplo pensando en la relación es-un, podemos decir que un Empleado es-una persona que además posee
un sueldo y un jefe, es decir, es una extensión.
La extensión es una de las aplicaciones más utilizadas y naturales de la herencia, ya que sienta las bases de un buen diseño que derive en código claro, mantenible y robusto.
La extensión se puede combinar con la redefinición, para reutilizar aun más el
código heredado. Esto es, podemos redefinir un método de manera tal que se
comporte exactamente igual al método heredado del padre, pero que además haga algo extra. Volviendo al ejemplo del Botón, imaginemos que queremos hacer
un botón tal que, cuando el cursor del ratón pase por encima, cambie de color.
Deberemos entonces definir una nueva clase, que herede de Button y redefinir el
método OnPaint, pero en vez de escribirlo todo desde cero, llamamos al método
de la clase padre para dibujar el botón, y luego, si el cursor está sobre el rectángulo que comprende el botón, le cambiamos el color.
TIPOS DE HERENCIA
Hasta hora vimos ejemplos de herencia donde una clase derivada hereda sólo de
una clase base. Sin embargo, en la práctica pueden presentarse casos en que una
clase herede de más de una clase padre. Por ejemplo, imaginemos que tenemos
una clase Empleado, con una propiedad Sueldo, y una clase Músico con una pro-
› HERENCIA MÚLTIPLE
La mayoría de los lenguajes actuales no soportan herencia múltiple,
ya que en general este tipo de herencia presenta más problemas que
soluciones. Uno de los problemas
de la herencia múltiple es cómo re-
solver casos en que un método o
propiedad existe en más de una clase base, entonces, ¿cuál es el que
recibe la clase derivada? Tanto los
lenguajes de .Net como Java no permiten herencia múltiple.
69
04_IntrodProg.qxd
23/7/07
20:47
Page 70
PROGRAMACIÓN ORIENTADA A OBJETOS
piedad InstrumentoQueToca y queremos definir una clase EmpleadoDeOrquesta.
Como los empleados de orquestas son músicos (tocan un instrumento) y además
son empleados (perciben un sueldo), podemos heredar de ambas clases para reutilizar el código de cada una. Este tipo de herencia se denomina Herencia Múltiple.
CLASES ABSTRACTAS
En el ejemplo de las figuras geométricas que vimos al comienzo, definimos una
clase Figura con un método CalcularSuperficie. Ahora bien, esa clase, es una abstracción de todas las posibles figuras geométricas y, por lo tanto, no le podemos
definir el cálculo de superficie, ya que depende de cada tipo de figura. Afortunadamente, para resolver cuestiones como ésta desde el diseño, tenemos la posibilidad de crear métodos abstractos: métodos que están declarados, pero que no
tienen implementación. La implementación de un método abstracto queda relegada a las clases derivadas. Una clase que contiene al menos un método abstracto se denomina abstracta.
INTERFACES
Las interfaces son un derivado de las clases abstractas. En particular, una interfaz es esencialmente igual a una clase con todos sus métodos abstractos. Una
clase que herede de una interfaz (en realidad para hablar correctamente debemos decir que la clase implementa una interfaz) está obligada a implementar
› CLASE ABSTRACTA O INTERFAZ › INSTANCIA
Una decisión que muchas veces tendremos que tomar es si optar por una
clase abstracta o por una interfaz. La
diferencia radica en dos puntos: si
usamos una clase abstracta, tendremos la posibilidad de escribir algo de
código (métodos no abstractos), para
no tener que escribirlo en cada clase
descendiente. Con las interfaces, tendremos abierta la posibilidad de heredar de otra clase.
70
En .Net, cuando definimos una variable que es una instancia de una clase, en realidad, lo que estamos guardando en la variable no es el objeto
completo sino la dirección de memoria (en el HEAP) donde se encuentra
el objeto. Cuando pasamos un parámetro por valor, lo hacemos por referencia, y cualquier modificación
del parámetro, la haremos sobre el
objeto original.
04_IntrodProg.qxd
23/7/07
20:47
Page 71
› HERENCIA
todos sus métodos y propiedades. Las interfaces se utilizan para proveer una
abstracción de algún comportamiento. Además, los lenguajes modernos como
Java y los lenguajes de .Net permiten que una clase implemente varias interfaces, proveyendo así una especie de herencia múltiple.
Clase Figura
Interfaz IDibujable
CalcularSuperficie()
Dibujar()
Clase FiguraDibujable
Dibujar()
La Clase
FiguraDibujable es
una figura, pero
también tiene la
capacidad de dibujarse
al implementar la
interfaz IDibujable.
Figura 8. Las interfaces permiten abstraer comportamientos
y, a la vez, heredar de otra clase, proveyendo un tipo de herencia múltiple.
› PROGRAMADOR .NET
Un completísimo portal con información para programadores. Actualmente, hay más de 3500 recursos entre artículos y ejemplos
de código. Los artículos y ejemplos cubren la mayoría de los temas y de los lenguajes actuales
que todo programador debe conocer. Está organizado en categorías, por lo que resulta sencillo
encontrar lo que buscamos.
71
04_IntrodProg.qxd
23/7/07
20:47
Page 72
PROGRAMACIÓN ORIENTADA A OBJETOS
POLIMORFISMO
> El polimorfismo es una técnica que permite tratar a un objeto de una clase
derivada como si fuese de la clase padre. Polimorfismo significa muchas formas, y
justamente se usa este término, porque permite que una variable tenga múltiples
formas. La definición misma de la relación de herencia brinda la posibilidad de
contar con el polimorfismo, esto es, como una clase derivada es-una clase base,
siempre que necesitemos una clase base podremos usar una clase derivada. Por
ejemplo, si tenemos un método que recibe un objeto de clase Figura, podremos
pasarle un objeto de clase Rectángulo o de clase Círculo, ya que tanto el rectángulo como el círculo son figuras. Los lenguajes actuales permiten polimorfismo
tanto por herencia como por implementación de interfaces.
La mayor utilidad del polimorfismo radica en que permite programar en un nivel de abstracción superior, ya que podemos especificar los contratos (es decir,
qué esperamos en un método o propiedad) utilizando clases abstractas, interfaces o clases base de una gran jerarquía de herencia. Además, como veremos a continuación, mediante el polimorfismo, se logra el máximo nivel de extensibilidad
del código, ya que permite encapsular las responsabilidades de los objetos dentro
de los objetos mismos y desde afuera trabajar con su abstracción o clase base.
Para ilustrar la utilidad del polimorfismo, supongamos que tenemos que escribir un procedimiento que imprima la superficie de una figura, pero sin trabajar
con orientación a objetos. Para hacerlo, necesitaremos conocer el tipo de figura
y escribir una sentencia de tipo select case para calcular la superficie según el tipo de figura. Veamos algo de código (en C#).
› EARLY BINDING VS LATE BINDING
El término Binding hace referencia al
enlace entre el tipo de dato de una variable y el de su valor. Cuando el tipo de
dato que tendrá un valor queda establecido en tiempo de codificación, se
denomina Early Binding, mientras que
cuando el tipo de dato real se conoce
72
recién en tiempo de ejecución, hablamos de Late Binding (enlace tardío).
Cuando se explota la técnica de polimorfismo, se usa siempre Late Binding, ya que una variable se declara de
una clase, pero en ejecución puede ser
de una clase derivada.
04_IntrodProg.qxd
23/7/07
20:47
Page 73
› POLIMORFISMO
switch(tipoFigura)
{
case “Cuadrado”:
Console.WriteLine(base*altura);
break;
case “Triangulo”:
Console.WriteLine((base*altura)/2);
break;
case “Circulo”:
Console.WriteLine(3.14*radio*radio);
break;
}
El problema con esta porción de código reside en que, cuando agreguemos un
nuevo tipo de figura, tendremos que agregar también el caso para hacer el cálculo correspondiente.
Si utilizamos objetos, podemos tener una clase abstracta Figura, con un método abstracto CalcularSuperficie() y sendas clases derivadas para modelar los distintos tipos de figuras. Cada clase derivada es responsable de redefinir el método
CalcularSuperficie para adaptarlo a su fórmula de cálculo. De este modo, nuestro procedimiento para imprimir la superficie se reduce a esto:
› LA MAGIA DEL POLIMORFISMO
El polimorfismo abre las puertas a
un nivel más de abstracción, en el
que podemos escribir funcionalidad
sin interesar mucho cómo se va a
utilizar luego.
Un ejemplo claro de esto es el manejo de secuencias de bytes en .Net.
El Framework .Net posee una clase
llamada Stream, que representa
una secuencia de bytes.
Luego, hay clases derivadas para
leer y escribir de distintos medios,
como archivos y llamadas http, y
otros. Lo bueno es que muchos métodos reciben como parámetro un
Stream, pero podemos pasarle lo
que necesitemos.
Éste es un claro ejemplo de Polimorfismo, con una aplicación práctica en el mundo real.
73
04_IntrodProg.qxd
23/7/07
20:47
Page 74
PROGRAMACIÓN ORIENTADA A OBJETOS
public void Imprimir(Figura f)
{
Console.WriteLine(f.CalcularSuperficie())
}
Cuadrado c;
...
Imprimir( c ); // un cuadrado es una figura
Con esto, cuando necesitemos agregar un nuevo tipo de figura, bastará con escribir la clase correspondiente (heredando de Figura) e implementar el método
CalcularSuperficie como corresponda, y no deberemos modificar el procedimiento de impresión.
La técnica de polimorfismo es un poco difícil de entender al comienzo, pero
una vez asimilada se convierte en el arma más poderosa de la orientación a objetos. El secreto está en entender que, cuando codificamos la llamada a un método de una clase base, debemos tener presente que en ejecución se llamará al método correspondiente de una clase derivada.
■ CONCLUSIONES
La programación orientada a objetos, bien usada, puede ayudarnos a construir software con las características más deseables por todo programador:
facilidad de escritura, facilidad de mantenimiento, extensibilidad y reutilización de código. La extensibilidad es la clave para escribir aplicaciones altamente flexibles, ya que permite agregar o modificar comportamiento con muy
poco esfuerzo. Es muy importante aprender algunos patrones de diseño e incorporar correctamente los conceptos de abstracción, encapsulamiento y polimorfismo, para no caer en el error de utilizar un lenguaje orientado a objetos para escribir código estructurado.
74
05_IntrodProg.qxd
23/7/07
20:16
Page 75
CAPÍTULO 5
UML
Hemos visto distintas formas de expresar algoritmos,
como el pseudocódigo y los diagramas de flujo.
Estas herramientas resultan muy útiles, pero fueron
pensadas para expresar procedimientos y, por lo tanto,
tienen su aplicación más popular en la programación
estructurada. Con la llegada de las técnicas de POO,
se buscaron nuevas formas de expresar detalles
del problema por resolver ya que, en la programación
orientada a objetos, sólo nos concentramos en los
objetos del dominio y en los mensajes entre ellos.
ATENCIÓN AL LECTOR > [email protected]
05_IntrodProg.qxd
23/7/07
20:16
Page 76
UML
¿QUÉ ES UML?
> UML es la sigla de Unified Modeling Language, o Lenguaje Unificado de Mo-
delado. Como su nombre lo indica, UML es un lenguaje. Pero no un lenguaje
de programación como los que vimos durante todo el libro, sino uno de modelado: su propósito principal consiste en definir modelos. Por modelo, se entiende una representación simplificada de la realidad. Además, UML es un lenguaje
visual, es decir, toda su expresividad se basa en gráficos.
El lenguaje UML nació en 1995 como resultado del trabajo en conjunto de
Rumbaugh y Booch, dos investigadores en el área de Metodología, que comenzaron a trabajar juntos en Racional, una compañía fundada por Booch. Luego se
les sumó Jacobson, quien aportó más ideas para desarrollar lo que se convirtió en
la primera versión de UML. Hasta entonces, se habían desarrollado unos cuantos mecanismos de modelado de software, pero como ninguno fue formalizado
correctamente ni estandarizado, cada ingeniero de software los aplicaba como sabía y como le convenía. Esto trajo como consecuencia que los modelos desarrollados fueran incomprendidos por los programadores, perdiendo su efectividad y,
por lo tanto, su utilidad. UML fue aprobado como un estándar, lo que lo convierte en una herramienta fundamental para los analistas, diseñadores y arquitectos de software, que necesitan transmitir a los programadores, de forma clara y
precisa, los detalles del software por implementar.
UML consiste en un conjunto de diagramas de distintos tipos. Cada uno está destinado a cubrir alguno de los aspectos del software, y se pretende que con
UML se puedan modelar absolutamente todos los elementos de una aplicación. Hay diagramas para modelar los objetos, sus interacciones, las interacciones del sistema con el mundo exterior (ya sean usuarios u otros sistemas). El
objetivo principal de UML es el de proveer una herramienta de comunicación
precisa y sin ambigüedades. En la actualidad, existen numerosas herramientas
que no sólo ayudan a construir los diagramas en UML, sino que además permiten validar el modelo construido contra la especificación del lenguaje e incluso generar código sobre la base del modelo.
A lo largo de este capítulo, estudiaremos los principales conceptos de algunos
de los tipos de diagrama más importantes de UML y veremos cómo se relaciona
con los conceptos de orientación a objetos que ya hemos aprendido.
76
05_IntrodProg.qxd
23/7/07
20:16
Page 77
› DIAGRAMA DE CLASES
DIAGRAMA DE CLASES
> El diagrama de clases se utiliza para modelar las clases que intervienen en la
solución, con sus propiedades, sus métodos y las relaciones entre ellas. Es un diagrama de tipo estático, en el sentido que modela entidades que se mantienen
constantes durante la ejecución. Recordemos que las clases son fijas, y lo que varía son las instancias, o sea, los objetos.
El diagrama de clases resulta muy útil tanto para especificar los requerimientos
como para documentar los detalles del diseño. Además, mediante técnicas de ingeniería directa, permite generar el código de las clases, no sólo la definición, sino también las propiedades y el esqueleto de los métodos. Veremos a continuación las principales características del diagrama de clases, para empezar, cómo y
cuándo utilizarlo.
CLASES
Las clases se representan mediante un rectángulo dividido horizontalmente en
tres partes. En la primera parte, se coloca el nombre de la clase; en la segunda,
las propiedades; y en la tercera, los métodos. Recordemos que UML es un lenguaje y, por lo tanto, debemos respetar sus nomenclaturas y estándares, o el modelo que definamos no será un diagrama UML válido.
Nombre
de la clase
Cliente
Nombre
RealizarPago(cantidad)
}
}
Propiedades
Métodos
Figura 1. En el diagrama de clases, cada una se representa
mediante un rectángulo con el nombre de la clase en la parte superior.
De las tres partes, sólo es obligatorio el nombre de la clase en la primera; las otra
dos pueden quedar vacías. Además, no es necesario especificar todas las propiedades y operaciones de la clase, sólo las que resultan útiles para el modelo.
77
05_IntrodProg.qxd
23/7/07
20:16
Page 78
UML
Para darle más precisión al modelo, se puede especificar el tipo de dato de cada
propiedad, separándolo del nombre de la propiedad con dos puntos (Nombre:
String). Si el tipo de dato de una determinada propiedad es otra clase, estamos
ante un caso de composición, que tiene una forma específica en UML (que veremos luego), sin embargo, en casos en que no haya lugar a dudas es posible indicarlo como si fuese un tipo de dato básico, como muestra la Figura 2.
Cliente
Nombre: String
Contacto: Persona
La sección de
propiedades y de
métodos puede
quedar vacia.
Podemos especificar los tipos
de dato de las propiedades.
En algunos casos podemos
usar clases como tipo de dato
de las propiedades.
Figura 2. Si bien es un caso de composición, a veces,
podemos usar clases como tipo de las propiedades.
Visibilidad
En UML, se puede especificar la visibilidad de cada elemento de una clase
(propiedad o método). Si bien la visibilidad es algo más relacionado a la implementación en un lenguaje particular, es importante indicarla en el modelo
de diseño, ya que permite dejar definidas ciertas normas de encapsulamiento
que el analista o el diseñador detectan durante las primeras fases del proyecto. Si bien UML prevé notaciones para los tipos de alcance básicos, el lenguaje está abierto y permite utilizar modificadores propios del lenguaje que se utilice luego para la implementación.
› INGENIERÍA DIRECTA E INVERSA
Cuando se habla de modelos y, sobre todo de UML, es muy común
mencionar a la Ingeniería Directa y
a la Ingeniería Inversa. Mediante la
ingeniería directa, se puede generar
78
el código fuente que implemente el
modelo, mientras que, a través de la
ingeniería inversa, se puede obtener
un modelo completo o parcial a partir de código fuente ya escrito.
05_IntrodProg.qxd
23/7/07
20:16
Page 79
› DIAGRAMA DE CLASES
La visibilidad se expresa mediante un signo a la izquierda del nombre de la propiedad o del método, y los utilizados son el signo más (+) para los elementos públicos, el signo menos (-) para los privados y el numeral (#) para los protegidos.
Recordemos que los elementos protegidos sólo son visibles por la clase y por sus
descendientes, mientras que los privados sólo son visibles dentro de la clase.
RELACIONES ENTRE CLASES
Las relaciones entre clases también están contempladas en UML, dado que, durante el diseño, es muy útil especificar claramente cualquier relación, que deberá ser traducida luego al código.
Composición
En UML, la composición se representa mediante una línea que une las dos clases, pero colocando un pequeño rombo del lado de la clase que se compone a
partir de la otra. Opcionalmente, se puede indicar el nombre de la propiedad que
representa la composición, pero no es muy habitual hacerlo, sobre todo en las
etapas iniciales del análisis.
En la Figura 3, vemos que una Escuela se compone de Aulas. Observemos los pequeños números debajo de la línea. Estos números indican la multiplicidad, es decir, cuántos elementos pueden existir de cada lado. El asterisco (*) indica muchos,
sin especificar cuántos. Según esto, el diagrama de la Figura 3 deja bien claro que
una escuela se compone de muchas aulas. Del mismo modo, si leemos la multiplicidad en sentido inverso, podemos entender que un aula está sólo en una escuela.
Escuela
+ Nombre: String
Aula
Posee
1
1..*
+ Capacidad: int
Figura 3. UML permite especificar la relación de composición,
indicando además cuántos elementos de una clase componen a la otra.
Además, sobre la línea que indica la relación, se puede colocar un texto aclaratorio sobre el tipo de composición. En el ejemplo de la Figura 3, hemos especificado Posee como nombre de la relación. Si bien es opcional, el nombre de
79
05_IntrodProg.qxd
23/7/07
20:16
Page 80
UML
la relación ayuda mucho a la claridad del diagrama y a su posterior implementación en un lenguaje de programación.
Asociación simple
Anteriormente vimos que, si el tipo de dato de una propiedad es una clase, podemos escribirlo directamente o bien utilizar una relación de asociación. Hay una
tercera opción que es usar ambos al mismo tiempo, pero no la veremos ahora.
La relación de asociación se representa de manera similar a la composición, pero
sin utilizar el rombo; simplemente se coloca una línea entre las dos clases asociadas
y una flecha que indica el sentido de la asociación. Al igual que en la composición,
puede agregarse el nombre de la relación para aumentar la riqueza del modelo.
Escuela
+ Nombre: String
Ubicada
en
Dirección
+ Calle: String
+ Numero: int
Figura 4. La relación de asociación es similar a la composición,
pero se utiliza una flecha para indicar el sentido de la relación.
› PROGRAMACION Y DISEÑO
Los conceptos introducidos por la POO
como forma de trabajo, no sólo se aplican a la programación propiamente dicha, sino que también se puede hacer
80
análisis orientado a objetos, y diseño
orientado a objetos. UML está pensado para la creación de diagramas
usando este paradigma.
05_IntrodProg.qxd
23/7/07
20:16
Page 81
› DIAGRAMA DE CLASES
Herencia
La relación de herencia también tiene su forma particular dentro del lenguaje
UML. La representación de la herencia consiste en una flecha con la punta sin
rellenar (en blanco). Lo más común es colocar las clases derivadas debajo de la
clase base, de manera que la flecha que representa la herencia tenga sentido de
abajo hacia arriba, generando un diagrama tipo árbol. Si la clase base es abstracta, se debe colocar el nombre con letra itálica (por ejemplo, Figura). Aunque parezca una obviedad (dada la definición de herencia) tengamos en cuenta que, en
el diagrama de las clases derivadas, no es necesario volver a indicar las propiedades y los métodos heredados.
Escuela
+ Nombre: String
Primaria
Secundaria
Terciaria
Publica
Privada
Figura 5. La herencia se representa mediante flechas sin relleno, apuntando hacia la clase base.
En el caso de las interfaces, UML prevé una forma de representarlas, como así
también una notación especial para indicar la implementación de una interfaz por
parte de una clase. Para indicar que se trata de una interfaz y no de una clase, en el
diagrama de clases se debe colocar el indicador interfaz sobre el nombre, en la pri81
05_IntrodProg.qxd
23/7/07
20:16
Page 82
UML
mera división del rectángulo que representa en este caso la interfaz. Además, si bien
desde el punto de vista del polimorfismo la implementación de interfaces resulta
similar a la herencia, no es lo mismo, por lo que UML hace la distinción correspondiente. Para especificar que una clase implementa una interfaz, se debe colocar
una flecha de punta hueca apuntando hacia la interfaz, pero con línea punteada.
En la Figura 6 se aprecia una interfaz (IComunicador) y una clase, la que implementa (ComunicadorPorModem). Notemos que, a diferencia del diagrama de herencia,
aquí sí debemos especificar en la clase todos los métodos y propiedades de la interfaz, ya que al aceptar el contrato que la interfaz nos impone, debemos implementarlo completamente (caso contrario ni el modelo ni el código serán válidos).
<<interface>>
<<interface>>
IComunicador
IComunidor
+ Inicializar():
Inicializar() :void
void
+ Finalizar():
Finalizar() :void
void
+ <<property>> DispositivoListo():
DispositivoListo() :bool
bool
+ CadenaRecibida(string):
CadenaRecibida(string) :void
void
+ EscribirCadena(string):
EscribirCadena(string) :void
void
+ AsignarReceptorDeDatos(RecepciónDeDatos):
AsignarReceptorDeDatos(RecepcionDeDatos) :void
void
IDisponible
ComunicadorPorModem
– puerto: SerialPort
+ Inicializar(): void
+ Finalizar(): void
+ <<property>> DispositivoListo(): void
+ CadenaRecibida(string): void
+ EscribirCadena(string): void
Figura 6. Al modelar la implementación de una interfaz, debemos volver
a indicar todas las propiedades y métodos en la clase que la implementa.
82
05_IntrodProg.qxd
23/7/07
20:16
Page 83
› DIAGRAMA DE SECUENCIA
DIAGRAMA DE SECUENCIA
> Varias veces dijimos que la programación orientada a objetos se basa en la representación en software de los objetos que intervienen en el dominio de la aplicación junto con las relaciones e interacciones entre ellos. Mediante el diagrama
de clases, UML permite especificar de manera estática las clases del modelo y sus
relaciones. Para expresar la forma en que los objetos (no las clases) interactúan
para llevar a cabo su objetivo dentro de la aplicación y solucionar un problema,
UML provee el diagrama de secuencia, que permite especificar los mensajes que
intercambian los objetos, el orden en que lo hacen y cómo reacciona cada objeto ante la llegada de un mensaje proveniente de otro.
ELEMENTOS DEL DIAGRAMA DE SECUENCIA
A diferencia del diagrama de clases, en el diagrama de secuencia intervienen instancias, es decir, objetos concretos que pertenecen a una determinada clase. Los objetos se representan simplemente como rectángulos con su nombre y la clase a la
que pertenecen dentro de él, separadas por dos puntos (:). En caso en que no interese la identidad de una instancia en particular, se puede omitir el nombre, colocando sólo el nombre de la clase a la que pertenece, precedida por los dos puntos.
Otro elemento de este diagrama son los mensajes. Desde el punto de vista de la
implementación, podemos considerar los mensajes como invocación a métodos:
cuando un objeto A envía un mensaje B a un objeto C, podemos traducir como
que el objeto A invoca el método B del objeto C. Los mensajes se representan mediante flechas simples, con el nombre del mensaje escrito sobre ellas.
Por último, dado que para representar una secuencia debemos especificar el orden en que los mensajes son enviados, UML permite indicar una línea de tiempo, que se coloca en el diagrama de manera vertical, y que consiste en una línea
punteada por cada una de las instancias que intervienen en el diagrama.
Para entender cómo funciona el diagrama de secuencia, imaginemos que tenemos que representar un escenario de matriculación de un alumno en un curso.
Tenemos una clase Alumno, una clase Curso y una clase Facultad. La clase Facultad tiene la capacidad de determinar si un alumno puede inscribirse en un curso
(porque conoce las correlatividades y las asignaturas aprobadas por el alumno).
Cuando un alumno quiere inscribirse, hace la solicitud al curso (mediante un
83
05_IntrodProg.qxd
23/7/07
20:16
Page 84
UML
mensaje), y el curso consulta con la universidad si se cumplen las condiciones. Si
todo está bien, el curso acepta la inscripción del alumno.
La Figura 7 representa este escenario mediante un diagrama de secuencia.
unAlumno : Alumno
unCurso: Curso
: Universidad
SolicitarInscripcion(unAlumno)
VerificarCorrelatividades(unAlumno, unCurso)
PuedeCursar
Solicitud aprobada
Figura 7. El diagrama de secuencia muestra la interacción
entre los objetos en un determinado escenario.
Como observamos en el diagrama, el objeto Alumno envía un mensaje SolicitarInscripcion al objeto Curso. Luego, éste envía un mensaje VerificarCorrelatividades al objeto de clase Universidad, quien contesta afirmativamente. Ante la llegada de la respuesta de la universidad, el objeto Curso le confirma la inscripción
al objeto Alumno. Observemos que el tiempo corre hacia abajo, por lo que el orden en que aparece cada mensaje en el diagrama es cronológico.
En la Figura 7, hay algunos detalles que aún no mencionamos. Los mensajes de
línea punteada corresponden a las respuestas de un objeto a un mensaje determinado. Los rectángulos delgados sobre las líneas de tiempo corresponden a instancias particulares, esto es, cada rectángulo en la línea de tiempo de un objeto corresponde a una instancia particular.
84
05_IntrodProg.qxd
23/7/07
20:16
Page 85
› DIAGRAMAS DE CASOS DE USO
DIAGRAMAS DE CASOS DE USO
> Un caso de uso es un resumen de un escenario particular, que describe la
forma en que los elementos externos (usuarios u otros sistemas) interactúan
con el software que se está modelando. UML permite crear diagramas para
modelar casos de uso, que resultan de gran utilidad para documentar y para
negociar con los usuarios y clientes sobre la funcionalidad que tendrá el software una vez implementado.
En los diagramas de caso de uso, intervienen actores y casos de uso. Los actores
son cualquier elemento que puede interactuar con el software (usuarios, organizaciones o sistemas). Los casos de uso son secuencias de acciones que proveen algún tipo de valor para los actores. Los casos de uso se dibujan mediante óvalos con
su nombre adentro, mientras que los actores se representan mediante hombrecitos. Opcionalmente, puede dibujarse un rectángulo para indicar los límites del sistema y así dejar bien claro cuáles son las responsabilidades que se asumirán a la
hora de la implementación (todo lo que está fuera de los límites, se asume que no
será responsabilidad de los desarrolladores).
En la Figura 8, se aprecia cómo el gerente de personal interactúa con el sistema de sueldos, tanto renovando contratos de personal como generando liquidaciones de sueldos. Además del gerente, un sistema que pertenece al estado
interactúa con el sistema de sueldos solicitando el cálculo de los impuestos para controlar el estado impositivo de los empleados. Observemos también que
un caso de uso no es independiente, sino que puede utilizar a otro para cumplir su tarea (representado en la figura como una línea que une Generar Liquidaciones con Calcular Impuestos).
› RECURSOS EN LA WEB
En Internet hay una gran cantidad de
información sobre UML, desde detalles básicos hasta ejemplos completos y de gran complejidad. Sin duda, el
sitio de cabecera es el oficial de UML
(en inglés): www.uml.org.
Otra página de gran interés, también
en idioma inglés, que se puede visitar
es Agile Modeling (www.agilemodeling.com/artifacts), que cuenta con
una sección completa dedicada a diagramas y modelos UML.
85
05_IntrodProg.qxd
23/7/07
20:16
Page 86
UML
Sistema de Sueldos
Renovar Contratos
Gerente de Personal
Generar Liquidaciones
Calcular Impuestos
Sistema estatal de Control
Figura 8. El diagrama de casos de uso especifica cómo se relaciona el sistema con el mundo exterior.
■ CONCLUSIONES
A lo largo del capítulo, vimos algunos de los diagramas que permite crear UML y
su aplicación en el ciclo de vida del desarrollo de software, sobre todo para documentar las primeras etapas (análisis y diseño). Si bien, al ser visual, UML resulta bastante fácil de aprender, es recomendable familiarizarse primero con las
técnicas y conceptos generales de la orientación a objetos y luego dedicarse a
aprender UML, de manera de poder sacarle el mayor provecho posible a este poderoso lenguaje de modelado. También será imprescindible la lectura del libro
UML Distilled, de Martin Fowler (traducido al español como UML gota a gota).
86
06_IntrodProg_ApendA.qxd
23/7/07
20:17
Page 87
APÉNDICE
El examen
En este apéndice, haremos un repaso general
de los principales conceptos aprendidos y veremos
algunas preguntas del ejemplo del examen
correspondiente a la estrella cero del programa
Desarrollador Cinco Estrellas 2005
(http://www.dce2005.com).
ATENCIÓN AL LECTOR > [email protected]
06_IntrodProg_ApendA.qxd
23/7/07
20:17
Page 88
EL EXAMEN
ALGORITMOS
Un algoritmo es una secuencia ordenada y sistemática de pasos que lleva a la
solución de un problema o a realizar cierto cálculo complejo. Recordemos que se
debe tener en cuenta la importancia del orden de las operaciones. Si contamos
con una secuencia de pasos para realizar una tarea, pero en ella no importa el orden en que los llevemos a cabo, porque igual alcanzamos el objetivo, no se trata
de un algoritmo. Todo programador debe tener la habilidad para deducir algoritmos a partir de un problema, ya que esta tarea conforma el primer paso para
la implementación de cualquier aplicación de software.
Cuando creamos un algoritmo para un problema determinado, normalmente,
deberemos expresarlo para poder comunicarlo a los demás o para dejar documentada la idea. Existen distintas formas de expresar algoritmos, pero las dos más comunes y más usadas son el pseudocódigo y los diagramas de flujo. El pseudocódigo es una forma flexible y abierta de expresar algoritmos, pero sin las ambigüedades del lenguaje natural. Resulta parecido a un lenguaje de programación, pero no existen reglas precisas que deban ser cumplidas al usarlo, con lo cual cada
programador puede escribir pseudocódigo como mejor le resulte. Por otro lado,
los diagramas de flujo son una herramienta visual y más precisa, ya que cuenta
con reglas que definen exactamente cómo debe expresarse cada tipo de estructura de un algoritmo (como por ejemplo, puntos de decisión).
LENGUAJES DE PROGRAMACIÓN
Los lenguajes de programación permiten escribir instrucciones para una computadora utilizando palabras y símbolos pertenecientes a algún idioma humano.
El principal objetivo de los lenguajes de programación es proveer una herramienta que resulte más sencilla que ingresar las secuencias de números que es capaz
de interpretar una computadora como instrucciones.
Según el grado de abstracción o de lo mucho que se aleje un lenguaje del código nativo de la máquina, se puede clasificar en lenguaje de bajo nivel (como Assembler), lenguaje de nivel intermedio (como C) o lenguaje de alto nivel (como
por ejemplo los lenguajes de .Net). En general, los lenguajes de bajo nivel son
más difíciles de utilizar, pero permiten escribir programas más eficientes, mientras que los de alto nivel poseen mayor expresividad, ya que con una instrucción
del lenguaje es posible darle varias instrucciones a la máquina, incluso con un esfuerzo mental menor por parte del programador.
88
06_IntrodProg_ApendA.qxd
23/7/07
20:17
Page 89
› PROGRAMACIÓN ESTRUCTURADA
Como la computadora sólo puede interpretar instrucciones expresadas como secuencias de números binarios (unos y ceros), es necesario traducir los programas
escritos en algún lenguaje, a instrucciones que la máquina pueda entender. Para
ello, podemos utilizar un intérprete o un compilador. Los compiladores toman el
texto del programa (el código fuente) y generan un archivo con instrucciones ejecutables por la computadora. El proceso de compilación se realiza una sola vez:
cuando el programa está terminado y antes de poder correrlo en la computadora.
Los intérpretes, en cambio, toman cada una de las instrucciones del programa, las
convierten a código de máquina y las ejecutan. A diferencia de la compilación, el
proceso de interpretación se lleva a cabo cada vez que el programa se ejecuta; por
esto, generalmente, los programas compilados funcionan un poco más rápido que
los programas interpretados. En .Net, al momento de compilar, no se genera código de máquina, sino que se produce código en un lenguaje intermedio que luego,
durante la primera ejecución, es traducido a lenguaje de máquina.
En algunos lenguajes, el compilador no genera el código definitivo, sino que genera un archivo llamado código objeto, que luego debe ser enlazado con las librerías utilizadas para generar un único archivo ejecutable. El encargado de realizar esto es un programa llamado linker (enlazador).
PROGRAMACIÓN ESTRUCTURADA
La programación estructurada es un paradigma que propone construir programas utilizando sólo tres tipos de construcciones: la secuencia de instrucciones,
la ejecución condicional y la repetición. Frente a la programación lineal que utiliza las instrucciones de salto (GOTO) para modificar el curso de ejecución, la
programación estructurada representa una importante mejora en las formas de
construir software, ya que permite obtener código mucho mejor escrito, más claro, más legible, fácil de mantener y de modificar. Repasemos algunos de los principales conceptos de la programación estructurada:
Identificadores: un identificador es una palabra que se utiliza para darle nombre a algún componente del programa, ya sea una instrucción, un subprograma
o una variable. La mayoría de los lenguajes de programación poseen reglas estrictas para la escritura de nuevos identificadores, como por ejemplo, que deban comenzar necesariamente con una letra del alfabeto o con un guión bajo.
Variables: es una posición de memoria donde se encuentra un dato que debemos manejar dentro del programa. Casi siempre, las variables poseen un nom89
06_IntrodProg_ApendA.qxd
23/7/07
20:17
Page 90
EL EXAMEN
bre, y se utiliza un identificador para nombrarlas y referenciarlas en el código
fuente. Las variables poseen un alcance o ámbito, que determina las porciones de
código desde donde se puede acceder al valor contenido en la variable.
Tipos de datos: definen el rango de valores y las operaciones que se pueden
aplicar sobre una variable. No todos los lenguajes exigen asociar un tipo de dato
a una variable (como por ejemplo Visual Basic). Los lenguajes que obligan a definir las variables con tipo, se denominan comúnmente fuertemente tipados. Los
lenguajes de esta clase son más seguros en cuanto a errores en tiempo de ejecución, ya que permiten detectar asignaciones incorrectas a las variables durante el
proceso de compilación del programa.
Sentencias: son acciones que pueden ser ejecutadas como parte del programa.
La principal diferencia entre sentencia e instrucción es que una sentencia puede
traducirse en cero o más instrucciones. Una sentencia que se traduce en cero instrucciones se denomina no ejecutable. El ejemplo más conocido de sentencia no
ejecutable son los comentarios utilizados para dejar mensajes en el código fuente, que explican alguna idea o cualquier otro aspecto que el programador considere necesario para aumentar la legibilidad del código. Siempre resulta una buena práctica el uso de comentarios. Las sentencias pueden ser simples o compuestas. Una sentencia simple es una sentencia en sí misma, mientras que una sentencia compuesta consiste en dos o más sentencias simples agrupadas mediante
alguna estructura de control, como puede ser un condicional.
Procedimientos y funciones: una de las técnicas más usadas para simplificar la
implementación de un programa consiste en dividirlo en unidades más pequeñas
llamadas módulos o subprogramas. Generalmente, los módulos son implementados como procedimientos o funciones. Los procedimientos son unidades independientes que pueden ser llamadas desde cualquier otra parte del programa y
consisten de una secuencia de instrucciones que se ejecutan y terminan, pero sin
devolver nada a quien lo llamó. Las funciones, en cambio, luego de ejecutarse,
devuelven un valor como resultado. Por eso, se utilizan para realizar algún cálculo o alguna operación que requiera devolver un resultado.
PROGRAMACIÓN ORIENTADA A OBJETOS
La programación orientada a objetos (POO u OOP, por sus siglas en inglés) es
un paradigma de programación que propone identificar los objetos propios del
dominio del problema por resolver, y representar sus propiedades y sus interac90
06_IntrodProg_ApendA.qxd
23/7/07
20:17
Page 91
› PROGRAMACIÓN ORIENTADA A OBJETOS
ciones mediante el código del programa. La POO permite lograr programas aún
más fáciles de entender que con la programación estructurada, ya que hay una
correspondencia casi directa entre la realidad y la representación de esa realidad
mediante un lenguaje de programación. Además, utilizada correctamente, la programación orientada a objetos permite una mayor reutilización de código y un
mejor encapsulamiento, lo que facilita el mantenimiento tanto para corregir
errores como para agregar funcionalidades nuevas.
Los conceptos más importantes que hay que conocer son el de clase y el de objeto. Una clase es una abstracción de un grupo de objetos que comparten ciertas
características y comportamiento. Un objeto es un elemento particular de una
determinada clase, y como tal, posee las mismas propiedades y comportamiento
que los demás miembros de la clase, pero posee una identidad única. Esto significa que cualquier otro objeto será distinto, por más que tenga las mismas propiedades. Los objetos se caracterizan por tener un estado, que consiste en el conjunto de valores de sus propiedades en una unidad de tiempo específica.
Otro concepto importante es el de encapsulamiento. Éste consiste en ocultar dentro de una clase la implementación real de una propiedad o método, de manera que
desde afuera se acceda mediante una interfaz que oculte los detalles internos. Lograr
un buen nivel de encapsulamiento es fundamental para reducir el impacto de los
cambios que harán en un futuro. Los lenguajes de .Net permiten el encapsulamiento mediante el uso de propiedades, que básicamente son una forma de exponer atributos de la clase, pero ocultando la forma interna en que se almacena ese valor.
Muchas clases poseen las mismas características que otras, aunque tienen algunas
propias. En situaciones así, podemos hacer uso de la herencia. La herencia es una
técnica que permite definir una clase como derivada de otra. Al hacerlo, la nueva
clase recibe o hereda todo el comportamiento y las propiedades de su clase padre.
Los programadores y analistas más experimentados buscan crear jerarquías de herencia, de manera de aprovechar al máximo la reutilización de código de las clases
superiores en las clases inferiores. En ocasiones, puede ocurrir que la clase base (la
clase de la que heredan las otras) no represente un conjunto de objetos concretos y
exista sólo para contener comportamiento y código común. Este tipo de clases se
denominan abstractas. Algunos lenguajes permiten que una clase herede de varias,
pero esto muchas veces desemboca en un problema de implementación. Los lenguajes de la plataforma .Net sólo permiten heredar de una única clase. En .Net (y
en Java también), si necesitamos abstraer una cierta funcionalidad común a va91
06_IntrodProg_ApendA.qxd
23/7/07
20:17
Page 92
EL EXAMEN
rias clases no relacionadas entre sí, podemos hacer uso de las interfaces, parecidas a las clases abstractas con la diferencia que no tienen ningún método implementado y que una clase puede implementar más de una interfaz, además de heredar de una clase. Una de las capacidades más interesantes brindadas por la programación orientada a objetos es el polimorfismo Éste consiste en conocer la clase real de un objeto recién durante la ejecución del programa. El polimorfismo
se basa en la herencia y es la clave para lograr código extensible y flexible, atributos muy deseados en todo programa.
DESARROLLADOR 5 ESTRELLAS
Veamos algunas de las preguntas del examen cero del DCE 2005. Los exámenes de Desarrollador 5 Estrellas incluyen 20 preguntas de tipo multiple choice,
donde debemos elegir entre 4 posibles respuestas. Para aprobar, es necesario responder bien al menos el 70% de las preguntas.
1)
¿Cuál es la utilidad de los modificadores de acceso?
A. Permiten definir el nivel de acceso de los miembros de una clase.
B. Permiten definir niveles de autenticación y autorización de los métodos
de una clase.
C. Permiten relacionar clases entre sí.
D. Permiten crear instancias de clases.
La respuesta correcta es la A. Los modificadores de acceso permiten definir
quiénes pueden acceder a una propiedad o método. Los más comunes son
public, private y protected.
2)
Las estructuras de control selectivas:
A. Dirigen el flujo de ejecución según la evaluación de expresiones.
B. Permiten ejecutar un conjunto de sentencias repetidamente una cierta
cantidad de veces o hasta que se cumpla una determinada condición.
C. Todas las opciones son correctas.
D. Ninguna opción es correcta.
La respuesta correcta es la A. Recordemos que las estructuras de selección en
la mayoría de los lenguajes son dos: if y select case (o switch en C#).
92
06_IntrodProg_ApendA.qxd
23/7/07
20:17
Page 93
› DESARROLLADOR 5 ESTRELLAS
3)
¿Qué es un algoritmo?
A. Un software de aplicación.
B. Cualquier código escrito en un lenguaje de programación.
C. Un método para resolver un problema mediante una serie de pasos precisos, definidos y finitos.
D. Las opciones B y C son correctas.
Esta pregunta es un poco tramposa. La respuesta correcta es la C. La respuesta D no puede ser correcta porque la opción B afirma que un algoritmo es código escrito en un lenguaje de programación, y aprendimos que si
está escrito en un lenguaje de programación ya se trata de un programa
(que, generalmente, es la implementación de un algoritmo).
4)
Un entorno integrado de programación normalmente contiene:
A. Un editor de código fuente.
B. Un compilador.
C. Un depurador.
D. Todas las opciones son correctas.
Ésta es bastante sencilla. Los entornos integrados de desarrollo justamente
se llaman se esa manera, porque integran todas las herramientas necesarias
para la escritura, compilación y depuración de programas. Por lo tanto, la
respuesta correcta es la D.
5)
¿Para qué sirve el encapsulamiento?
A. Permite ocultar los métodos de una clase.
B. Permite ocultar la implementación de los métodos de una clase.
C. Permite identificar objetos de forma unívoca.
D. Permite generar relaciones de herencia.
Como vimos en el Capítulo 4 y repasamos en este apéndice, el principal objetivo del encapsulamiento es el de ocultar la implementación de los métodos (y
propiedades) de una clase. La respuesta correcta, entonces, es la B.
93
06_IntrodProg_ApendA.qxd
23/7/07
20:17
Page 94
EL EXAMEN
6)
¿Cuál es el bloque que utiliza .NET para proveer administración estructurada de excepciones?
A. OnError/Goto
B. If/Else
C. Throw
D. Try/Catch/Finally
La respuesta correcta es la D. En Visual Basic 6, se utilizaba el OnError/Goto para manejar los errores; pero desde la primera versión de, .Net se utilizó el Try/Match/Finally, y se denomina estructurada por no utilizar GoTo.
7)
Si se desea definir un comportamiento para un conjunto de clases no necesariamente relacionadas entre sí, la mejor opción es:
A. Definir una jerarquía de herencia entre las clases.
B. Encapsular el comportamiento en una clase agregada.
C. Definir una interfaz.
D. Relacionar las clases mediante una asociación bidireccional.
Como hemos visto, la herencia no es una relación sólo sintáctica, sino más
bien semántica. Entre la clase base y la derivada, se define una relación es-un.
Si las clases no están relacionadas, entonces, no es natural usar herencia, y podemos hacer uso de las interfaces para definir el comportamiento común. Por
lo tanto, la respuesta correcta es la C.
8)
¿A qué se denomina comúnmente Cast?
A. A un conjunto de datos que serán convertidos.
B. A la conversión implícita de un objeto/tipo o tipo/objeto.
C. A una forma explicita de convertir tipos de datos entre sí.
D. Ninguna opción es correcta.
La respuesta correcta es la C. El Cast o Typecast es una conversión explícita
entre tipos de datos. Mediante la operación de cast le estamos diciendo al
compilador que nos hacemos responsables de las consecuencias de la conversión (como pérdida de precisión o, incluso, que los tipos sean incompatibles).
94