Está en la página 1de 33

Fundamentos de programación

TEMA 1 GÉNESIS, EVOLUCIÓN E INFLUENCIA DE LA ORIENTACIÓN


A OBJETOS

A pesar de que pueda parecer que la orientación a objetos (ORIENTACIÓN


A OBJETOS) es una tecnología que nació a principios de los años noventa, en
realidad las ideas básicas surgieron y se aplicaron en programas reales 30 años
antes.
El concepto básico de objeto, como veremos, se utiliza para agrupar en
una entidad única conceptos que se utilizaban separadamente. A partir de esta
primera idea, tuvo lugar un lento proceso de enriquecimiento en el que sí se
hicieron aportaciones originales que no se conocían previamente, entre las cuales,
la herencia es la más importante.
Con el tiempo se ha visto que estos conceptos, que habían surgido como
un modo diferente de programar, se podían aplicar a otros ámbitos y tareas. Hoy
día es difícil utilizar un producto o seguir una metodología informática que no esté
influenciado por la orientación a objetos.

1. GÉNESIS

Todo comenzó en la Universidad de Oslo, donde se dedicaban a construir


modelos que simulaban sistemas físicos complejos, en concreto motores de
vehículos. Obviamente se trataba de simular mediante programas, diseños de
coches y motores antes de construir los primeros prototipos, para saber como se
iban a comportar y poder validar los diseños. Esto era complicado porque en primer
lugar el diseñar el programa completo era muy complejo, pero además debían
saber, no solo informática, sino también ingeniería mecánica para tabular las
respuestas de los mecanismos, además claro está se trataba de simulaciones que
muy probablemente sería necesario modificar hasta llegar al modelo definitivo y, no
cabe la menor duda, a mayor volumen de programa, mayor dificultad en su
modificación.
Entonces tuvieron la idea: ¿Y si en lugar de diseñar el programa como un
todo, con más o menos módulos, intentamos establecer un paralelismo con el
artefacto que deseamos simular? Es decir, por cada pieza real del aparato se
construirá un módulo y la conexión de dos piezas reales se simulará con llamadas
de un módulo a otro mediante el traspaso de valores teóricos, mecánicos o
eléctricos.
Podemos decir entonces, que la orientación a objetos nació en el ámbito de
los simuladores, donde se observó que era más fácil diseñar programas de
simulación estableciendo un paralelismo con las diferentes piezas que componen el
artefacto que el programa tenía que simular.
Por tanto, podríamos dar como primera definición de objeto a un módulo
con atributos y operaciones que modela una entidad del mundo real (tangible o
intangible) y que es de interés para un usuario o para un grupo de usuarios.

Esto resolvió los dos problemas que tenían: en primer lugar ya no era
necesario que el diseñador del programa fuese experto en informática, simulación e
ingeniería; se precisaba solo ser un buen informático, pero el diseño se limitaba a
reproducir informáticamente el diseño físico proporcionado por el ingeniero
mecánico.
En segundo lugar las modificaciones del programa también quedaban
resueltas; pues si se sustituía una pieza solo habría que cambiar la pieza
informática homónima y cambiarle sus atributos, recompilar todo el sistema de
2 · Fundamentos de programación

nuevo y ya lo teníamos en marcha; así los diferentes cambios de prototipos


también se podían llevar a cabo con celeridad.
Pero además se encontraba una nueva ventaja añadida, ya que para
diseñar un nuevo prototipo, no era necesario partir desde cero, sino que muchos
módulos podían aprovecharse de diseños anteriores, pues venían a ser la misma
arandela, tornillo, turbocompresor, etc, utilizado en un nuevo diseño pero con sus
mismas propiedades anteriores.
Esta capacidad de aprovechar módulos preexistentes se denomina
reutilización o reusabilidad.
El primer lenguaje específico para simulación a objetos se desarrolló en
1967: el Simula67, hoy día apenas se utiliza, pero su influencia en otros programas
actuales como C++ o Java es más que patente.

2. EVOLUCIÓN DE LA ORIENTACIÓN A OBJETOS EN LOS LENGUAJES DE


PROGRAMACIÓN

A principios de los año70, Alan Kay, un estudiante de doctorado de la


Universidad de UTA, propuso en su tesis un nuevo tipo de ordenador que
denominado Dynaborientación a objetosk pensado para que lo utilizaran los niños.
Por ello era necesario que el texto fuera mínimo y estaba basado en elementos
gráficos (Aquí aparece por vez primera el concepto de interfaz gráfica de usuario).
Como los lenguajes de diseño existentes en la época no le eran útiles para
conseguir el propósito que buscado creó el Smalltalk, cuyo hito más importante
fue el de establecer el concepto de herencia. Si existe ya un objeto definido y
advertimos la necesidad de definir otro objeto muy parecido aunque con
características propias; en lugar de duplicar y añadir las nuevas características,
definimos un objeto que “hereda” las características comunes del objeto ya
existente y, a continuación, indicamos sus características propias (la herencia es
otro de los conceptos que permite la reutilización).
El lenguaje C++ fue un paso más adelante (el primer + se refiere a las
mejores hechas en C como lenguaje básico y el segundo + a la incorporación de la
orientación a objetos ), e incluyó herencia múltiple (posibilidad de heredar
características de más de un objeto), declaración de clases parametrizadas o
genéricas y clases diferidas o virtuales.

Otros lenguajes de programación también incluyeron la orientación a


objetos como Pascal, Cobol, Fortran, Eiffel (uno de los más completos y más
elegantemente definidos), Java, etc.
Estos dos útimos lenguajes añadieron la programación por contrato, es
decir, se entiende la relación entre dos objetos como si se estableciese un contrato,
donde cada una de las partes tiene unos derechos y unas obligaciones, así se
garantiza formalmente el comportamiento del sistema o, al menos, la detección
controlada de cualquier incumplimiento.

Además la orientación a objetos ha influenciado el diseño de elementos


como las bases de datos (cuyo modelo relacional no era suficiente para cubrir todas
las expectativas que se necesitaban fuera del campo de la gestión).
Los sistemas operativos y las interfaces gráficas de usuario son un punto
aparte. El nacimiento de la orientación a objetos parte como necesidad para crear
una interfaz amigable para el PC Dynaborientación a objetosk de utilización entre
niños; pero Apple que compró los derechos que se definieron para este ordenador
en su momento, como las ventanas, barras de desplazamiento, etc, y
posteriormente Microsoft, que a su vez le compró los diseños para Windows,
olvidaron totalmente los aspectos orientados a objetos y ofrecieron solo una
interfaz de programa de aplicación (API), que hoy día y lentamente se están
encargando de corregir.

Por último, no acabaríamos este módulo sin recordar un par de ejemplos de


máxima utilización de la orientación a objetos como son el programa Autocad (el
Fundamentos de programación · 3

programa de diseño gráfico más utilizado por arquitectos e ingenieros de todo el


mundo), y el nuevo lenguaje de programación de páginas web para internet XML,
totalmente orientado a objetos y que se espera que en pocos años, desbanque al
ya desfasado HTML.

3. LAS NUEVAS INDUSTRIAS DE LA ORIENTACIÓN A OBJETOS

Tres Nuevas industrias son las fundamentales:


 Librerías de clases u objetos: Son en informática, el equivalente de la
industria de componentes para el sector del automóvil. Son un conjunto de
clases agrupadas según funcionalidades.
 Componentes: Son un apequeña arquitectura de objetos que tiene unos
servicios predefinidos pro el fabricante que pueden adaptarse mediante
parámetros y que se han pensado para conectarse entre sí o con otros
componentes.
 Objetos distribuidos: Son objetos que no se encuentran en la máquina en
la que se ejecuta el programa, pero que pueden ser “invocados” a través
de una red de comunicaciones para participar en la ejecución del mismo.
4 · Fundamentos de programación

TEMA 2 QUÉ ES LA ORIENTACIÓN A OBJETOS

La orientación a objetos: un modo de pensar

La orientación a objetos consiste en organizar y agrupar los componentes


normales de cualquier sistema sotware (atributos + operaciones), haciendo énfasis
no en los procesos que hay que realizar sobre los datos, sino en los objetos que
participan, agrupando y encapsulando en su módulo genérico (la clase) los
atributos y operaciones del objeto y especificando de forma clara y precisa la
interfaz de ese módulo.

La diferencia entre la programación clásica y la orientación a


objetos está bien clara. En la programación clásica, la idea de software tiene como
objetivo primordial alcanzar una funcionalidad o conjunto de funciones concretas;
por ejemplo si nos piden un programa de gestión comercial, seguramente todos
nuestros esfuerzos y preocupaciones se centrarán en crear gestión de facturas, si a
posteriori nos piden la elaboración de estadísticas sobre beneficios, seguramente la
adaptación del código será complicada pues no era lo que esperábamos hacer
inicialmente.
En cambio, con la orientación a objetos realizamos la construcción de un
modelo informático de un sistema real; de esta forma garantizamos que el sistema
será flexible y no se verá condicionado por ninguna funcionalidad concreta inicial.
Por ello la orientación a objetos es mucho más flexible a los cambios que el sistema
tradicional de programación.+
Fundamentos de programación · 5

La ventaja fundamental de la orientación a objetos es la reutilización. Se


define como la posibilidad de volver a utilizar un fragmento de código en futuras
aplicaciones o programas. Existen ocasiones en las que podemos reciclar hasta un
80% de los componentes creados previamente. De hecho, la reutilización es el
factor principal por el que toda la industria informática ha apostado por trabajar
con la orientación a objetos. Reutilizar ayuda a aumentar la productividad y
aumentar posibilita un incremento en los beneficios o poder introducir productos
más deprisa en el mercado y ganar a la competencia. Sin embargo, no debemos
pensar que reutilizar sea fácil, o no tenga costes asociados. Para poder reusar se
debe haber diseñado pensando en la reutilización y eso no es nada fácil.
Existen dos grandes conceptos que han incluido de forma fundamental en
el paradigma de la orientación a objetos , y son: la programación modular y los
TAD (Tipos Abstractos de Datos):

La programación modular se entronca de forma opuesta con el diseño


descendente que habíamos estudiado en asignaturas anteriores. Dividiamos,
recordemos, programas grandes en otros más pequeños y simples de resolver; esto
es valido para problemas sencillos, pero antes programas de gran envergadura se
hace necesaria la programación modular. Éste se propone desarrollar los programas
en dos etapas:
 Diseño modular: Se parte el problema en subproblemas
independientes denominados módulos, a los que se les encomienda
una misión determinada en el funcionamiento del sistema. Una vez
realizado cada módulo, se aplica el diseño descendente a cada uno de
ellos.
 Implementación de cada módulo.

La descomposición en módulos debe superar ciertas propiedades:


 Cada módulo tiene su propia especificación.
 Podemos utilizar otros módulos preexistentes o utilizar los actuales para
modelos futuros  reutilización.
 La implementación de un módulo se puede cambiar, sin que esto afecte
al resto de módulos
 Cada módulo encapsula sus propios detalles de implementación,
haciéndose invisible al resto de los módulos.

Los Tipos Abstractos de Datos son el otro referente fundamental de la


orientación a objetos. Un TAD es un tipo de datos equiparable a los tipos
predefinidos de los lenguajes de programación, pero que define el programador
para satisfacer alguna necesidad de información detectada durante el proceso de
diseño modular. Su definición es concreta en una especificación que,
posteriormente, debe implementar.
El proceso de definición de un TAD tiene dos pasos:
1. Especificación: Se establece el rango de valores del TAD y se
especifican todas las operaciones de las que dispondrá; se
realizará mediante pre y postcondiciones o bien mediante
ecuaciones que describen el comportamiento de las operaciones.
2. Implementación: que se compone de tres partes:
a. Representación del tipo: se debe escoger un tipo u otro de
estructura de datos.
b. Implementación de las operaciones: cada operación se
implementará mediante un algoritmo y el TAD responderá
según ello.
c. Aparato formal de vinculación entre la implementación
y especificación.

El TAD puede reutilizarse a traves de:


 Genericidad: modificamos el TAD que ya tenemos, añadiendo
propiedades e información (la desventaja es que el TAD puede
aumentar muchísimo y tener una representación demasiado grande).
6 · Fundamentos de programación

 Enriquecimiento: Se crea un TAD nuevo con nuevas propiedades que


utiliza TAD preexistentes: lo negativo es que suele ser más ineficientes
que los definidos por genericidad.

La reutilización realmente se obtiene por la herencia, un mecanismo


mucho más potente que la genericidad o el enriquecimiento.

Bases de la orientación a objetos

La orientación a objetos se basa en la integración de 6 conceptos:


encapsulación, ocultación de la información, abstracción, clasificación, herencia y
polimorfismo, que es necesario comprender y seguir de modo absolutamente
riguroso. No seguirlos sistemáticamente, omitirlos puntualmente por prisa u otras
razones hace perder todo el valor y los beneficios que nos aporta la orientación a
objetos.
Encapsulación: “Decide qué módulos quieres; parte del programa de
modo que los datos estén escondidos dentro de los diferentes módulos”. Con este
paso, lo que se pretende decir es que cuando nos enfrentamos al desarrollo de un
programa complejo, lo primero que debemos hacer es dividirlo en partes más
pequeñas. Cada una de estas partes pasa a ser un módulo; por ello un módulo es
la encapsulación de estructuras de datos u operaciones sobre esa estructura de
datos.
Los módulos encapsulados se comunicarán entre ellos para acceder a
información que necesitamos y está en otro módulo o por la necesidad de realizar
una operación entre ellos.
La experiencia nos ha enseñado que el mejor modo de implementar estos
módulos es esconder la estructura interna que tiene cada uno de ellos y ofrecer
nada más que una serie de operaciones claramente definidas a través de un
interfaz; esto se denomina ocultación de información; consiste, por tanto, en
esconder del exterior la estructura física de los datos y la implementación de las
operación de un TAD, dejando ver solo una especificación de las operaciones
aplicables y sus parámetros. Hay ciertos lenguajes de programación que pueden
obligar a la ocultación pero no obligan determinantemente, sino que depende del
programador forzarla o no (C++, Delphi) y otros lenguajes más orientación a
objetos obligan de manera efectiva (Eiffel).

La abstracción es otro concepto importante, “Decide qué tipos de datos


quieres; provéelos de un conjunto completo de operaciones para cada tipo”. Para
cada tipo de objeto definiremos una clasa y como usarla.

La clasificación es un concepto relacionado con el concepto de


abstracción. La clasificación propone agrupar objetos del mundo real que
pertenecen a un mismo tipo de entidad (o representaciones informáticas) en clases
de objetos. Es decir, que un TAD no represente un único objeto, sino todo un
conjunto de objetos o una clase de ellos.
La abstracción y la clasificación están íntimamente relacionados. Por
ejemplo, hemos realizado ya varios programas que requieren un contador,
normalmente con un contador lo que hacemos es ponerlo a cero, incrementarlo en
uno y compararlo con algún límite (en cada paso del bucle). Con esto hemos
creado una abstracción del contador. Ahora este contador lo abstraemos como
entero que solo ofrezca las operaciones inicializar, incrementar y comparar; así no
lo podremos usar en otras operaciones típicas de enteros como multiplicación o
división. Así, trabajando con los tipos adecuados y definiendo bien sus límites, los
programas son más seguros.

El quinto concepto fundamental es la herencia: “Decide qué clases


quieres;… encuentra las partes comunes y hacedlas explícitas mediante la
herencia”. Heredar de una clase (herencia simple) quiere decir definir una nueva
clase que tiene todos los atributos y todas las operaciones de la clase de la que
Fundamentos de programación · 7

hereda aunque se pueden introducir ciertos cambios como serían nuevos atributos
y operaciones. Esto nos ofrece ventajes como la reutilización, posibilidad de
ampliación, un bajo coste de mantenimiento y una forma de relacionar clases de un
modo que sea sensato desde un punto de vista semántico.
La herencia también se puede ver como forma de compartir código, de
forma que cuando se utiliza la herencia para definir una nueva clase sólo se debe
añadir aquello que sea diferente. Es por eso que a la programación con herencia
también se le llame programación por diferencias.
Otro aspecto de la herencia es la herencia múltiple, consistente en definir
por herencia una clase a partir de dos o más superclases

Por último, el polimorfismo se puede definir como la habilidad que tienen


las abstracciones para compartir propiedades. También podemos definirlo como el
hecho de que un nombre puede denotar objetos de clases diferentes pero
relacionadas por una clase base común. Normalmente un mismo operador, sobre
diferentes clases actúa de manera distinta (por ejemplo el + en una cadena de
caracteres puede combinarlas y en una cadena de enteros puede sumarlos
matemáticamente). Esta sobrecarga de operadores hace que podamos ampliar el
número de clases de una aplicación sin necesidad de introducir modificaciones a los
algoritmos principales.

Beneficios, costes y peligros

La orientación a objetos aunque no


es la solución definitiva, ofrece muchos
beneficios respecto de las tecnologías
previas. A pesar de esto, se debe tener muy
presente que estos beneficios tienen un
coste y su rentabilidad no es inmediata.

Como beneficios obtenemos:

La reutilización aumenta la
productividad en el desarrollo del software.
En el diseño clásico podíamos reutilizar de
forma muy limitada: determinadas funciones
o subaplicaciones que, a menudo requerían
de otras aplicaciones del programa o no
funcionaban exactamente como queríamos.
En la orientación a objetos podemos
reutilizar mucho más porque las piezas son
más pequeñas; si además estas piezas se han diseñado pensando en su
reutilización posterior, llegamos a niveles muy significativos de reaprovechamiento,
hecho que favorece un incremento de la productividad.

También la ocultación de la información y la encapsulación aplicadas


sistemáticamente nos ofrecen muchos beneficios. EN primer lugar facilitan la
localización de errores. Por la encapsulación sabemos que cada funcionalidad se
lleva a cabo solo en un lugar y por la ocultación garantizamos que el contenido solo
puede ser modificado internamente. Creada una clase y comprobado su
funcionamiento no hay que volver a comprobarlo, por ello los errores solo se darán
en las clases que no han superado todavía esta prueba.
También facilitan el mantenimiento de aplicaciones, pues podemos
modificar todo lo que queramos de una clase que, mientras no modifiquemos su
interfaz, no afectará a ninguna otra clase (justo lo contrario a lo que ocurre en una
programación clásica, donde no sabemos que alcance podrá tener un mínimo
cambio que realicemos).
Además se facilita la portabilidad de aplicaciones.
8 · Fundamentos de programación

La transición transparente es otra ventaja añadida y se refiere a la


posibilidad de avanzar en el desarrollo a través de todas las etapas (especificación,
análisis, diseño y programación) mediante una sola notación o representación del
problema. En la orientación a objetos el único cambio entre etapas es la
incorporación de más clases o una descripción más detallada de estas.
Evidentemente cada vez que se deba reescribir la descripción del sistema se
pueden introducir errores / divergencias o interpretaciones diferentes. Por lo tanto,
no tener que reescribir incrementa la fiabilidad del sistema.
Tanto es así, que es normal compilar el código generado automáticamente
a partir del análisis o el diseño aunque no se haya llegado a la etapa de
programación, pues si en el análisis describimos una clase totalmente, no hay
ninguna razón por la que se deba retrasar la compilación y prueba de aquella clase
hasta la fase de programación del producto.

Además la orientación a objetos es un modelo mejor porque permite


describir de manera “natural” la realidad que deseamos representar en nuestro
sistema software.
Los sistemas orientados a objetos permiten garantizas más la corrección
gracias a la definición de aserciones y reutilización. Al incluir dentro de los
objetos aserciones que garantizan el correcto funcionamiento y utilización, se
garantiza la calidad del sistema, su integridad y consistencia. La segunda razón es
la reutilización. Una de las características esenciales que debe tener toda librería de
componentes reutilizables es que las clases que están catalogadas sean correctas.
Es decir, que han pasado por los controles necesarios que validan su
funcionamiento. Por lo tanto, cuantos más componentes reutilicemos de una
librería válida menos errores podemos tener en nuestro sistema.

Eso sí, todos estos beneficios aportan algunos costes al sistema:


 Herramientas: La orientación a objetos obligará a adquirir alguna
herramienta CASE además de nuevos compiladores. Aunque es posible
trabajar sin estas herramientas, resulta bastante difícil y se pueden
cometer errores fácilmente y duplicar trabajo innecesario. Encontrar una
clase dentro de una librería reutilizable (y por tanto válida, con el esfuerzo
que ello supone) sin un sistema de catalogación y recuperación automática,
es muy difícil.
 Formación: Es indispensable para el reciclaje profesional, debe ser
individual y, por ello, supone un coste importante. La orientación a objetos
profesionaliza la ingeniería informática, con los beneficios, pero también
costes que ello supone.
 Reutilización: Suele pasar que si nos lanzamos a diseñar librerías
reutilizables nada más empezar, en las primeras aplicaciones, seguro que al
poco tiempo nos encontramos con que no son tan reutilizables como
esperábamos. La reutilización requiere un tiempo de, al menos, dos años
de programación para comenzar a ser efectiva.

Los peligros de la orientación a objetos se basan sobre todo en el punto


anterior de reutilización expresado ya. Es muy importante el hecho de comprobar la
corrección y la calidad de las clases antes de considerarlas “cerradas” o
“terminadas”. La “presunción de corrección” en las clases de una librería es muy
importante para la reutilización y la localización de errores.
Fundamentos de programación · 9

TEMA 3 OBJETOS Y CLASES

Una clase es una representación informática de


cualquier entidad del mundo real que tenga sentido o
interés para nosotros. En el paradigma de la orientación
a objetos una clase se representa como un
encapsulamiento de atributos y operaciones o servicios
sobre estos atributos. Puede representar una clase
cosas tangibles como un cliente o un artículo, o
intangible como una ventana de windows o una
estructura de datos.

Alumno
Nombre
DNI
Dirección
Población
Fecha_Nacimiento
Curso
ObtenerNombre()
EstablecerNombre()
ObtenerDNI()
EstablecerDNI()

En el gráfico superior encontramos una


representación gráfica de una clase en UML (Unified
Modeling Language), el lenguaje estándar para el
análisis y diseño ORIENTACIÓN A OBJETOS. La primera
parte del rectángulo contiene el nombre, debe ser
siempre un sustantivo en singular (a veces puede incluir
una segunda palabra que lo califique mejor). El nombre
es fundamental pues es la base de la reutilización (para
poder buscar una clase dentro de una librería de miles
de clases la única pista que tenemos es el nombre) y el polimorfismo (podemos
definir procedimientos similares para clases con el mismo nombre).
En segundo lugar, tenemos los atributos, estos atributos no son todos los
que podría tener la clase, quiero decir, un alumno en el mundo real es una
persona, con gustos aficiones, etc, pero informáticamente hablando hacemos una
abstracción de esa realidad con los datos que únicamente nos interesan en nuestro
sistema de gestión.

Cada uno de los alumnos es lo que definiremos como objetos (realmente


debería definirse como orientación a clases y no a objetos); y creamos esos objetos
desde las clases a través de la instanciación.
En el tercer recuadro tenemos los atributos de la clase Alumno; en
cualquier caso, requerimos como mínimo una operación de lectura para poder
acceder al valor de cada atributo y una de escritura para poder modificar el valor
de cada dato. Por ejemplo para obtener el nombre escribiríamos String n =
X.ObtenerNombre(); y para cambiar o rellenar el nombre:
X.EstablecerNombre(“Juan”);.

Para cada atributo, lo iniciaremos con public si queramos que pueda ser
visto por cualquiera o como private si queremos ocultarlo los atributos desde el
exterior.

INTRODUCCIÓN AL UML

Por tanto, UML es un lenguaje para describir modelos. Básicamente,


un modelo es una simplificación de la realidad que construimos para comprender
mejor el sistema que queremos desarrollar. Un modelo proporciona los “planos” de
10 · Fundamentos de programación

un sistema, incluyendo tanto los que ofrecen una visión global del sistema como los
más detallados de alguna de sus partes. Para comprender el objetivo del modelado
con UML, es muy útil compararlo con otras áreas de ingeniería, como es la
construcción de edificios o automóviles, con sus diferentes planos y vistas; o
incluso con la industria cinematográfica, donde la técnica del storyboarding
(representación de las secuencias de un película con viñetas dibujadas a mano)
constituye un modelado del producto1.
La especificación, visualización, construcción y documentación de cualquier
sistema software requiere que el sistema pueda ser estudiado desde diferentes
puntos de vista, ya que un usuario final necesita una visión diferente del sistema de
la que necesita un analista o un programador. UML incorpora toda una serie de
diagramas y notaciones gráficas y textuales destinadas a mostrar el sistema desde
las diferentes perspectivas, que pueden utilizarse en las diferentes fases del ciclo de
desarrollo del software.
Debemos tener claro también que un diagrama de clases UML (esto es
aplicable a cualquier tipo de diagrama UML) es tan sólo una vista del modelo
estático del sistema en cuestión, esto quiere decir que una misma clase puede
aparecer en varios diagramas y que podemos crear más de un diagrama, bien si es
el número de clases es elevado o bien si queremos mostrar dos o más partes del
sistema que tienen poco que ver en diagramas separados.

Clases y Objetos

Una clase es una descripción de un conjunto de objetos que comparten la


misma estructura y semántica y que presentan el mismo comportamiento y
relaciones. En UML se presentan mediante un rectángulo con tres compartimentos.
En el primero de estos compartimentos se indica cual es el nombre de la clase, en
el segundo se indican cuáles son las propiedades de la clase (atributos en
terminología UML), mientras que en el último se indica el comportamiento de la
clase.

El nombre de una clase debería ser un sustantivo en singular y debería


comenzar con mayúsculas. El nombre de una clase debe de estar de acuerdo a su
semántica o intención, en otras palabras, debe ser significativo a la vez que simple.
De no ser así, se dificultará la posible reutilización posterior de esa clase.
Fundamentos de programación · 11

Las propiedades de la clase se representan mediante una lista de


atributos que figuran en el segundo compartimento, describiremos dentro de los
atributos:
 Nombre: También debe ser significativo, pero suele comenzar en
minúscula.
 Tipo: Puede ser cualquier clase o uno de los tipos de datos básicos (queda
separado del nombre por :)
 Visibilidad: hasta que punto las operaciones de otras clases pueden acceder
al atributo; puede ser público +,(puede ser accedido por otras clases),
protegido #,(accedido solo por clases descendientes) o privado -(no puede
ser accedido y es lo que suele ser, recordemos que Java esconde en la
interfaz los atributos y solo muestra operaciones).

Por ejemplo en la cuenta bancaria encontramos –saldo:float. Significa que es


un número real, que se llama saldo y que es de visibilidad privada; o lo que es lo
mismo private float saldo.

Finalmente, el comportamiento de la clase, queda representado por una lista de


operaciones, en las que es necesario especificar su signatura, que tiene la siguiente
sintaxis:
visibilidad nombreMetodo (Parámetros) [:tipo de retorno]
+getSaldo(): float
public float getSaldo()

Implementación

La implementación de una clase sin relaciones en Java es un proceso


bastante directo; de hecho, prácticamente todas las herramientas de modelado
UML proporcionan la opción de generar el código automáticamente a partir del
diagrama de clases, dejando al programador la tarea de escribir el cuerpo (código)
de los métodos Java que resultan de las operaciones.
Así quedaría la implementación en java del ejemplo CuentasBancarias:

/**
* La clase CuentaBancaria representa cuentas bancarias en las
* que se mantiene un saldo.
* @author Miguel Angel Sicilia
*/
public class CuentaBancaria {
/** Número de cuenta*/
private String numero;
/** Saldo en euros de la cuenta*/
protected float saldo;
/**
* Constructor de una cuenta bancaria.
* @param numero numero identificativo de cuenta
* @param saldoInicial saldo inicial de la cuenta
*/
public CuentaBancaria(String numero, float saldoInicial){
this.numero = numero;
saldo = saldoInicial;
}
/**
* Getter para el saldo de la cuenta.
*/
public float getSaldo() {
return saldo;
}
/**
* Getter para el numero de la cuenta.
12 · Fundamentos de programación

*/
public String getNumero(){
Introducción al Lenguaje De modelado Unificado.
return numero;
}
/**
* Aumenta el saldo en la cantidad indicada.
* @param cantidad la cantidad a ingresar
*/
public void ingresar(float cantidad){
saldo += cantidad;
}
/**
* Disminuye el saldo en la cantidad indicada.
* @param cantidad la cantidad a retirar
*/
public void retirar(float cantidad)
throws SaldoResultanteNoPermitido{
saldo -= cantidad;
}
}
Nótese que hemos definido el atributo saldo protected al darnos cuenta que posibles futuras
subclases
podrían requerir su manipulación. También hemos declarado que el método retirar puede
lanzar una
excepción definida por nosotros. Aunque en el código de esta clase no lo hace, posiblemente
lo hará el
código de subclases que por ejemplo, no permitan que una cuenta de un tipo determinado
quede en
“números rojos”.

Relaciones entre clases

Una relación entre clases se puede definir como una conexión entre dos
o más clases. En orientación a objetos, las principales relaciones que se pueden
establecer entre clases son las de generalización (o herencia), asociación,
agregación y dependencia (o uso).
Las asociaciones representan relaciones estructurales entre clases. En
UML, se define una asociación como una relación que describe un conjunto de
enlaces, donde cada enlace define una interconexión semántica entre instancias de
las clases que participan en la asociación. Se dice que las instancias de una de las
clases “conocen” a las instancias de la otra clase a las que están enlazadas.

En UML, las asociaciones binarias se representan mediante una línea

continua que conectan dos clases (aunque puede darse el caso particular en que
Fundamentos de programación · 13

las dos clases involucradas en la asociación binaria sean la misma. En este caso,
hablamos de asociaciones reflexivas).

A la representación de las asociaciones se le pueden añadir ciertos


elementos que aportan información adicional, como la direccionalidad, los
nombres de rol o la cardinalidad, que veremos a continuación. Una asociación
binaria representa una interconexión entre las instancias (u objetos) de dos clases
A y B. Esta interconexión puede ser o no bidireccional. En caso que la interconexión
sea bidireccional, (opción por defecto) será posible navegar desde las instancias de
la clase A a las instancias de la clase B, y a la inversa, es decir, desde las instancias
de la clase B podremos acceder a las instancias de la clase A. Si decidimos que la
interconexión debe ser unidireccional, entonces será necesario añadir una flecha
abierta encima de la línea de asociación; esta flecha indica el sentido de la
navegación.
Las asociaciones pueden tener nombre. Dado que por defecto las
asociaciones son bidireccionales, se pueden especificar dos nombres en la
asociación. En UML, estos nombres son los roles que juegan las clases en la
asociación.
Dado que una asociación binaria representa una interconexión de instancias
de dos clases, es necesario indicar cuántos objetos de cada clase pueden estar
involucrados en dicha interconexión. En otras palabras, es necesario indicar la
cardinalidad (o multiplicidad) de la asociación. Podemos indicar el valor máximo y
mínimo de esta cardinalidad. Dadas dos clases A y B, la cardinalidad indica con
cuántas instancias de B se puede relacionar cada instancia de A (valor mínimo y
máximo). Los casos más generales decardinalidad son los siguientes:
• Cardinalidad “uno a uno”: en este caso una instancia de la clase A se
relaciona con una única instancia de la clase B, y cada instancia de la clase
B se relaciona con una única instancia de la clase A.
• Cardinalidad “uno a muchos”: en este caso una instancia de la clase A se
relaciona con varias instancias de la clase B, y cada instancia de la clase B
se relaciona con una única instancia de la clase A.
• Cardinalidad “muchos a muchos”: en este caso una instancia de la clase A
se relaciona con varias instancias de la clase B, y cada instancia de la clase
B se relaciona con varias instancias dela clase A.
14 · Fundamentos de programación

A continuación se muestra una tabla resumen de la posible multiplicidad de


los cardinales y sus notaciones:

Las asociaciones reflexivas son un tipo de asociación binaria en el cual la clase


de origen y destino de la asociación es la misma. Como ejemplo típico, trataremos
la organización de una empresa, definida mediante la relación “es responsable de”
entre instancias de la clase Empleado.

En el ejemplo anterior hemos dibujado una asociación bidireccional donde


un empleado puede tener cero, uno o muchos empleados bajo su responsabilidad,
mientras que todos los empleados tienen un único responsable (excepto el Director
General, que no tiene ningún responsable). Es importante “visualizar” la estructura
de instancias que se desprenden de este diagrama. Esta estructura, en nuestro
caso particular, representará un árbol de instancias, por la restricción de que una
persona tiene un solo responsable como mucho.
Fundamentos de programación · 15

El concepto de Clase de la Asociación: En una asociación entre dos clases, es


posible que la propia asociación tenga atributos. La informaciónque tienen los
valores de estos atributos no tiene sentido propio excepto en el concepto del
enlace. Un ejemplo típico es el modelado de un pedido de múltiples productos. La
cantidad pedida de cada producto no pertenece ni al pedido en sí (a diferencia, por
ejemplo, de la fecha del pedido) ni al producto, sino a laIntroducción al Lenguaje
De modelado Unificado. relación del pedido con cada uno de los productos
solicitados en él. En UML, este ejemplo se modelaría como una clase de la
asociación, que se conecta con una línea discontinua a la asociación de la cual
depende.
Los lenguajes de programación no proporcionan un mecanismo
directo para implementar estas asociaciones, sino que se implementan
creando una tercera clase que posee dos asociaciones normales, como se
muestra en la siguiente figura.

Una asociación n-aria constituye una relación que se establece


entre tres o más clases. Un ejemplo típico es el de la cuenta de los goles
marcados por jugadores de los distintos equipos en el campeonato de
Liga. En este caso, hay que enlazar a un jugador con un equipo en una
temporada, ya que en diferentes temporadas puede estar en diferentes
equipos (incluso quizá en la misma). Estas relaciones se representan en
UML mediante un rombo, y pueden tener una clase de la asociación con
la información que depende de la existencia del propio enlace, en
nuestro caso, una clase Registro con los datos del jugador en el equipo
y temporada indicado en el enlace (ver figura en la página siguiente).
Dado un jugador concreto que juega en un equipo concreto
tendrá varios registros de goles en cada temporada. Dada una
temporada concreta y un equipo concreto, habrá un registro de goles
para cada jugador del equipo. Finalmente, dada una temporada y
jugador concreto, puede meter goles en diversos equipos (asumimos
que un jugador dentro de una misma temporada puede cambiar de
equipo).

Las relaciones de agregación y composición son tipos especiales de


asociación. En las asociaciones binarias, se asume que las instancias de
ambas clases son independientes, en el sentido de que no dependen de
la existencia de las instancias de la otra clase. Sin embargo, las relaciones de
agregación asumen una subordinación conceptual del tipo “todo/parte”, o bien
“tiene un”.
A efectos de implementación en Java, las técnicas son las mismas, con la
salvedad de que no se pueden crean ciclos de enlaces, es decir, una “parte” no
puede estar agregada en sí misma, ni directa ni indirectamente. Se puede eliminar
la instancia que representa al “todo” y seguir existiendo las instancias que eran sus
“partes”.
Las agregaciones se representan mediante un rombo en la parte del
objeto agregado, para distinguirlo de los objetos “más pequeños” que contiene. Por
ejemplo, puede considerarse que un Grupo de Trabajo es un agregado de sus
miembros, lo cual no implica que cuando el grupo se disuelva, las instancias que
representan a sus miembros deban eliminarse también. Nótese que las estructuras
de objetos resultantes son en general un grafo dirigido acíclico.

La composición es un tipo de agregación más específico en el cual se


requiere que una instancia de la clase que representa a las partes esté asociada
como mucho con una de las instancias que representan el “todo” (nótese que no
puede haber multiplicidad “muchos” en el extremo del agregado). De esta manera,
el objeto compuesto gestiona sus partes en exclusiva. Por lo general, la estructura
16 · Fundamentos de programación

de objetos resultantes es siempre un árbol. Un ejemplo típico es un procesador de


textos que estructure los documentos en páginas, y éstas en párrafos. El
documento guardará referencias a sus páginas en exclusiva, es decir, no las cederá
a otros objetos, y deberá proveer mecanismos para la creación y el borrado de
páginas. Nótese que en Java el borrado solo requiere “perder” la referencia, ya que
si borramos una página de este modo, el recolector de basura la eliminará y a su
vez eliminará las instancias de los párrafos contenidos en la página, puesto que
ninguna otra instancia podía contener referencias a esos párrafos. Por ello, se dice
que los ciclos de vida de las “partes” están supeditados al ciclo de vida del “todo”.
Las composiciones implican la propagación a las “partes” de las invocaciones a las
operaciones sobre el “todo”. Siguiendo el ejemplo anterior, si pedimos que se copie
o imprima un documento, el documento hará copia o mandará imprimir a sus
páginas y a su vez, las páginas harán lo mismo con los párrafos que contienen.

Una dependencia entre clases denota una relación de uso entre las
mismas. Esto quiere decir que si la clase de la que se depende cambia, es posible
que se tengan que introducir cambios en la clase dependiente. Por ejemplo, cuando
un método de la clase A tiene como parámetro un objeto de la clase B, decimos
que A depende de B, usa sus servicios.
Por ejemplo, si tenemos una clase Impresora que tiene una operación
imprimir que toma como parámetro una instancia de la clase Documento, diremos
que Impresora depende de Documento. Nótese que las impresoras no mantienen
enlaces con los documentos que imprimen, sólo los procesan con la operación
correspondiente, pero después de cada llamada a imprimir, no “recuerdan” los
documentos que imprimieron. Se dice que este es el tipo de relación entre clases
“más débil”, y como recomendación general, sólo debería mostrarse en los
diagramas cuando aporten alguna información importante.

Imaginemos que la clase impresora invoca a las tres operaciones de la


instancia de Documento para imprimirlo. Pues bien, si por ejemplo, eliminamos la
operación “obtenerPie” (o cambiamos los parámetros o el tipo de retorno de alguna
de las operaciones de Documento), habrá que cambiar la implementación de
imprimir.

La generalización es una relación taxonómica entre una clase más


general (denominada superclase o clase padre) y una clase más específica
(denominada subclase o clase hija). Lo fundamental de esta relaciones es que
tienen que reflejar relaciones “es un tipo de”, es decir, las instancias de la subclase
son un tipo específico de instancias de la clase padre. De ello se deriva una
propiedad importante: los objetos de la clase hija pueden emplearse en cualquier
lugar en que se requiera una instancia de la clase padre, pero no a la inversa. La
subclase hereda las propiedades, el comportamiento y las relaciones de la
superclase, a la vez que puede añadir sus propias propiedades, relaciones y
comportamiento.
Nótese que la relación de herencia es transitiva “de abajo hacia arriba”, es
decir, si C es un tipo de B y B es un tipo de A, entonces C es un tipo de A. En UML
la relación de generalización se representa mediante una línea sólida que une
superclase y subclase. Además, esta línea sólida incorpora una flecha con punta
Fundamentos de programación · 17

triangular que queda localizada al lado de la superclase. Un conjunto de clases


relacionadas entre si mediante relaciones de generalización constituye una
jerarquía de herencia que puede ser un grafo o un árbol dependiendo,
respectivamente, de si existe o no herencia múltiple.

Ejemplo. Cuentas Bancarias: Relación de generalización


Vamos a extender la clase CuentaBancaria anterior para soportar dos
nuevos tipos de cuentas (una vez más, es un ejemplo no realista, sólo
con propósitos didácticos):
Con esta nueva extensión, podríamos considerar abstracta la clase
CuentaBancaria (si es que en nuestra entidad bancaria imaginaria
nuestra entidad bancaria sólo considera dos tipos de cuentas). En el
diagrama anterior hemos marcado la clase
CuentaBancaria como abstracta, lo que se visualiza e UML con el
nombre de la clase en cursiva (aunque no lo mostramos aquí, la
cursiva se utiliza también para diferenciar los métodos abstractos en
UML).
1. Cuentas a plazo fijo, en las cuales si se realiza una retirada de
dinero se produce una penalización sobre el importe retirado (por
simplicidad, no consideramos la fecha de vencimiento). En estas
cuentas, el saldo nunca puede ser negativo.
2. Cuentas con crédito limitado, en las cuales no se permite un nivel de
“números rojos” superior a uno especificado al abrir la cuenta.
18 · Fundamentos de programación

TEMA 4 HERENCIA Y RELACIONES ENTRE CLASES

1. HERENCIA

La herencia es la única tecnología propia de la orientación a objetos; todas


las otras: encapsulación Ocultación de la información, etc habían sido ideadas
anteriormente y apropiadas por la orientación a objetos. La herencia, en cambio,
nacida con la orientación a objetos, permite obtener niveles altos de reutilización y
un aumento de la fiabilidad de los sistemas software. También aumenta mucho la
fiabilidad, dado que cuando reutilizamos una clase para extenderla con la herencia,
partimos del hecho de que la clase anterior ya era correcta y estaba libre de
errores; así si en un programa reutilizamos el 90% significa que, de haber errores,
tiene que serlo en el 10% de nueva creación.

La herencia es la técnica que permite definir una nueva clase a partir de


otra clase existente a partir de la descripción de las diferencias que existen entre
ellas. Así la nueva clase se convierte en una subclase de la anterior y ésta, a su vez,
es la superclase de la nueva. Como normalmente es muy difícil encontrar justo la
clase que uno busca, es muy útil definir una nueva a partir de otra anterior y solo
aplicando cambios que se ajustan a nuestras necesidades. (herencia por
especialización). Por ejemplo para la clase alumno, podemos definir la subclase
alumnoBecario, y contendrá las mismas responsabilidades que el anterior pero
algunas particularidades diferentes, como puede ser el precio de la beca, por
ejemplo.
En otras ocasiones al fijarnos en el mundo natural nos interesa modelizar
distintas clases que tienen muchísimo en común pero también algunos detalles
diferentes entre ellas. Lo que hacemos es extraer todo aquello que es común,
generando una clase y dejar a cada una después solo aquello que les es específico,
esto se conoce como herencia por generalización. Este podría ser el caso de
encontrarnos con alumnos becarios y alumnos de doctorado; extraemos todo lo
Fundamentos de programación · 19

que les es común para crear la superclase alumno y posteriormente creamos las
subclases con las diferencias entre cada una de ellas.

En los dos ejemplos anteriores hemos tratado casos de herencia simple


en el que cada subclase hereda solo de un único padre, su superclase; en
contraposición a la herencia múltiple; Este sería el caso de definir el
alumnoBecario a partir de las superclases Alumno y Profesor; pero claro al hacerlo
así obtenemos una clase alumnoBecario que tiene dos nombres y dos servicios de
obtener y establecer nombre (uno por cada “padre”), esto es lo que se conoce
como herencia repetida; en algunos casos nos convendrá, por ejemplo si está como
alumno y profesor en la UOC debe tener dos nombres, pues es su “login” para
acceder al sistema y le reconozca en cada uno de estos dos papeles.

Las adaptaciones posibles en la herencia son 4:


 Añadir servicios: Consiste en añadir a la nueva subclase nuevos servicios
que antes no estaban en la superclase.
 Implementar servicios: Un servicio diferido es el que está asociado a
una clase, pero no se implementa desde la misma. Por ejemplo, para public
abstract int CalculoDescuento (int albaran); se denomina como “abstract”
porque es un servicio diferido, que se calcula de manera diferente para
cada cliente, así que no podemos generalizar un cálculo por porcentaje.
 Redefinir servicios: Consiste en volver a implementar un servicio
previamente definido en la superclase pero que no nos interesa que esté
definido de aquel modo y lo queremos redefinir de manera distinta para
esta subclase.
20 · Fundamentos de programación

 Ampliar servicios previamente definidos: Se extiende un servicio que


ya había sido definido previamente en la superclase.

El polimorfismo es la característica que permite que clases diferentes tengan


un mismo servicio pero implementado de forma distinta, ello se encuentra
relacionado con el enlace estático y el enlace dinámico.
Las trampas de la herencia son varias, dado que aunque es un mecanismo
muy potente, también puede ser muy peligroso. Encontramos dos grandes
trampas:
 Utilizar una subclase en lugar de una superclase
 Superclases poco generales: si especificamos DNI como parte de la
superclase Alumno; resulta que los extranjeros o los que tengan menos de
18 años no se verán identificados aquí y tendremos que recurrir a una
mayor generalización, para dar cabida a todos los tipos de Alumnos
distintos.

2. TIPOS DE CLASES

Existen tres tipos de clases: las abstractas, diferidas y genéricas. Todas son
inutilizables directamente, es decir, no permiten crear instancias, pero en cada caso
por razones diferentes:
Las clases abstractas no se pueden utilizar para crear instancias en
nuestra aplicación porque describe aspectos que tienen otras clases en nuestro
dominio. EN el ejemplo de las clases Alumno, AlumnoDoctorado y AlumnoBecario;
alumno es una clase abstracta en nuestro programa del que no vamos a crear
instancias si hacemos un programa de doctorado. En ese caso utilizaremos
AlumnoDoctorado y consecuentemente, también Alumno, ya que la primera es
descendente de esta, pero solo crearemos alumnos de Doctorado. Pero sin os piden
una aplicación demográfica clasificada en alumnos estudiantes, trabajadores o
jubilados, la clase Alumno sería perfectamente adecuada y, en el segundo caso, no
sería una clase abstracta.
Las clases diferidas son las que incorporan servicios diferidos y, por ello,
no permiten crear instancias.
Las clases genéricas o parametrizadas son aquellas que tienen algún
atributo sin implementar, cuando las queremos usar llamamos a la clase e
implementamos para nuestro caso específico ese atributo que le falta.

Las clases terminales son aquellas que no pueden tener subclases, pues
las hemos bloqueado precisamente para evitar cambios futuros con la herencia.

3. PROGRAMACIÓN POR CONTRARO

Bertrand Meyer, autor de Eiffel, creó la expresión programación por


contrato. En ella caben destacar las aserciones. Las aserciones son restricciones
(expresiones booleanas) de cumplimiento obligatorio que se pueden imponer en
cualquier operación o en todo el comportamiento de un objeto.
El concepto de contrato se refiere a que se debe entender la relación entre
dos objetos como si se estableciese un contrato en el que cada una de las partes
tiene unos derechos y unas obligaciones. Así se garantiza formalmente el
comportamiento del sistema o, por lo menos la detección controlada de cualquier
incumplimiento.
Existen tres tipos de aserciones:
 Las precondiciones: Son restricciones que se deben cumplir antes de
ejecutar un servicio. Implícitamente significa que quien pide la ejecución de
un servicio lo hace sólo cuando lo puede hacer y con unos valores de
parámetros conocidos. Éste tipo de aserción es el que más se utiliza,
porque en orientación a objetos se reutilizan objetos que muchas veces no
Fundamentos de programación · 21

se sabe cómo se han implementado y es fácil utilizar un objeto


incorrectamente.
 Las postcondiciones: Se comprueba al acabar la ejecución de un
servicio. Garantizan que el objeto que recibe el mensaje acaba en un
estado correcto. Se suelen utilizan sobre todo en la puesta a punto del
programa en la fase de diseño para detectar errores. Una vez que todo
funciona correctamente, normalmente se desactivan.
 Las invariantes: Son condiciones sobre el estado de los objetos que se
deben cumplir en todo momento; se suelen utilizar muy poco debido al alto
coste que tienen.

Cuando hay herencia y se han utilizado aserciones hay que tener en cuenta que
las precondiciones de la superclase se añaden a las subclases con or (se debe
cumplir la precondición de la clase o bien alguna de las superclases) por lo que será
igual o más débil que la original.
Las postcondiciones se añaden con and y será por lo tanto igual o más fuerte
que la original, pues debe cumplir la poscondición de la clase y también de las
superclases.
Cuando una aserción no se cumple se puede forzar la ejecución de una
excepción, es decir, un tratamiento de error; y se activa cuando una o varias de
las condiciones expresadas por una aserción no se cumplen. Tiene dos partes el
tratamiento de una excepción:
 La que provoca el error y lanza la excepción (throw).
 La que captura la excepción y la trata (match).

Si no se cumple la condición de la excepción (if) se genera una excepción; el


programa retrocede hasta el servicio que había llamado al método, mira el control
de la excepción por si lo tuviera definido; si el servicio puede tratar la excepción, la
trata; si no puede, el programa continúa retrocediendo hasta que encuentra un
servicio que controle la excepción o bien para el programa al no encontrar ninguna.

A modo de resumen
22 · Fundamentos de programación

TEMA 5 OTROS ASPECTOS DE LA ORIENTACIÓN A OBJETOS

No todos los lenguajes disponibles en el mercado incluyen todas las


características que se han presentado hasta ahora; exclusivamente el Eiffeel es el
único que las permite.
Los lenguajes orientación a objetos tienen un coste en recursos superior
al de los lenguajes procedimentales, ello se debe a:
 Al estar dividido en muchas llamadas entre métodos o servicios implica más
consumo de memoria y tiempo.
 Todas las clases deben tener servicios constructores de objetos y si no hay
recuperación de memoria automática, también se necesitan servicios
destructores de objetos para no copar toda la memoria (llamadas al garbage
collector). Aunque hay que decir que una de las razones del éxito de Java es
que con una sintaxis conocida como la de C++, ha conseguido eliminar la
gestión manual de la memoria y la utilización de punteros.
 Casi siempre debe crearse una tabla con las direcciones de memoria de los
servicios heredados para poder encontrar en tiempo de ejecución los servicios
que se necesita aplicar.

Además el lenguaje orientación a objetos es mezcla de compilador (se traduce


el código generado por el programador antes de la utilización como C++) e
intérprete (se traduce simultáneamente, según se vaya necesitando); lo cual lo
hace un poco más lente que el resto (recordemos que se corre en la Virtual Java
Machina).
Las metaclases son clases que definen clases, es decir, una metaclase es una
clase de clases; por lo tanto, las instancias de una metaclase con clases que a su
vez también pueden crear instancias.

La reutilización es una característica importante del software de calidad. Es


decir, un componente se debería diseñar e implementar de modo que pueda ser
reutilizado en futuras aplicaciones o programas. Existen dos grandes formas de
reutilización: los patrones y los frameworks.

Los patrones son descripciones de problemas cotidianos en la programación


dentro de un entorno de desarrollo de software que aporta las soluciones a dicho
problema: un patrón no está ligado a ningún dominio particular ni a ningún
lenguaje de programación, es sólo una solución de diseño. Suele haber dos grandes
tipos de patrones:
 Patrones arquitectónicos: Afectan a toda la arquitectura del sistema o a
una parte importante. Expresan un esquema de organización estructural
funcional para sistemas software y proporcionan un conjunto de
subsistemas predefinidos y describiendo sus relaciones.
 Patrones de diseño: Afectan a pequeñas partes de la arquitectura: dan un
esquema para refinar los subsistemas o partes d eun sistema software
dando soluciones a problemas generales de diseño.

Los frameworks son una colección de clases ya implementadas que


colaboran para proveer un conjunto de servicios dentro de un dominio particular.
Podríamos decir que son un conjunto de bloques prefabricados que sirven para la
construcción de software. Con estas utilidades, los diseñadores de software no
deberán comenzar desde cero cada vez que creen una nueva aplicación. Toda
aplicación se construirá a partir de una colección de objetos en la que tanto el
diseño como la implementación pueden reutilizarse.
Las ventajas que esto aporta es que como son prefabricados se reduce la
codificación y puesta a punto, además proporcionan una arquitectura y un alto nivel
de abstracción y son una buena base para la industria de software que crea
componentes. En lo negativo está la limitación de la flexibilidad, es decir, esta
restringido al uso que el programador le diseñó, es difícil aprender a fondo el
Fundamentos de programación · 23

funcionamiento de un framework específico y puede reducir el grado de creatividad


del usuario.

La productividad es otro aspecto que es necesario plantearse. Hasta ahora se


valoraba la productividad de un informático en función del número de líneas de
código que producía. Ahora esto y a no es suficiente. Hay que saber reutilizar y
acostumbrarse a reutilizar (con el consecuente esfuerzo que esto supone); es
necesario conocer los componentes y como se pueden ampliar ya que, raramente,
se podrán utilizar tal y como están.
24 · Fundamentos de programación

TEMA 1 PRÁCTICA INTRODUCCIÓN A JDK

Java es un lenguaje completamente orientado a objetos. Todo en Java,


excepto unos cuantos tipos básicos como los números, son objetos y clases.
Se caracteriza el lenguaje Java por:
 Simple: Java es fácil y claro. No hay necesidad de cabeceras de ficheros,
aritmética de punteros, estructuras, uniones….
 Orientado a objetos: La orientación a objetos en Java es comparable a
C++; la mayor diferencia es que Java no soporta la herencia múltiple.
 Distribuido: Java tiene una extensa librería de objetos a los que se puede
usar a través de los protocolos TCP/IP, FTp y http.
 Robusto: Tiene un modelo de gestión de memoria que evita que los datos
se corrompan.
 Seguro: Se utiliza un entorno de red distribuido en el que prima la
seguridad.
 Arquitectura neutra: Es multiplataforma, el código del compilador se
puede ejecutar en diferentes procesadores debido a la presencia de un
sistema interpretador en tiempo de ejecución de Java: La maquina virtual
de Java.
 Portable: Debido a lo anterior las librerías que forman parte del sistema
definen interfaces portables.
 Interpretado: El intérprete de Java puede ejecutar códigos binarios
directamente en cualquier máquina.
 Alto rendimiento.
 Multihilo.
 Dinámico.

Java es el primer lenguaje que ofrece a los programadores una serie de


funcionalidades a través de internet mucho más fácil de realizar que en otros
lenguajes. Java proporciona además, los applets, pequeños programas Java que se
encuentran insertos dentro de páginas web y que permiten obtener un mayor
dinamismo en estas páginas (animaciones, sonidos, etc). El navegador, al recibir de
la red una página web que tiene applets, activa la máquina virtual Java que tiene
en su interior y ejecuta los códigos byte de los applets.

Hay conceptos erróneos sobre Java que conviene aclarar:


 Java no es una extensión de HTML.
 Java es un lenguaje de programación con una sintaxis sencilla, pero con
una enorme librería difícil de gestionar.
 Java no es un entorno de programación, solo son herramientas de
programación.
 Java nunca se convertirá en un lenguaje universal para todas las
plataformas, dado que es un lenguaje independiente de las plataformas, y
portable además.

Debido a que java es independiente de la plataforma y tiene que ejecutar la


máquina virtual para ejecutar programas, no se hace un lenguaje indicado para
cuestiones de tiempo real pues a veces se ralentiza su velocidad; para evitar esto
se han desarrollado compiladores just-in-time y compiladores nativos. Los primeros
traducen el código byte justo antes de ejecutar para que sea más rápido; los
segundos compilar el código de forma dependiente a la plataforma, así se consigue
mayor rapidez pero, por desgracia, pierde la portabilidad del Java.

Java se ejecuta en una ventana Ms-Dos. Primero debemos realizar con


cualquier procesador de textos (Bloc de notas de windows, por ejemplo) el
programa que queremos compilar. Tras ello compilamos con jawac y el nombre del
fichero en cuestión y, por último lo ejecutamos con java y el nombre del fichero con
la extensión class que hemos obtenido del proceso anterior.
Fundamentos de programación · 25

Si queremos ejecutar, por ejemplo, en el explorador, debemos añadirle además


un archivo de extensión html.
Los manuales de referencia y demás aparecen en la página de Java.sun.com.
26 · Fundamentos de programación

TEMA 2 PRÁCTICA INTRODUCCIÓN AL LENGUAJE JAVA (I)

1. ELEMENTOS BÁSICOS

Encontramos:
 Comentarios: Se suelen añadir del mismo modo que en C++; el primer
tipo de comentario empieza con “/*” y acaba con “*/” y normalmente no se
pueden anidar. Otro tipo de comentario y que luego se utilizará para
generar la documentación en javadoc son los que empiezan por “/**” y
acabn con “*/”. Otro tipo de comentario es el que se extiende desde “//”
hasta el final de la linea de texto, no puede ocupar más.ç
 Sentencias: Es una acción ejecutable y puede ocupar una o más líneas,
las sentencias van separadas por el punto y coma.
 Bloques de código: Las sentencias se suelen agrupas en bloques para
que de forma sencilla, una sola pueda controlar la ejecución de muchas. Al
igual que en C los bloques de código se delimitan por las llaves “{“ y “}”.
 Palabras reservadas: Son las que tienen un significado especial para el
compilador Java y que, por tanto, no podemos usar, como int, new, bolean,
byte, etc.

2. TIPOS DE DATOS

Tenemos datos simples: numéricos, booleanos y caracteres y otros tipos de


datos complejos: objetos y matrices.
 Numéricos: Hay 6 tipos distintos:

Byte Entero con signo de tamaño desde -128 a 127


muy pequeño
Short Enter corto con signo Desde -32768 a 32767
Int Entero con signo Desde -214783648 hasta
+214783648
Long Entero largo con signo Desde -9223372036854775808
hasta el positivo
Float Número en coma flotante Decimales
Double Número con coma flotante Decimales con mayor precisión

 Booleanos: Puede tener el valor true o false. Las variables booleanas no


iniciadas se inician como false
 Carácter: Contienen un único carácter basado en el código Unicode.
 Cadena: (String): Es una secuencia de caracteres, basado en una de las
bibliotecas de Java.
 Matrices: O tabla de arrays es un grupo de variables del mismo tipo; es
un objeto que se gestiona por referencia. Para declarar una matriz hay que
usar corchetes y además podemos reinicializarlo con un valor concreto,
ejemplo: int[]dias_mes = {31. 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}.

3. EXPRESIONES Y CONTROL DE FLUJO

Un identificador es un nombre que se le da a una variable, clase o


función; pudiéndose elegir cualquier nombre que comience por una letra y no sea
una palabra reservada; por ejemplo: contador = 325. El literal es el valor concreto
de ese identificador, en este caso: 325, y además es del tipo int ya que otros tipos
requieren letras al final del número para identificarlos
Fundamentos de programación · 27

Las expresiones son combinaciones de variables, palabras reservadas y


símbolos que son evaluados a un valor en un determinado tipo de datos. El valor de
la asignación es el de la parte derecha de la sentencia; así: a = b = 15; asigna a
los valores a y b el valor de 15.
Los operadores pueden ser de varios tipos y los podemos ver en la página
siguiente. La preferencia entre operadores nos dice dentro de una expresión, el
orden en que se debe aplicar, así, por la preferencia que se puede ver en la tabla
de la página siguiente, la operación 13 + 7 * 4 – 15 / 2; el compilador de Java la
entenderá como: 13 + (7 * 4) – (15 / 2).

Operador Propósito Preferencia Asociatividad


++, -- Incremento / Decremento 1 Derecha
+, - Más unario / Menos unario 2 Derecha
* Multiplicación 4 Izquierda
/ División 4 Izquierda
Módulo (resto de la
% 4 Izquierda
división entera)
+, - Suma / Resta 5 Izquierda

<,>,>=,
Verifica magnitudes 7 Izquierda
<=
== Verifica la igualdad 8 Izquierda
¡= Verifica la desigualdad 8 Izquierda
Condicional: retorna uno
?: de los operandos en 14 Derecha
función de un tercero
¡ Negación 2 Derecha
&& AND Condicional 12 Izquierda
|| OR Condicional 13 Izquierda

& AND lógico 9 Izquierda


Λ XOR lógico 10 Izquierda
| OR lógico 11 Izquierda
~ Negación 2 Derecha
>>,<< Desplazamiento a la 6 Izquierda
izquierda / derecha
>>> Desplazamiento derecha 6 Izquierda
sin signo

Los operadores de conversión sirven para convertir un tipo de datos en


otro, para ello, se escribe entre paréntesis el nombre del tipo en que se quiere
convertir; ejemplo: i = (int) l

Normalmente, el conjunto de sentencias de un bloque se ejecutan una sola


vez unas detrás de otras, pero existen las sentencias de control de flujo que
sirven para alterar este orden inicial, hay varios tipos:
 Sentencias condicionales: if y switch.
 Sentencias de bucle: cuya forma general es for, aunque también existe
while y do-while.
 Sentencias de alteración del flujo: continue que bifurca el final de un bucle
justo después de la última línea de sentencias, las return se utilizan para
salir de un método o de un constructor y las sentencias break.
28 · Fundamentos de programación

4. APPLETS

Para utilizar un applet necesitamos:


 Las clases de applet que se obtienen con la compilación de los ficheros
fuente de las clases mediante el mismo compilador que se utiliza para el
resto de las aplicaciones: el javac.
 Una página HTML que incluya el applet

Un Applet siempre tiene que estar dentro de una página web, y la etiqueta que
lo contiene puede tener muchos parámetros, pero nos interesan sobre todo 3:
1. Code: Indica cual es el archivo que tiene el código del applet. Se debe
especificar aquí el archivo ya compilado con extensión class.
2. Width: Indica al navegador el ancho (en píxeles) del applet dentro de la
página web.
3. Height: Indica al navegador la altura (en píxeles) del applet dentro de la
página web.

5. EXCEPCIONES

En situaciones excepcionales, como cuando se obtiene un dato de entrada


no válido, en lugar de estropearse todo el programa, Java utiliza un modo de
capturar errores denominado gestión de excepciones; que puede hacer dos cosas:
o volver a un estado seguro y permitir al usuario ejecutar otros comandos o bien
guardar todo el trabajo y acabar el programa correctamente. Las excepciones
pueden ser de dos tipos:
 Heredan de RuntimeException: Como por ejemplo acceso a una matriz
fuera de su rango, acceso a una variable que no hace referencia a ningún
objeto o una eliminación no válida.
 No Heredan de RuntimeException: Intentar leer más allá del final de un
fichero o intentar abrir una URL formada de manera incorrecta.

Una vez que Java lanza una excepción, el método que ha provocado la
excepción acaba y el control vuelve al método más cercando en la secuencia de
llamadas que contenga un match() que incluya la excepción. Para capturar una
excepción tenemos un bloque try-catch. Si un código cualquiera del bloque try
lanza una excepción de la clase indicada en la cláusula match, entonces Java omite
el resto del código en el bloque try y ejecuta el código de tratamiento dentro de la
cláusula match.
Cuando Java lanza un error se detiene el proceso de todo el código en el
método. Esto supone un problema si el método local ha adquirido un recurso que
solo conoce este método y si este recurso debe ser liberado. Una solución sería
capturar y volver a lanzar la excepción, pero es necesario liberar el código normal y
el código de la excepción. Para poder solucionar este problema, Java utiliza la
cláusula finally

6. PACKAGES

Java Permite agrupar clases en una colección denominada paquete


(package). Los paquetes son convenientes para organizar el trabajo y para separar
el trabajo de las librerías de código proporcionadas por otro.. Por ejemplo, nos dan
una serie de clases útiles en un paquete denominado corejava. La librería estandar
de Java se distribuye en una serie de paquetes que incluyen java.lang, java.util,
java.net, etc. Los paquetes estándar de Java son ejemplos de una jerarquía de
paquetes que se pueden organizar por niveles. Esta jerarquía garantiza la unicidad
de nombres de paquetes.
Hay dos formas de utilizar los paquetes:
Fundamentos de programación · 29

 Indicar el nombre completo del paquete, aunque es una opción bastante


pesada.
 Utilizar la clave import, así posteriormente se puede hacer referencia a las
clases de un paquete sin indicar los nombres completos. Se puede importar
una clase específica o el paquete entero.

Todos los ficheros de un paquete se deben colocar en un subdirectorio que


corresponda al nombre completo del paquete. Por ejemplo, todos los ficheros del
paquete corejava deben pasar al subdirectorio corejava. Todos los ficheros del
paquete java.util están en el subdirectorio java\util.

7. ENTRADA/SALIDA ESTÁNDAR

La entrada/salida son dos aspectos fundamentales. Un ordenador no


tendría ninguna utilidad si no pudiese aceptar datos del exterior y presentar los
resultados lo más pronto posible. Las clases que contiene el paquete Java.io
permiten la gestión de entrada/salida independientemente del sistema utilizado en
la máquina. Los conceptos fundamentales de este paquete son los flujos (streams)
y ficheros.
La clase File define métodos independientes de la plataforma para la
manipulación de ficheros concretos del sistemas de ficheros nativo; no se permite
el acceso a los contenidos del fichero ya que no hay modos de lectura y escritura
pero se permite consultar la protección contra lectura, contra escritura, consultas si
el fichero es un directorio, consultar si existe ese fichero y generar listados de
directorios completos.
Como File no permite leer ni escribir ficheros, otras clases más potentes lo
permiten: Stream y RandonAccessFile. Esta última solo gestiona operaciones de
entrada/salida con archivos, e implementa las clases DataOutput y DataInput.
La gestión de la entrada/salida en Java está basada en los Streams
(secuencia o cinta de datos): permite componer diferentes formas de
entrada/salida, muchos tipos de ficheros diferentes, (secuenciales, acceso directo e
incluso ficheros ZIP). Existen 58 tipos de streams diferentes, hecho que da una idea
de la variedad y especificidad de cada tipo.
Cada flujo tiene datos de origen en caso de flujos de salida o datos de
destino en caso de flujos de entrada; por lo tanto tienen la necesidad común de
conectarse a alguna “entidad” antes de ser verdaderamente útiles. Los flujos de
entrada deben conectarse a dispositivos generadores de datos y los de salida a
lgún dispositivo que pueda aceptarlos.
La serialización es el mecanismo más sencillo para conformar objetos
persistentes, es decir, guardar los objetos de una ejecución en el disco. De este
modo es posible, por ejemplo, guardar el estado de una aplicación en el disco y
recuperar este estado en la siguiente ejecución.; para ello se debe:
 Implementar la interfaz Serializable: esta interfaz no tiene métodos,
solo sirve a Java como marcador que la clase se puede guardar en el
disco.
 Debemos abrir el fichero con un stream del tipo ObjectInputStream y
ObjectOutputStream
 Llamar al médo readObject() o writeObject() para leer o escribir cada
stream respectivamente

El método WriteObject es recursivo, si dentro del objeto que está grabando


se encuentra una referencia a otro objeto, también lo graba, lo que permite grabar
con una sola llamada, jerarquías enteras de objetos.
Las clases String y StringBuffer están muy relacionadas entre sí: Ambas
sirven para almacenar cadenas de caracteres. Éstos se guardan en formato Unicode
y no ASCII, lo que permite trabajar con todos los caracteres de la mayoría de las
lenguas, así como los acentos.
30 · Fundamentos de programación

La clase String crea una cadena inmutable: una vez creada no se puede
modificar su contenido (al contrario que la clase stringBuffer) por lo que cada vez
que llevamos a cabo operaciones que modifican la cadena, Java crea una nueva
instancia con el nuevo valor y es el que nos retorna la operación.
Fundamentos de programación · 31

TEMA 3 PRÁCTICA INTRODUCCIÓN AL AWT

1. ELEMENTOS BÁSICOS

AWT es la librería gráfica con un modelo de eventos que proporciona JDK;


los elementos básicos que componen la librería son:
 Componentes: botones, áreas de texto…
 Contenedores: Ventanas, paneles… que tienen dentro uno o varios
componentes.
 Gestores de posición: que gestionan la disposición de los componentes
dentro de los contenedores

La clase base de los componentes java dentro de AWt es la clase


java.awt.Component, de la que heredan todos los componentes visuales; estos
componentes cambian su aspecto en función de la plataforma (Windows, Unix…) y
las propiedades básicas son: background color (color de fondo), Bounde
(dimensiones), Cursor, Enabled, Font, Foreground Color (color del texto del
componente), Locale, Location, Name, Size y Visible.

En lo que respecta a los contenedores, AWT tiene dos tipos de


contenedores:
 Los paneles: no son visibles, se ve lo que contienen pero no lo que les
contiene.
 Las ventanas: son contenedores visibles que cambian de aspecto en
función de la plataforma. Un ejemplo:

Los gestores de posición se encargan de la posición y la medida de los


objetos que se encuentran en un contenedor. Por ello cada contenedor tiene
asociado un gestor de posición de modo que no depende del contenedor la forma
como se gestiona la visualización de los componentes en su interior;
32 · Fundamentos de programación

La Clase Panel es un componente, pero también es un contenedor: es un


contenedor sin marco que agrupa componentes y casi siempre es invisible; siempre
debe estar dentro de otro contenedor, como puede ser una ventana u otro panel.
La ventana es el contenedor visible por excelencia. Excepto las splash Windows
(ventana que aparece al iniciarse algunas aplicaciones que no tienen botones ni
nada aparte de una imagen de presentación) que heredan de la clase windows, las
demás heredan de una clase hila, la clase frame: java.awt.Frame. Es la ventana
base, que puede existir por sí misma, a diferencia de panel y dialog que dependen
de un frame.

La Clase dialog gestiona las ventanas de diálogo: son ventanas que aparecen
temporalmente por encima de la ventana de la aplicación para comunicar un
mensaje o una información al usuario, o bien para pedir información: una vez que
la ventana ha cumplido su misión, desaparece, por ello los diálogos siempre
dependen sde una ventana principal. Dentro de esta clase, destaca la clase
FileDialog, que es la tipica clase en la que tenemos que dar un nombre de archivo
para guardar “save” o para abrilo “load”.

Vamos a definir ahora un poco los tipos de componentes que nos


encontramos y son los siguientes:
 Componentes básicos: como label: se trata de etiquetas de texto corto y
fijo que marcan un componente o área de componentes determinada
dentro de una ventana. También tenemos los botones (button) que
representa al típico botón que tienen todas las aplicaciones con entorno
gráfico, suelen tener un texto asociado que comunica información
semántica del botón (el significado de apretar el botón) y una funcionalidad
para ese botón. También nos encontramos con la clase canvas (lienzo) que
no es más que una superficie sobre la que podemos dibujar.
 Componentes de texto: El TextComponent no permite crear instancias,
dado que es la base de los otros componentes de texto. Los principales son
textfield (campo de texto que solo ocupa una línea) y textarea (área de
texto que ocupa más de una línea y que cuando éste excede del tamaño
establecido aparecen unas barras de desplazamiento para poder visualizar
todo el texto).
 Componentes de selección: Sirve para seleccionar objetos, entre ellos
tenemos los checkbox (llamadas casillas de comprobación, son campos
booleanos en los que podemos elegir verdadero o falso), Choice
(Representa una elección entre muchas opciones representadas por objetos
String y List (parecida a choice, salvo porque podemos ver las opciones a
elegir dentro de una caja con una barra de desplazamiento.
 Componentes de menú: La clase menuComponent es la clase madre de
todos los componentes relacionados con el menú. Pero observamos que
hay varios tipos importantes en este grupo: MenuBar (Barra de menú),
MenuItem (Elementos que pueden estar dentro del menú: a su vez pueden
ser simples, separadores, de comprobación –con un check al lateral si
están activados- y menú –el propio componente de menú tiene submenus),
Menú y PopUpMenu (menús de contexto cuyo contenido cambia según el
Fundamentos de programación · 33

elmento de la ventana en el que trabajamos en un momento dado y el


estado de ese elemento).
 Otros componentes: Scrollbar (permite el desplazamiento en una sola
dirección o dimensión), ScrollPanel (permite desplazarse en dos direcciones
o dimensiones).
 Gestores de Posición: FlowLayout (coloca los elementos uno detrás de
otro), GridLayout (coloca los componentes en forma de cuadrícula),
BorderLayout (define 5 áreas dentro del contenedor, estas son Norte, Sur,
Este, Oeste y Centro), CardLayout (coloca los componentes una delante o
detrás de otro) y GridBagLayout (permite definir la posición y el tamaño de
cada componente por separado)

Texto elaborado a partir de:


Fundamentos de programación II
Julià Minguillón i Alfonso, Elena García Barriocanal, Juan José Ramos González,
Miquel Àngel Piera i Eroles, Miguel Ángel Sicilia Urbán,
Jordi Serra Ruiz, David Conrado Cabanillas Barbacil, Xavier Franch Gutiérrez, Marta I. Tarrés Puertas
Junio 2002

También podría gustarte