Esta es una vista previa del archivo. Inicie sesión para ver el archivo original
Programacion Orientada a Objetos 2015-2016 Campus/Tema_4_Herencia_y_polimorfirmo_v1_1.pdf
1
Tema 4
Herencia ,clases
abstractas y
polimorfismo.
<<cambios pag 505 libro Meyer><
UPSAM 2010-2011
2
Índice de contenidos
1. Herencia, clases abstractas y polimorfismo. ......................................................................... 3
1.1. Generalización ............................................................................................................... 3
2. Herencia ................................................................................................................................ 6
2.1. Reescritura de métodos ................................................................................................. 6
2.2. Clases abstractas ............................................................................................................ 7
3. Polimorfismo ......................................................................................................................... 9
3.1. Ejemplo de polimorfismo ............................................................................................ 10
4. Tipos de herencia ................................................................................................................ 11
5. Ligadura .............................................................................................................................. 12
5.1. Ventajas de la ligadura dinámica................................................................................. 13
5.2. Polimorfismo con ligadura dinámica .......................................................................... 14
3
1. Herencia, clases abstractas y polimorfismo.
1.1. Generalización
Según la DRAE el significado de generalizar es el siguiente:
Abstraer lo que es común y esencial a muchas cosas, para formar un concepto
general que las comprenda todas.
El proceso de generalizar es fundamental en ciencia, constituye la base de la creación de
reglas Universales.
La generalización se constituye así como el proceso de abstraer y representar lo común.
En este proceso siempre diferencia dos roles: lo que es general , y lo que es específico y
puede ser representado por lo general. Veremos más adelante como la generalización y
especialización crea una nueva relación entre clases .Además la generalización es una
relación mucho más fuerte que la asociación, de hecho, la generalización implica el
nivel más alto de dependencia entre dos clases (y por consiguiente de acoplamiento).
Generalización de clases
Conceptualmente la generalización es una idea muy simple. Pensando en cosas
generales encontramos conceptos como por ejemplo el de asignatura , árbol ,etc.., y en
algo más concreto encontramos la asignatura de programación, el abeto, el pino ,etc.
Son conceptos relacionados entre sí , pero a distinto nivel de detalle. Si pensemos en los
razonamientos y propiedades aplicados al concepto general serian aplicables a los
conceptos más específicos.
En la Figura 1, tenemos una clase general denominada Forma , que como se puede
comprobar es un concepto bastante general. A partir de este concepto derivamos otros
más específicos, que son variantes de la idea general Forma.
4
Forma
Cuadrado Circulo Triangulo
Elemento más general
Elementos más especificos
“Es un tipo de..”
Subclase
Subtipo
Hijo
Descendiente
Superclase
Supertipo
Padre
Antecesor
Clase base
Figura 1 Generalización
Existen dos procesos por los que podríamos llegar a esta jerarquía: a partir de un
proceso de especialización o por medio de la generalización. En la especialización,
identificaríamos primero el concepto general Forma y posteriormente veríamos la
necesidad de buscar o definir conceptos más específicos. En la generalización,
podríamos identificar una serie de clases como Cuadrado, Circulo y Triangulo y
encontrar que comparten atributos y comportamiento, lo que daría lugar a una clase más
general.
De manera general, podemos definir la relación de generalización como un tipo de
relación en la que una clase (clase general) generaliza el comportamiento y las
propiedades de otras clases (subclases). De otra manera, podemos decir que las
subclases especializan el comportamiento de la clase general.
Clase general
Subclase 1 Subclase 2 Subclase N
Figura 2. Generalización/Especialización en notación UML
Algunos ejemplo de relaciones Generalización/especialización.
5
Publicacion
Revista Libro Acta
Lista
Lista
enlazada
Lista doblemente
enlazada
Lista
circular
6
2. Herencia
Cuando se crea una jerarquía como la de la Figura 1, implícitamente existe una
relación de herencia entre los participantes, por que las subclases heredan todas las
características de sus superclases. Para ser más específicos en relación con la
programación orientada a objetos, las subclases heredan:
Atributos
Operaciones
Relaciones
Restricciones
Las subclases también pueden añadir nuevas características o reescribir las
operaciones de las superclases.
2.1. Reescritura de métodos
En el ejemplo mostrado en Figura 2 las subclases Cuadrado y Círculo heredan todos
los atributos, operaciones y restricciones de la superclase Forma. Esto significa que
aunque no podamos observar estas características en las subclases, estas están
implícitas. A nivel conceptual podemos decir entonces que un Círculo y un Cuadrado
son un tipo de Forma.
Forma
origen:Punto
ancho:int
alto:int
dibujar(g:Grafico)
obtenerArea():int
Cuadrado Circulo
radio: int { ancho/2}
ancho=alto
Figura 3. Jerarquía y herencia
En este ejemplo (Figura 3) la clase Forma define dos métodos denominados dibujar(…)
y obtenerArea(). Estos métodos podrían no ser apropiados para la clase Círculo o
Cuadrado. Es de esperar que se dibuje un cuadrado cuando se envié el mensaje
7
dibujar( ) a un objeto de la clase Cuadrado y un Circulo cuando se envíe este mismo
mensaje a un objeto de la clase Círculo. Por tanto la operación dibujar() definida en la
clase base y que ambas subclases han heredado no sirve, de hecho, esta operación
podría no dibujar nada en absoluto ¿qué aspecto tiene una forma?. Los mismos
argumentos son aplicables a la operación obtenerArea() . ¿Cómo podemos calcular el
área de una forma no definida?
Estos problemas indican claramente la necesidad de permitir que las subclases sean
capaces de cambiar el comportamiento de superclase. La subclase Cuadrado y la
subclase Circulo necesitan implementar sus propio comportamiento para las
operaciones dibujar(…) y obtenerArea(…) y sobrescribir el comportamiento por defecto
heredado de la clase base para proporcionar un comportamiento más especifico y
apropiado.
La Figura 4 muestra esta acción: las subclases Circulo y Cuadrado proporcionan sus
propias operaciones para dibujar() y obtenerArea() .
Forma
dibujar(g:Grafico)
obtenerArea():int
Cuadrado Circulo
dibujar(g:Grafico)
obtenerArea():int
dibujar(g:Grafico)
obtenerArea():int
Figura 4 Reescritura de métodos heredados
Para reescribir una operación de la clase base, una subclase debe proporcionar una
operación con exactamente el mismo prototipo de la operación de la clase base.
2.2. Clases abstractas
Las relaciones de generalización, introducen a nivel conceptual clases, que más que
clases de implementación son elementos o conceptos de organización. En muchas
generalizaciones, será necesario delegar la implementación de las operaciones a las
subclases. En nuestro ejemplo la operación dibujar (…) de Forma es un buen ejemplo,
porque no sabríamos como pintar una Forma sin determinar. El concepto de dibujar una
forma es demasiado abstracto para tener una implementación.
8
Se dice entonces que una clase tienen operaciones abstractas o un comportamiento
abstracto. Podemos indicar esto haciendo que el método sea abstracto.
Debido a que las clases generales con métodos abstractos no tienen un
comportamiento definido(es decir no tienen una implementación para sus
métodos), estas clases no pueden ser instanciadas. A este tipo de clases se las
denomina clases abstractas.
En la Figura 5, tenemos una clase abstracta llamada Forma, con dos métodos
abstractos dibujar(…) y obtenerArea(). La implementación para estas operaciones las
proporcionan tanto la subclase Circulo como la subclase Cuadrado. Aunque la clase
abstracta Forma es incompleta, y no puede ser instanciada, sus subclases si son
completas cuando son instanciadas.
Existe una serie de ventajas en la utilización de clases abstractas:
Se pueden definir un conjunto de operaciones abstractas en la clase base clase
abstracta de forma que deben ser implementadas por las subclases. Se puede ver
esto como “un contrato” que todas las subclases deben cumplir (o
implementar).
Se puede escribir partes de código que manipulen Formas y de acuerdo al
principio de substitución esta parte de código se podrá usar con cualquier
subclase.
Forma
dibujar(g:Grafico)
obtenerArea():int
Cuadrado Circulo
dibujar(g:Grafico)
obtenerArea():int
dibujar(g:Grafico)
obtenerArea():int
Clase abstracta
Operaciones abstractas
Operaciones concretas
Clases concretas
Figura 5. Clases abstracta y clases concretas
9
3. Polimorfismo
El termino polimorfismo deriva del griego ( poly=”muchos”;
morphos=”forma”).Polimorfismo significa “varias formas”. En la POO una operación
polimórfica es aquella que tiene varias implementaciones.
Hemos visto en el ejemplo anterior dos operaciones polimórficas. Las operaciones
abstractas dibujar(…) y obtenerArea(…) de la clase Forma tienen dos
implementaciones diferentes, una implementación en la subclase Cuadrado y otra en la
subclase Círculo.
La Figura 6 ilustra perfectamente el polimorfismo
Forma
dibujar(g:Grafico)
obtenerArea():int
Cuadrado Circulo
dibujar(g:Grafico)
obtenerArea():int
dibujar(g:Grafico)
obtenerArea():int
Clase abstracta
Operaciones polimórficas
implementaciones
Clases concretas
Figura 6. Operaciones polimórficas
La subclase Círculo y Cuadrado heredan de la superclase Forma y proporcionan una
implementación para las operaciones polimórficas dibujar(…) y obtenarArea(). Todas
las subclases de Forma deben proporcionar una implementación concreta para las
operaciones dibujar(…) y obtenarArea(), esto significa que desde el punto de vista de
estas operaciones se puede tratar todas las subclases de Forma de la misma
manera. Un conjunto de operaciones abstractas es por consiguiente una forma de
definir un conjunto de operaciones que todas las subclases deben implementar. Esto se
conoce como contrato.
Claramente la implementación de dibujar() y obtenerArea() diferirán para la subclase
Cuadrado y la subclase Círculo. Así por ejemplo el método obtenerArea() de Circulo
devolverá el resultado de la operación π*r*r mientras que la subclase Cuadrado
devolverá el resultado de la operación alto*ancho. Esta es la esencia del polimorfismo,
objetos de diferentes clases tienen operaciones con el mismo prototipo (igual
signatura) pero diferentes implementaciones.
10
Encapsulación, herencia y polimorfismo son los “tres pilares” de la orientación a
objetos. El polimorfismo nos permite diseñar sistemas más simples, que se adaptan
mejor a los cambios por que permite tratar objetos diferentes de la misma forma.
De hecho lo que hace al polimorfismo un aspecto esencial de la orientación a objetos
es que permite enviar el mismo mensaje a objetos de diferentes clases.
3.1. Ejemplo de polimorfismo
Vamos a ver un ejemplo del uso del polimorfismo. Suponemos que tenemos una clase
VentanaGráfica que mantiene y utiliza una colección de Formas. El modelo de clases
es el mostrado en la Figura 7.
Forma
dibujar(g:Grafico)
obtenerArea():int
Cuadrado Circulo
dibujar(g:Grafico)
obtenerArea():int
dibujar(g:Grafico)
obtenerArea():int
VentanaGráfica
1 *
Figura 7 Uso del polimorfismo
Hemos comentado que una clase abstracta no puede ser instancia, luego no podemos
instanciar la clase Forma, pero de acuerdo con el principio de substitución, se
pueden crear instancias de las subclases concretas de Forma en cualquier lugar
donde aparezca la clase base.
Así, aunque se puede observar que un objeto de la clase VentanaGráfica puede contener
una colección de varios objetos Forma, los únicos objetos que realmente se pueden usar
son instancias de las subclases, por qué Forma es abstracta, y no puede ser instanciada.
En este caso particular hay dos subclases concretas, la subclase Circulo y Cuadrado.
Así, Ventana gráfica contendrá colecciones de objeto de tipo Círculo y Cuadrado.
En la Figura 8 se muestra un modelo de objetos instancia del diagrama de la Figura 7.
Este modelo muestra como un objeto VentanaGráfica soporta una colección de cuatro
objetos s1,s2,s3 y s4 donde s1, s2 y s4 son objetos de la clase Circulo y s3 es un objeto
de la clase Cuadrado. La cuestión es ¿Qué ocurre cuando VentanaGráfica interactúa
con la colección de formas, y envía a cada objeto de la colección el mensaje dibujar(…).
La respuesta es que cada objeto se comportará de la forma esperada: los Cuadrados se
11
dibujarán como cuadrados y los Círculos como círculos. Es la clase del objeto la que
determina lo que el objeto debe dibujar.
s1:Circulo
vg:VentanaGráfica
s2:Cuadrado
s3:Circulo
s4:Circulo
dib
uja
r()
dibu
jar()
dibujar()
dibujar()
Figura 8 .Uso de operaciones polimórfica.
El punto clave en todo esto es que cada objeto responde a un mensaje por medio de la
invocación de la operación correspondiente especificada por su clase.
La construcción del lenguaje que hace posible el polimorfismo es la ligadura
dinámica ( visto más adelante) conocida también como ligadura tardía o postergada
entre llamadas a funciones y los cuerpos reales de dichas funciones.
4. Tipos de herencia
En las relaciones de herencia, se podría dar el caso de que una clase podría heredar las
propiedades de más de una clase. Esto da lugar a que tengamos dos tipos de herencia:
La herencia simple .Es aquella en la que cada clase hereda de una única clase.
La herencia múltiple. Es la transmisión de métodos y datos de más de una clase
base a la clase derivada. (Figura 9).
-inversion
-interes
ProductoFinanciero
-plazo
Deposito
-Nacciones
Acciones
Combinado
Figura 9.Herencia múltiple
Se pueden presentar dos problemas cuando se diseñan clases con herencia múltiple:
12
Colisiones de nombres de diferentes clases base.
Herencia repetida de una misma clase base.
Las jerarquías de herencia múltiple pueden ser complejas de gestionar. De hecho, no
todos los lenguajes OO la implementan. Por ejemplo en Java, C# y SmallTalk no existe
la herencia múltiple Aunque en Java se puede simular o implementar a través del
concepto de Interfaz. Sin embargo en C++ si se admiten herencia múltiple.
En el ejemplo anterior existe el problema de la repetición de elementos heredados. La
clase Combinado hereda desde dos clases diferentes los mismos atributos inversión e
interés, produciéndose una colisión.
5. Ligadura
La ligadura representa, generalmente una conexión entre una entidad y sus
propiedades. Si la propiedad se limita a funciones, la ligadura es la
conexión entre la
llamada a una función y el código que se ejecuta tras la llamada.
funcion1(){
//...
}
main(){
funcion1();
}
xFF000234
El compiador sustituye
esta instrucción por un
salto a la zona de
implementación de la
fucnion1
Ejemplo
JUMP FF000234
El momento de tiempo en el que un atributo o función se asocia con sus valores o
funciones se denomina tiempo de ligadura. La ligadura se clasifica según sea el tiempo
o momento de la ligadura, y esta puede ser: estática o dinámica.
La ligadura estática se produce antes de la ejecución (durante la compilación),
mientras que la ligadura dinámica se produce durante la ejecución.
En un lenguaje de programación con ligadura estática, todas las referencias se
determinan en tiempo de compilación. La mayoría de los lenguajes procedimentales son
de ligadura estática; el compilador y el enlazador (linker) definen directamente la
posición fija del código que se va a ejecutar en cada llamada a la función.
La ligadura dinámica supone que el código a ejecutar en respuesta a un mensaje no
se determinará hasta el momento de la ejecución. Únicamente la ejecución del
programa (normalmente un puntero a la clase base) determinará la ligadura efectiva
entre las diversas que son posibles (una para cada clase derivada).
Vamos a ver y a estudiar estos conceptos a través de un ejemplo. Para ello vamos a
considerar que la notación virtual sobre un método de una clase indica al compilador
que esa función que será implementada en una subclase no en la clase donde se define.
13
Vamos a suponer, que sólo se puede usar esta opción cuando exista la posibilidad de
que una subclase implemente los métodos denominados virtuales.
Vamos a imaginarnos que un instante de tiempo dado en la memoria hay tres objetos
instancia de tres clases diferentes:
Class Circulo
dibujar()
{
//Código para dibujar circulo
}
obtenerArea(){
Return PI*radio*radio;
}
Class Cuadrado
dibujar()
{
//Código para dibujar cuadrado
}
obtenerArea(){
Return ancho*alto;
}
Class Forma
virtual dibujar()
Virtual obtenerArea()
Int x,y;
Mapa de memoria
Class VentanaGráfica
DibujarFiguras()
{
Int numFormas=10;
For (int i=0;i<numFormas;i++)
figuras[i]->dibujar();
}
Forma *figuras[10];
x000FF01
¿?
En tiempo de compilación no se
puede determinar cual es la
implementación( es decir la
dirección) especifica de
dibujar()
x000FE00
Figura 10 Mapa de memoria, funciones virtuales y polimorfismo
La cuestión es ¿Como resuelve el compilador la siguiente instrucción?
figuras[i]->dibujar();
Donde figuras[i] es una array de Figuras.
El compilador tiene que enlazar la llamada a la función dibujar() con alguna zona de
memoria , pero en este caso, como vemos en la Figura 10, no tiene ninguna
implementación. Pero como está indicado con la palabra reservada virtual. El
compilador “sabe” que debe buscar esta implementación en alguna clase derivada.
¿Cuál de ellas? La que en ese momento este en la posición indexada
5.1. Ventajas de la ligadura dinámica
La principal ventaja de la ligadura dinámica frente a la ligadura estática es que la
dinámica ofrece un alto grado de flexibilidad y diversas ventajas prácticas para manejar
14
jerarquías de clases de un modo muy simpe. Entre las desventajas de la ligadura
dinámica se encuentra que en principio es menos eficiente que la ligadura estática.
Los lenguajes OO que si siguen estrictamente el paradigma ofrecen sólo ligadura
dinámica. Los lenguajes híbridos ( C++,Simula) ofrecen los dos tipos de ligadura.
5.2. Polimorfismo con ligadura dinámica
Con la ligadura dinámica, el tipo de objeto no es preciso decidirlo hasta el momento de
la ejecución. El ejemplo anterior en la sección de la clase VentanaGrafica :
DibujarFiguras()
{
Int numFormas=10;
For (int i=0;i<numFormas;i++)
figuras[i]->dibujar();
}
La instrucción figuras[i]->dibujar(); envía al programa al bloque apropiado de código
basado en el tipo de objeto . En concreto pasará el mensaje dibujar() a la figura
apuntada por figuras[i]. La indicación virtual en el método dibujar() de la clase Figura
ha indicado al compilador que esta función se puede llamar por medio de un puntero.
Mediante la ligadura dinámica, el programa determina el tipo de objeto en tiempo de
ejecución.
Programacion Orientada a Objetos 2015-2016 Campus/Tema 3 Practicas guiadas c__y java 2012_2013.pdf
1
Laboratorio de Programación UPSAM
Tema 3: Práctica guiadas
Objetivos
-Implementar asociaciones entre clases.
-Utilización de colecciones para implementar relaciones. Se usarán los tipos array
(C`++) ,List (C++)y LinkedList (Java).
Nota: Este documento constituye material de apoyo y de estudio para las prácticas
obligatorias que posteriormente se deberán entregar.
Práctica guiada 1 C++
(Profesora Elisa García)
Un juego de luces está formado por un número variable de focos (entre 4 y 6), que se determina
exactamente cuando se fabrica. El técnico de luces puede realizar varias operaciones sobre el juego de
luces: encenderlo completamente, encender una luz en particular, apagarlo completamente o apagar un
foco concreto. Cada foco puede ser encendido y apagado por el operario de manera independiente.
Se pide:
Realizar un programa principal que realice las siguientes acciones:
Crear un juego de luces con 5 luces.
Encender todas las luces.
Apagar la primera.
Imprimir el estado de todas las luces del juego de luces.
Estudio de la solución:
Las dos abstracciones necesarias son Juego de Luces y Foco. Y tienen una asociación
Juego de luces juega el rol de agregado respecto a Foco. Pues el juego de luces se puede
concebir como un conjunto de Focos. Identificamos que la relación es de agregación. La
agregación es fuerte (Composición) ya que el ciclo de vida de las instancias del tipo Foco no son
controladas completamente dentro del agregado(en este caso Juego de Luces), luego
tenemos una composición entre Juego de Luces y Foco.
La multiplicidad de la relación es 4..6 en la parte del agregado. Es una multiplicidad variable con
un valor máximo. Necesitamos un parámetro en el constructor que “informe “del número de
focos exactos que tiene el juego de luces.
Las responsabilidades individuales de cada clase son:
o Clase JuegoDeLuces: tiene que permitir al operador encender cada una de los Focos,
encender un Foco en particular, apagar todos y apagar uno en concreto.
2
o Clase Foco: debe poder ser apagado y encendido.
Tomando en cuenta todas estas consideraciones tenemos el siguiente diseño:
Diagrama de clases UML propuesto para la solución:
+JuegoDeLuces(entrada n : int)
+encender(entrada n : int)
+encenderTodos()
+apagar(entrada n : int)
+apagarTodos()
-Focos
-nFocos : int
JuegoDeLuces
+Foco(entrada enc : bool)
+encender()
+apagar()
-encendido
Foco
1 4..6
La implementación en C++ propuesta es la siguiente:
****** foco.h ***
#ifndef FOCO_H
#define FOCO_H
class Foco
{
private:
bool encendido;
public:
Foco(bool enc);
~Foco();
void encender();
void apagar();
bool estaEncendido();
};
#endif
****** foco.cpp ***
#include "foco.h"
Foco::Foco(bool enc){
encendido = enc;
}
void Foco::apagar(){
encendido = false;
}
void Foco::encender(){
encendido = true;
}
bool Foco::estaEncendido()
{
return encendido;
}
Foco::~Foco()
{}
3
Juego de Luces tiene los siguientes atributos:
El conjunto de Focos o colección de Focos: Esto se va a modelar como un array
de objetos tipo
Foco.
Y el número exacto de estos focos: Esto permite controlar cuantos Focos hay que encender por
que el número es variable (unas veces 6 otras 4) y con un número máximo de 6.
********************************************* juegodeluces.h ***
#ifndef _JUEGOLUCES_H
#define _JUEGOLUCES_H
#include "Foco.h"
#define MIN_LUCES 4
#define MAX_LUCES 6
class JuegoDeLuces{
private:
unsigned int numLuces;
Foco* Focos;
public:
JuegoDeLuces(int n=MIN_LUCES);
~JuegoDeLuces();
void encender(int);
void encenderTodos();
void apagar(int n);
void apagarTodos();
void verEstado();
};
#endif
********************************************* juegodeluces.cpp ***
#include <iostream>
#include "juegodeluces.h"
using namespace std;
JuegoDeLuces::JuegoDeLuces(int n){
if (n>0 && n<MAX_LUCES)
numLuces = n;
else
numLuces = MIN_LUCES;
Focos= new Foco[numLuces];
}
JuegoDeLuces::~JuegoDeLuces(){
delete [] Focos;
}
void JuegoDeLuces::encender(int n){
Focos[n].encender();
}
void JuegoDeLuces::encenderTodos(){
unsigned int i;
for (i=0; i<numLuces; i++)
Focos[i].encender();
}
void JuegoDeLuces::apagar(int n){
Focos[n].apagar();
}
void JuegoDeLuces::apagarTodos(){
unsigned int i;
for (i=0; i<numLuces; i++)
Focos[i].apagar();
4
}
void JuegoDeLuces::verEstado(){
unsigned int i;
for (i=0; i<numLuces; i++)
if (Focos[i].estaEncendido())
cout << "Foco ["<< i<<"]"<<" encendido"<<endl;
else
cout << "Foco ["<< i<<"]"<<" apagado"<<endl;
}
Vamos a probar estas clases creando un programa que realice lo siguiente:
Crear un juego de luces.
Encender todos los focos del juego de luces.
Apagar el primero foco del juego de luces.
*** MAIN.CPP ***
#include <iostream>
#include <stdlib.h>
#include "juegodeluces.h"
using namespace std;
int main(int argc, char *argv[]){
JuegoDeLuces ju1(5);
ju1.encenderTodos();
ju1.apagar(0);
ju1.verEstado();
system("PAUSE");
return EXIT_SUCCESS;
}
Nota: en esta solución se utilizan objetos estáticos en C++
En este programa a diferencia de los anteriores no hemos creado los objetos de forma dinámica.
La siguiente instrucción declara una variable (ju1) de tipo JuegoDeLuces e implícitamente se
llama al constructor de JuegoDeLuces pasándole 5 como argumento
JuegoDeLuces ju1(5);
En la siguiente instrucción se accede a una función miembro mediante el operador de acceso (.)
ju1.encenderTodos();
El programa principal equivalente con memoria dinámica sería el siguiente.
#include <iostream>
#include <stdlib.h>
#include "juegodeluces.h"
using namespace std;
5
int main(int argc, char *argv[]){
JuegoDeLuces *ju1=new JuegoDeLuces(5);
ju1->encenderTodos();
ju1->apagar(0);
ju1->verEstado();
delete jul;//Borramos
system("PAUSE");
return EXIT_SUCCESS;
}
Actividad propuesta
Realice un programa principal que realice lo siguiente.
Cree un juego de luces con 4 focos.
Solicite una secuencia de longitud fija de 4 caracteres “0” o “1” (Ejemplo “0011”). Donde “1”
significa foco encendido y “0” foco apagado.
Y en función de esta secuencia active o desactive los focos del juego de luces.
Práctica guiada 2.C++
Modelar la información acerca de departamentos y empleados de una empresa. Para ello considere las
siguientes características:
La información que se desea representar sobre un departamento es la siguiente: nombre del
departamento ( Marketing, I+D, etc) y los empleados que pertenecen al departamento. Por otro lado
sobre los empleados sólo es necesario guardar su nombre.
Se pide:
Escribir un programa principal con las siguientes acciones
Crear un departamento de I+D
Crear dos empleados
Asignar los empleados al departamento creado.
Estudio de la solución C++
Las dos conceptos que manejamos son: Departamento y Empleado
La asociación es de tipo 1:N .Se puede plantear como una agregación débil
El modelo de clase en UML es el siguiente:
Departamento
nombre: String
empleados: List
Departamento(String )
obtenerEmpelados():void
numeroDeEmpelados():int
Asignarempelado(Empleado)
Empleado
nombre:string
Empleado(String )
ObtenerNombre():String
0..*1
6
Modelamos la clase Departamento como un agregado compuesto por Empleados.
El tipo de agregación es débil ya que el ciclo de vida de los objetos de tipo Empleado no están
controlados por el agregado y podrían formar parte de otras asociaciones
Implementamos ahora cada una de las clases
Archivo empleado.h
#ifndef EMPLEADO_H
#define EMPLEADO_H
class Empleado
{
protected:
char *nombre;
public:
// constructores
Empleado();
Empleado(char * nomb);
// destructor
~Empleado();
//funciones
char * getNombre();
};
#endif
Archivo Empelado.cpp
#include "empleado.h"
// constructor
Empleado::Empleado()
{
nombre="nombre no asignado";
}
Empleado::Empleado( char *nomb){
nombre=nomb;
}
Empleado::~Empleado()
{}
char * Empleado::getNombre(){
return nombre;
}
Archivo departamento.h
#ifndef DEPARTAMENTO_H
#define DEPARTAMENTO_H
#include "Empleado.h"
#include <list.h>
class Departamento
{
protected:
char* nombre;
list<Empleado *> empleados;
public:
// contsructor
Departamento();
Departamento(char *nomb);
// destructor
~Departamento();
//función miembro
void obtenerEmpleados();
void numeroDeEmpleados();
void asignarEmpleado(Empleado *e);
7
};
#endif
Nota: Observe la siguiente declaración
list<Empleado *> empleados;
Esta línea declara una variable con nombre empleados de tipo list. list es
tipo colección que se encuentra en la librería estándar de C++
Departamento.cpp
#include "departamento.h" // class's header file
Departamento::Departamento(char * nomb)
{
nombre=nomb;
}
Departamento::Departamento()
{
nombre="Departamento sin nombre";
}
Departamento::~Departamento()
{
//se borra cada elemento de la lista empleados;
list<Empleado *>::iterator iterador_lista=empleados.begin();
for (;iterador_lista!=empleados.end();iterador_lista++){
delete *iterador_lista;
}
delete nombre;
}
void Departamento::obtenerEmpleados(){
//itereador_lista se posiciona al principio de la lista
list<Empleado *>::iterator iterador_lista=empleados.begin();
Empleado *e;
//Recorrer cada elemento de la lista
for (;iterador_lista!=empleados.end();iterador_lista++){
e=*iterador_lista; //Asignar el contenido del iterador
cout<<"Empleado :"<<e->getNombre()<<endl;
}
}
void Departamento::numeroDeEmpleados(){
cout<<"Dpto "<<nombre<<" tiene "<< empleados.size()<<" empleados";
}
void Departamento::asignarEmpleado(Empleado *e){
//inserta un empleado al principio de la lista
empleados.push_front(e);
}
El programa principal sería el siguiente:
#include <cstdlib>
#include <iostream>
#include "Departamento.h"
#include "Empleado.h"
using namespace std;
8
int main(int argc, char *argv[])
{
Departamento *dpto=new Departamento("DESARROLLO");
Empleado *emp1=new Empleado("Juan");
Empleado *emp2=new
Empleado("Pedro");
dpto->asignarEmpleado(emp1);
dpto->asignarEmpleado(emp2);
dpto->obtenerEmpleados();
dpto->numeroDeEmpleados();
system("PAUSE");
return EXIT_SUCCESS;
}
Práctica guiada 2. Java
Estudio de la solución en Java
El mismo estudio realizado anteriormente es válido.
Clase Empleado.java
public class Empleado {
String nombre;
public Empleado(String nombre) {
this.nombre = nombre;
}
public String getNombre() {
return nombre;
}
}
Clase Departamento.java
import java.util.LinkedList;
public class Departamento {
String nombre;
LinkedList empleados;
public Departamento(String nombre) {
this.nombre = nombre;
empleados=new LinkedList(); //instanciar colección
}
public void asignarEmpleado( Empleado e)
{
empleados.add(e);
}
public void obtenerEmpleados()
{
Empleado e;
int tam= empleados.size();
int pos=0;
//Mientras haya elementos
while ( pos<=tam-1)
{
9
//obtener elemento
e=(Empleado)empleados.get(pos);
System.out.println(e.getNombre());
pos++;
}
}
public void numeroDeEmpleados()
{
System.out.println("Numero de empleados:" +empleados.size());
}
}
Observa que para implementar la relación con multiplicidad * usamos un tipo colección ( la clase
LinkedList).
LinkedList empleados;
LinkedList es una clase tipo colección que implementa una lista enlazada. Podríamos haber usados
otras disponibles como por ejemplo Vector. LinkedList se encuentra el paquete java.util, luego antes de
usarla debemos importarla:
import java.util.LinkedList;
Clase ProgramaPrincipal.java
public class ProgramaPrincipal {
public static void main(String[] args) {
Departamento dpto = new Departamento("DESARROLLO");
Empleado emp1 = new Empleado("Juan");
Empleado emp2 = new Empleado("Pedro");
dpto.asignarEmpleado(emp1);
dpto.asignarEmpleado(emp2);
dpto.obtenerEmpleados();
dpto.numeroDeEmpleados();
}
}
Programacion Orientada a Objetos 2015-2016 Campus/TEMA 2 Programación orientada objetos v3.pdf
1
Tema 2
Conceptos y
principios de la
orientación a objetos
2
Índice de contenidos
1. Introducción .......................................................................................................................... 3
2. Factores de calidad del software ........................................................................................... 3
3. El paradigma de orientación a objetos .................................................................................. 5
3.1. El principio de modularidad .......................................................................................... 6
3.1.1. El modulo y su estructura .......................................................................................... 7
3.1.2. Características de la programación modular ......................................................... 8
3.1.3. Tamaño del modulo ............................................................................................... 8
3.1.4. Ocultación de la información ................................................................................ 8
3.2. Diseño de módulos. Acoplamiento y cohesión ............................................................. 9
3.2.1. Acoplamiento ........................................................................................................ 9
3.2.2. Cohesión .............................................................................................................. 11
4. Tipos de datos y Tipos abstractos de datos ......................................................................... 12
5. De los módulos a los tipos abstractos de datos. .................................................................. 12
6. Clases y Objetos .................................................................................................................. 14
6.1. Atributos ...................................................................................................................... 17
6.2. Estado de un objeto. .................................................................................................... 18
6.3. Mensajes ,operaciones y métodos ............................................................................... 18
6.3.1. Comportamiento de un objeto ................................................................................. 19
6.3.2. Método .................................................................................................................... 19
6.3.3. Definición de un Método ......................................................................................... 20
6.3.4. Mensajes .................................................................................................................. 22
6.4. Ocultación de información .Visibilidad de los atributos y métodos ........................... 22
6.5. Propiedades de instancia y propiedades de clase ........................................................ 23
6.6. Los métodos constructor y destructor de una clase ..................................................... 23
3
1. Introducción
Vivimos en un mundo de objetos. Estos objetos existen en la naturaleza, en entidades
hechas por el hombre, en los negocios y los diversos productos que usamos. Pueden ser
clasificados, descritos, organizados, combinados, manipulados y creados. Por eso no es
de extrañar que se proponga una visión orientada a objetos para la creación de software
de computadora, una abstracción que modela el mundo de forma tal que nos ayuda a
entenderlo mejor.
A finales de los 60 se propuso un enfoque orientado a objetos para el desarrollo de
software, pero esta tecnología ha necesitado casi veinte años para ser ampliamente
usada. Durante los 90, la POO (programación orientada a objetos) se convirtió en el
paradigma para muchos desarrolladores de software.
Las tecnologías de objetos llevan a reutilizar, y la reutilización lleva a un desarrollo de
software más rápido y a programas de mejor calidad. El software orientado a objetos
es más fácil de mantener debido a que su estructura es inherentemente poco
acoplada. Esto lleva a menores efectos colaterales cuando se deben hacer cambios y
provoca menos frustración en el ingeniero del software y en el cliente. Además, los
sistemas orientados a objetos son más fáciles de adaptar y escalan más fácilmente (por
ejemplo: pueden crearse grandes sistemas ensamblando subsistemas reutilizables).
En este capítulo presentamos los principios y conceptos básicos que forman el
fundamento para la comprensión de la tecnología de objetos.
2. Factores de calidad del software
Algunos de los aspectos que han puesto de manifiesto las limitaciones de la
metodología clásica de desarrollo del software son: la evolución constante de las
aplicaciones informáticas para adaptarlas a nuevos requerimientos (tanto tecnológicos
como funcionales), la necesidad de reutilización para acortar tiempos de desarrollo, y la
capacidad de producir software más fiable y con un mayor grado mantenimiento.
Para atender estas necesidades y hacer que las aplicaciones informáticas sean viables
económicamente, es necesario facilitar la reutilización de programas minimizando el
trabajo de hacer adaptaciones a las funcionalidades nuevas. Un prerrequisito para
facilitar
la reutilización es que el software desarrollado responda a ciertos criterios o
factores de calidad.
El software básicamente es un mecanismo, que se usa para ejecutar rutinas de forma
automática y de forma enormemente rápida a través de un ordenador. Pero aparte de
estos aspectos, existen otros muy importantes como pudieran ser la fiabilidad con la que
se ejecutan estas rutinas por ejemplo, lo fácil que es interactuar con el programa o la
facilidad con la que se puede modificar o extender el programa para adecuarse a nuevos
requisitos.
4
Aunque hay muchas medidas de la calidad de software, la corrección, facilidad de
mantenimiento, integridad y facilidad de uso son algunos de los factores más
importantes.
Hace 25 años McCall y Cavano [MCC78] definieron un conjunto de factores de calidad
como los primeros pasos hacia el desarrollo de métricas de calidad del software. Estos
factores evalúan el software desde las siguientes dimensiones:
Operación del producto (utilizándolo).
Revisión del producto ( cambiándolo).
Transición del producto (modificándolo para que funcione en un entorno
diferente ( portándolo).
Para cada una de estas categorías, McCall definen una serie factores de calidad como
se muestra la siguiente figura:
Figura 1 Criterios de calidad de McCall
La siguiente tabla muestra un resumen con todos los factores de calidad del modelo de
McCall.
Factores de calidad McCall
Aspecto
que trata
Factor
Calidad
Relacionado con …
Operación
del Producto
Corrección
Fiabilidad
Eficiencia
Integridad
Facilidad
de uso
Hasta donde satisface un programa su especificación y logra los objetivos propuestos por
el cliente.
Probabilidad de operación libre de fallos de un programa en un entorno determinado
durante un tiempo específico.
Cantidad de recursos de computadora y de código requeridos por un programa para
llevar a cabo sus funciones.
Grado en que puede controlarse el acceso al software o a los datos, por personal
no autorizado.
Esfuerzo requerido para aprender un programa, trabajar con él, preparar su entrada
e interpretar su salida.
Revisión
del producto
Facilidad de
Mantenimiento
Flexibilidad
Facilidad
de prueba
Esfuerzo requerido para localizar y arreglar un error en un programa.
Esfuerzo requerido para modificar un programa operativo.
Esfuerzo requerido para probar un programa de forma que se asegure que realiza
la función requerida.
Transición Portabilidad Esfuerzo requerido para transferir el programa desde un hardware y/o entorno de
5
del producto
Reusabilidad
Facilidad de
interoperación
sistemas de software a otro.
Grado en que un programa (o partes de un programa) puede volverse a usar en otras
aplicaciones.
Esfuerzo requerido para acoplar un sistema a otro
La calidad no es tema desligado de los aspectos más técnicos, de hecho son algunos de
estos factores y su cumplimiento lo que impulsan determinadas mejoras en la forma de
abordar la construcción de un sistema software.
3. El paradigma de orientación a objetos
Un paradigma de programación hace referencia a como establecer, ver y resolver
problemas (en nuestro caso de programación). El paradigma orientado a objetos
establece el tipo de objeto como la unidad de representación y compresión de sistemas
software complejos.
El termino orientado a objetos sé uso para referirse al enfoque de desarrollo de software
que usaban lenguajes como ADA 95,C++, SmallTalk, etc. Hoy en día el paradigma
orientado a objetos encierra una visión completa de la ingeniería de software.
Hay muchas formas de enfocar un problema utilizando una solución basada en software.
El modelado y diseño orientado a objetos es una manera estructurar un sistema, en
la que se utilizan objetos para representar conceptos del mundo real. El dominio del
problema se representa mediante un conjunto de objetos con atributos y
comportamientos específicos. Los objetos son manipulados mediante una colección de
funciones llamados métodos y se comunican entre sí mediante un protocolo de
intercambio de mensajes.
Según Grady Booch: La POO es un método de implementación en el que los
programas se organizan como colecciones de objetos cooperativos, donde cada uno de
los cuales representa un instancia de alguna clase… .
En esta definición se pueden distinguir tres cuestiones:
1. La POO utiliza objetos cooperativos, no algoritmos (programación
estructurada), como bloques de construcción principales.
2. Visión cooperativa de objetos
3. Cada objeto es una instancia de una clase.
El concepto fundamental es el objeto, entendido como una combinación estructural
de datos y comportamiento en una única entidad. De esta forma podemos definir un
objeto como:
6
Un objeto es un módulo auto contenido de datos y operaciones que representa una
entidad tangible o intangible con significado en el dominio del problema.
De manera general la programación orientada a objetos divide el proceso en dos partes:
1. Definición de clases (tipos de objetos) y sus relaciones.
2. Puesta en funcionamiento de los objetos mediante la instanciación y el
intercambio de mensajes entre ellos.
El paradigma de orientación a objetos es atractivo debido a que favorece la creación y
reutilización de componentes independientes lo que va unido a factores de calidad
como son la reutilización y la extensibilidad, mantenimiento, etc. Además, los
componentes de software derivados mediante el paradigma de objetos muestran
características asociadas con el software de alta calidad, como la independencia
funcional, la ocultación de información, etc.
Para entender la orientación a objetos, es necesario abordar una serie de conceptos que
influyen en el diseño. Muchos son conceptos anteriores a la POO, pero que representan
una serie de importantes ideas que fundamentan las ventajas de la orientación de
objetos.
3.1. El principio de modularidad
La modularidad es la posibilidad de subdividir una aplicación en piezas más
pequeñas (denominadas módulos), donde cada una de las cuales debe ser tan
independiente como sea posible.
La programación modular se concibió como el desarrollo de programas a partir de
acoplar pequeñas piezas de código, denominadas subrutinas, que al mismo tiempo se
agrupaban en módulos cuando éstas trabajaban con los mismos datos.
El cumplimiento de muchos de los criterios de calidad vistos en el apartado anterior
depende en gran manera de la modularidad de las estructuras de representación, tanto de
código como de datos.
En este apartado profundizaremos en esta definición informal, y nos centraremos en
aquellas propiedades y características que debe tener un método para garantizar la
modularidad. Además discutiremos los criterios que se tienen que cumplir para que una
estructura de representación se pueda calificar de modular y, por lo tanto, permita una
metodología de desarrollo modular.
7
3.1.1. El modulo y su estructura
Pensemos en un gran bloque monolítico de código. Desde el punto de vista del
mantenimiento esta forma de codificar presenta numerosos problemas: la depuración de
errores se vuelve extremadamente difícil y la comprensión del código se vuelve
virtualmente imposible. La solución es dividir el bloque en otros bloques más
pequeños, conocidos como módulos.
La palabra modulo tiene diversas acepciones y significados. Un módulo es una unidad
claramente definida y manejable, con una interfaz definida. En otras palabras, es la
unidad mínima con sentido propio en un sistema software. No obstante vamos a ver una
serie de
definiciones de la palabra modulo.
Stevens, Myers y Constantine [1974] realizan un primer intento de definición. Ellos
definen un módulo “como un conjunto de una o más instrucciones contiguas
agrupadas con un nombre, por el cual otras partes del sistema pueden invocarlas y que
preferiblemente poseen su propio conjunto de variables”.
En otras palabras un módulo es un bloque de código que puede ser invocado como un
procedimiento, función o método.
Diccionario Webster .”Una pieza independiente de software que es parte integrante de
una u otras de mayor envergadura. Los módulos se compilan normalmente de forma
separada e independiente y proporcionan un mecanismo de abstracción o de ocultación
de información de forma que su implementación pueda ser cambiada sin afectar a otros
módulos.”
Un módulo se caracteriza fundamentalmente por su interfaz y por su
implementación. Parnas define el módulo como “un conjunto de acciones
denominadas, funciones o sub-módulos que corresponden a una misma abstracción,
que comparten un conjunto de datos comunes llamados atributos. Las acciones o
funciones de un módulo que son susceptibles de ser llamadas desde el exterior se
denominan primitivas o puntos de entrada del módulo”.
El concepto de modulo ha evolucionado a lo largo del tiempo junto con los lenguajes de
programación. Desde los subprogramas (funciones y procedimientos), pasando por lo
tipos abstractos de datos hasta el concepto de clase. El módulo ha ido cambiando de
forma. Pero en todos los casos su fin es el de proporcionar unidades auto contenidas,
independientes y que funcionen como si se permiten el símil como cajas negras.
Cada módulo tendrá un significado específico y debe asegurarse que cualquier cambio
en su implementación no afecte a su exterior (o al menos, que afecte lo mínimo
posible). De igual modo, se debe intentar asegurar que los errores posibles, condiciones
de límites o frontera, comportamientos erráticos, no se propaguen más allá del módulo
(o como máximo a los módulos que estén directamente relacionados).
8
La Figura 2 muestra un esquema de las partes más importantes de un módulo
idealizado desde el punto de vista del tipo abstracto de datos o clase.
Primitivas de acceso y manipulación
Atributos
Algoritmos
Parámetros
Interfaz
Sección privada
Figura 2 Estructura de un módulo
3.1.2. Características de la programación modular
La modularización es una técnica de división como estamos viendo, que permite al
programador resolver un problema, dividiéndolo en sub-problemas más pequeños, de
forma que se puedan resolver independientemente unos de otros, para después
combinarlos.
3.1.3. Tamaño del modulo
Al dividir u organizar de forma modular se deberá decidir la envergadura del módulo.
Existen módulos grandes y módulos pequeños, pero una característica esencial es que el
módulo sea auto contenido, completo, cohesivo y con el menor acoplamiento posible
respecto a otros módulos, de forma que se pueda integrar con otras partes y que se
pueda considerar para su corrección de forma aislada.
3.1.4. Ocultación de la información
En la etapa de diseño de un módulo, es imprescindible especificar el conjunto de las
propiedades del módulo que constituirán la información a la cual tendrán acceso los
otros módulos y a cuáles no. Estas propiedades las podemos dividir en dos tipos,
propiedades públicas y propiedades privadas:
Las propiedades públicas son aquellas que son visibles desde fuera del módulo
tanto para los usuarios como para otros módulos .Serán la interfaz del modulo
En cambio, las propiedades privadas son aquéllas que son internas al módulo,
por lo que su visibilidad es exclusivamente dentro del módulo y estará oculta al
exterior.
9
3.2. Diseño de módulos. Acoplamiento y cohesión
Aunque el diseño modular persigue la división de un sistema grande en módulos más
pequeños y manejables, no siempre esta división es garantía de un sistema bien
organizado. Muchos aspectos de la modularización pueden ser comprendidos solo si se
examinan módulos en relación con otros. Los módulos deben diseñarse con los
criterios de acoplamiento y cohesión. El primer criterio busca la menor dependencia
posible entre módulos y el segundo busca que todo lo agrupado bajo el modulo
obedezca al mismo propósito. En este sentido Booch define la modularidad como :”la
propiedad de un sistema que ha sido descompuesto en un conjunto de módulos
cohesivos y débilmente acoplados”.
Myers[1978] define cohesión y acoplamiento de la siguiente forma:
El acoplamiento entre módulos como el grado de interdependencia entre dos
módulos.
La cohesión como el grado de interacción dentro de un módulo.
Veremos a continuación estos dos conceptos tan importantes en el diseño de módulos
3.2.1. Acoplamiento
El acoplamiento es una medida de interdependencia entre módulos. Se dice que
existe alto acoplamiento cuando el cambio de un modulo fuerza al cambio de los que
dependen de él. El objetivo es hacer que está interdependencia sea mínima.
En la siguiente Figura (Figura 3) el módulo C1 tienen un acoplamiento relevante con
C10, si un cambio en C10 implica un cambio en C1.
C1
C10
Figura 3 Interdependencia entre módulos
Como el acoplamiento se puede producir sólo en las relaciones entre módulos, cuantas
más relaciones tenga un módulo, mayor probabilidad habrá que se produzcan
acoplamientos. Por lo tanto podemos extraer las siguientes conclusiones o reflexiones:
10
Cuantas menos conexiones existan entre módulos, menos oportunidad habrá
para que se produzca el «efecto onda», es decir, que un defecto en un módulo
pueda afectar a otro.
Cuanto menos acoplados estén los módulos menos implicaciones o efectos
colaterales podrán tener los cambios realizados.
A la vista de los anterior podría deducirse que una buena medida de diseño que evita el
acoplamiento sería la de limitar al máximo las conexiones entre módulos. Esto en
parte es cierto, ya que a menor número de relaciones menor probabilidad de verse
afectado por cambios en otros módulos, pero muchas veces es inevitable y es necesario
que un módulo se comunique con otros muchos y no necesariamente aumenta su
acoplamiento. Para entender esto hay que considerar además, el grado de exposición
que tiene un módulo respecto a los detalles internos de otro.
Pensemos en dos módulos M1 y M2. Supongamos que M2 es un módulo que representa
un almacén de medidas de temperatura, y ofrece capacidades a otros módulos para que
puedan ser extraídas en orden a como se obtuvieron. Y supongamos que M1es un
módulo estadístico que opera sobre estas medidas a través de M2. Imaginemos que M1
“conoce” el tamaño del array que tiene M2 para guardar las medidas. Si este tamaño
cambiase en M2, el cambio afectaría de manera irreversible a M1. Esto es debido a que
M1 tiene un fuerte acoplamiento con M2, no sólo por su relación con M2 sino también
por su exposición a los detalles internos de M2 de cómo funciona. La conclusión es que
el grado de acoplamiento entre módulos no necesariamente está solamente en función
del número de conexiones entre módulos.
El objetivo por tanto es obtener módulos débilmente acoplados.
Un acoplamiento bajo indica un sistema bien particionado y puede obtenerse de dos
maneras:
Reduciendo el número de relaciones: Cuanto menos conexiones existan entre
módulos, menor será la posibilidad del efecto en cadena (un error en un módulo
provoca otro colateral en otro modulo).
Debilitando el nivel de dependencia en las relaciones necesarias: Ningún
módulo debería depender de los detalles internos de implementación de
cualquier otro. Lo único
que tiene que conocer un módulo de otro es como
invocarlo y como interpretar el resultado obtenido.
La siguiente tabla muestra la clasificación de acoplamientos entre módulos y su grado
de acoplamiento desde un punto de vista de la programación estructurada.
Tipo de acoplamiento Grado de acoplamiento Facilidad de mantenimiento
Por contenido Alto (fuerte) Bajo
Común | |
De control | |
Por (Estampado) | |
Datos ↓ ↓
Bajo (débil) Alto
11
Las ventajas de un sistema débilmente acoplado son muchas. Tal como define Booch,
un sistema modular débilmente acoplado facilita:
La sustitución de un módulo por otro, de modo que los módulos afectados por
un cambio serán menos.
El seguimiento de un error y el aislamiento del módulo defectuoso que produce
ese error.
3.2.2. Cohesión
Otro medio para evaluar la partición en módulos (además del acoplamiento) es observar
como las actividades dentro de un módulo están relacionadas unas con otras; este es el
criterio de cohesión.
La cohesión tiene que ver con el grado de relación que tienen los elementos internos de
un módulo, o más generalmente, la forma en la que agrupamos elementos en una
unidad conceptual de mayor nivel. Por ejemplo, la forma en la que agrupamos
funciones en una librería, o la forma en la que agrupamos métodos en una clase, o la
forma en la que agrupamos clases en una librería, etc. Su objetivo es organizar los
elementos de tal manera que los que tengan más relación a la hora de realizar una tarea
pertenezcan al mismo módulo, y los elementos no relacionados figuren en módulos
separados.
La cohesión también se puede definir corno la medida de la relación funcional de los
elementos de un módulo; entendiendo por elementos, tanto la sentencia o grupo de
sentencias que lo componen, como las definiciones de datos o las llamadas a otros
módulos. Idealmente, un módulo coherente sólo debe hacer una única cosa.
El objetivo es diseñar módulos con una alta cohesión, cuyos elementos estén fuerte y
genuinamente relacionados unos con otros.
Myers define siete categorías o niveles de cohesión. La siguiente tabla muestra estos
grados de cohesión: baja cohesión (no deseable) y alta cohesión (deseable).
Tipo de cohesión Grado de cohesión Grado de mantenimiento
Por coincidencia Bajo Bajo
Lógica | |
Temporal | |
Procedimental | |
Por comunicaciones | |
Secuencial ↓ ↓
Funcional ↓ ↓
Alto
Alto
Tabla. Clasificación de cohesión de módulos
12
4. Tipos de datos y Tipos abstractos de datos
Todos los lenguajes de programación soportan el concepto de tipo de datos. Por
ejemplo, el lenguaje de programación C soporta los tipos int, float, char, así como los
tipos: colección y estructuras ( struct).
Un tipo de datos define un conjunto de valores posibles y un conjunto de operaciones
definidas para ese tipo de valores.
Por ejemplo un tipo entero define un conjunto de datos posibles (números enteros
negativos y positivos) además de las operaciones permitidas sobre ellos (las operaciones
aritméticas).
Los tipos abstractos de datos (TAD), vistos en el siguiente apartado, extienden la
función de los tipos de datos tradicionales .Y tienen la capacidad de ocultar detalles
internos de su implementación o funcionamiento. Esta capacidad de ocultar la
información permite el desarrollo de componentes de software reutilizables y
extensibles.
El tipo constituye así la forma en la cual un desarrollador modela las entidades de la
realidad que intenta representar y manipular y para el compilador representa la manera
de reservar memoria y saber cómo manipular determinados valores. Los lenguajes
modernos suelen permitir definir al programador sus propios tipos de datos permitiendo
así una mayor flexibilidad a la hora de representar entidades.
5. De los módulos a los tipos abstractos de datos.
Se ha ido avanzando en la independencia de conjuntos de instrucciones para hacerlas
no solamente una unidad funcional independiente, sino que esta una unidad funcional
sea cada vez de mayor envergadura. Los procedimientos y funciones pueden
considerarse las primeras aproximaciones a la programación modular, los subprogramas
(funciones o procedimientos) pueden ser vistos como módulos. Posteriormente el tipo
abstracto de datos ha constituido un avance significativo en la construcción de unidades
modulares más completas al unir estructuras de datos y funciones en una misma
unidad, permitiendo reducir el acoplamiento y aumentando la cohesión.
Una idea fundamental en la abstracción de los tipos abstractos de datos es la
separación de la especificación del tipo y su implementación interna. Ya que si la
representación interna se oculta, el modulo que llama o que usa esta unidad funcional
tendrá un acoplamiento muy bajo.
13
Para ver más en detalle esto, vemos que en esencia un TAD es un tipo de dato que
consta de datos y operaciones definidas para estos datos.
T.A.D= Datos+operaciones (funciones y procedimientos)
En el siguiente ejemplo se muestra un TAD que ofrece la característica de
ocultamiento y encapsulación estando compuesto de dos partes: interfaz pública e
implementación interna.
Ejemplo de un TAD. Definición e interfaz
TAD ListaCrtlProcesos:
Datos internos
Lista: Proceso
Operaciones accesibles
AñadirProc ( Proceso)
Proceso sacarProc ( )
Proceso obtenerPrimerProceso();
Implementación interna
AñadirProc ( Proceso){
Lista.add(Proceso);
}
Proceso sacarProc (
(Proceso)Lista.obtenerPrimerProceso();
)
Entre las ventajas que ofrecen los tipos abstractos de datos podemos destacar las
siguientes:
Permiten una mejor conceptualización del mundo real. Las estructuras de datos
complejas como son los TAD permiten mejorar la representación, mejorando
con ello la comprensión.
Separa la implementación de la especificación con lo que disminuye el
acoplamiento.
Favorece el diseño cohesivo de módulos puesto que obliga a plantear de
manera conjunta datos y operaciones.
Aumenta la reusabilidad de unidades funcionales puesto que estas se han
concebido como independientes, no acopladas y cohesivas.
14
6. Clases y Objetos
La clase unifica los principios del diseño modular y la definición de tipo de datos. La
clase en los lenguajes orientados a objetos es la unidad mínima de descomposición
funcional del software (módulo).
Desde el punto de vista de los tipos de datos. Una clase no es un tipo abstracto de datos
compuesto por datos y operaciones. Desde el punto de vista del diseño y la
programación modular, una clase es un módulo. La clase lleva al límite las ventajas de
ofrecidas por los tipos abstractos de datos: bajo acoplamiento y alta cohesión. Desde un
punto de vista cognitivo una clase representa una abstracción del mundo real,
caracterizada por un conjunto de atributos y por el conjunto de operaciones que admite.
En principio una clase puede representar casi cualquier cosa, una aproximación de lo
que representa una clase sería la siguiente:
Una clase representa una generalización de una serie de objetos que tienen el mismo
conjunto de características.
Como vemos, íntimamente relacionado con el concepto de clase, está el de objeto. El
objeto representa una instancia particular, es decir un ejemplar especifico de una clase
(Por ejemplo de la clase archivos, un SSOO puede tener los siguientes ejemplares:
cab234.txt (3Mb), runtime.exe(2Kb))
A veces, es difícil diferenciar los conceptos de clase y objeto. Para ver mejor esto con
un ejemplo, cuando hablamos de un objeto cuenta corriente, todo el mundo entiende
que nos referimos a un concepto que sirve para reflejar el dinero que una
persona tiene
en el banco, a través de un saldo, un código de identificación, y una serie de titulares.
En este caso concreto, cuenta corriente se debe concebir como una clase de objetos, ya
que representa un grupo o conjunto de entidades con ciertas características comunes
(número, saldo, titulares).La clase describe como es una cuenta corriente para ser
considerada como tal.
Cuando hablamos de la cuenta corriente 6773-0100-89-2223873, con saldo 10.000
euros, titulares Juan Antonio y Rosa , nos referimos a una cuenta en concreto. En este
caso, esta cuenta corriente se debe entender como un objeto particular o más
concretamente en programación, como una instancia de la clase Cuenta Corriente que
identifica un miembro individual y concreto de la clase de objetos descritos.
La clase describe un grupo de objetos que comparten un mismo conjunto de
características, mientras que el objeto es un representante particular de este grupo.
Así podemos ver que una clase es una descripción generalizada, como por ejemplo una
plantilla, que describe una colección de objetos similares. Por definición, todos los
objetos que pertenecen a una clase deben tener sus atributos y las operaciones
disponibles para la manipulación de los atributos.
15
La relación entre clases y objetos es una relación de pertenencia. Así un objeto se dice
que pertenece a una clase. Enfocado desde un punto de vista más técnico el siguiente
ejemplo muestra como un objeto instancia a una clase o dicho de otra forma una
variable de tipo clase recibirá como valor un objeto concreto.
Ejemplo de instancias de la clase cuenta
Objetos
El manual de referencia de UML (The UML Reference Manual [Rumbaugh] define un
objeto como: “Una entidad discreta con limites perfectamente definidos, que
encapsula un estado y un comportamiento instancia de una clase”.
Según (Booch). “Un objeto es un conjunto cohesivo de datos y funciones. “
Cuando hablamos de objetos, hacemos referencia a un conjunto de datos concretos que
pueden cambiar, mientras que cuando hablamos de clase hacemos referencia a como es
la estructura que representa a esos objetos y que se usa como plantilla para crearlos. En
este sentido la clase es un concepto estático, mientras que el objeto es un concepto
más dinámico.
En programación orientada a objetos una instancia se produce con la creación de un
objeto perteneciente a una clase . A diferencia de una variable convencional, un objeto
no se crea simplemente definiendo una variable hay que llamar a un método
especial denominado constructor (visto más adelante) que indica la forma de crear
el objeto. Para ver esto en la siguiente línea de código Java se crea un objeto cuenta
corriente.
CuentaCorriente cc01= new CuentaCorriente(Juan,Casas Fuente,5000);
http://es.wikipedia.org/wiki/Objetos_%28programaci%C3%B3n_orientada_a_objetos%29
http://es.wikipedia.org/wiki/Clase_%28inform%C3%A1tica%29
16
En nuestro caso cc01 es una instancia de la clase CuentaCorriente. O dicho de otra
forma cco1 es un objeto de la clase CuentaCorriente En la documentación
utilizaremos instancia, objeto o ejemplar indistintamente.
Representación conceptual y representación a nivel de lenguaje de programación
de una clase.
Conceptualmente y de forma general una clase se caracteriza por un nombre de clase,
un conjunto de atributos y un conjunto de métodos. La siguiente figura (Figura 4)
muestra una representación simbólica de una clase en la notación UML ( Unified
Modeling Language ver anexo I).
+Op1()
+OpN()
-Atributo 1
-Atributo N
Nombre Clase
Figura 4 . Representación UML de una clase
A nivel del lenguaje de programación la clase debe representarse con los elementos
propios que proporcionan los lenguajes de programación. En concreto para Java y C++
seria de la siguiente forma.
Representación y especificación de una clase en Java:
public class Ordenador {
//atributos
String marca;
String modelo;
String procesador;
int memoria;
float frecuencia;
boolean encendido;
//metodos
public Ordenador(){}
public Ordenador (String mar,String mod,String micro,int men,float fre){
marca=mar;
modelo=mod;
procesador=micro;
memoria=men;
frecuencia=fre;
}
public void encender(){
if (!encendido)
encendido=true;
}
}
Representación y especificación de una clase en C++
Archivo Ordenador.h
17
class Ordenador
{
private:
char * marca;
char * modelo;
char * procesador;
int memoria;
float frecuencia;
bool encendido;
public:
Ordenador();
Ordenador(char *mar,char *mod,char *micro,int men,float fre);
void encender();
~Ordenador();
};
Archivo Ordenador.cpp
#include "ordenador.h"
Ordenador::Ordenador()
{
}
Ordenador::Ordenador(char *mar,char *mod,char *micro,int men,float fre)
{
marca=mar;
modelo=mod;
procesador=micro;
memoria=men;
frecuencia=fre;
}
void Ordenador::encender(){
if (!encendido)
encendido=true;
}
Ordenador::~Ordenador()
{
}
Como se puede ver el mismo concepto es representado de forma diferente en los
diferentes lenguajes. Pero ambas representaciones comprenden elementos comunes
como son atributos y métodos.
6.1. Atributos
Cualquier objeto o entidad existente se puede describir a partir de una serie de atributos
.Una clase puede tener un número arbitrario de atributos que definen las características
más relevantes en el dominio del problema y que identifican unívocamente el tipo de
objetos que está representado.
Un atributo es una característica o propiedad de una entidad
Un atributo toma valor en un dominio de información, que define el tipo de valores
válidos en la interpretación de ese atributo .Por ejemplo, suponga que una clase
18
denominada Coche con el atributo color. El dominio de valores de color podría ser
(blanco, negro, plata, gris, azul, rojo, amarillo, verde).
6.2. Estado de un objeto.
El estado de un objeto es el valor de sus atributos observados en un instante de tiempo
concreto.
En un instante de tiempo determinado un objeto tendrá una serie de valores en todos y
cada uno de sus atributos, al conjunto de valores que toman los atributos de un objeto
en un instante de tiempo dado se denomina estado del objeto. Como es evidente el
estado de un objeto puede variar a lo largo del ciclo de vida del objeto en el programa.
El concepto de estado define los valores transitorios y estables por los que puede pasar
un objeto, de forma que se puede determinar la corrección de un sistema a partir del
conjunto de estados alcanzado por los objetos del sistema.
6.3. Mensajes ,operaciones y métodos
Cuando se diseña una clase, la estructura interna y los detalles de implementación se
ocultan, con la posibilidad de intercambiar mensajes como la única posibilidad de
conexión.
Datos
Métodos
Mensajes
Objeto
En un modelo de clases y sus objetos, los métodos (operaciones o servicios) asociados
a estas clases describen el comportamiento asociado a un objeto.
Cada método tiene un nombre y un cuerpo que realiza la acción o comportamiento
asociado con el nombre del método. Un método dentro de un objeto se activa por un
mensaje que se envía por otro objeto al objeto que contiene el método.
19
O1
O2
O4
1.me
nsaj
e
2.Activación del
servicio/operación
asociado a mensaje
6.3.1. Comportamiento de un objeto
Cuando un objeto recibe algún mensaje, siempre inicia algún tipo de procesamiento.
Cada una de las operaciones que tiene la clase proporciona una representación del
comportamiento de los objetos a los que representa. Por eso el conjunto de
operaciones que admite un objeto o clase de denomina comportamiento del objeto, y
está íntimamente relaciona con la funcionalidad que puede realizar.
Por ejemplo, dada la siguiente clase llamada Punto que representa puntos en un espacio
bidimensional y que podemos representar de la siguiente forma:
Punto
X:Real
Y:Real
Punto (X:Real,Y:Real)
mover(X1:Real,Y1:Real)
La consecuencia de la existencia de la operación mover(X1:Real,Y1:Real) es que la
clase punto ha sido diseñada para recibir un estimulo (llamaremos a este estímulo
mensaje).Cada vez que un objeto recibe un mensaje/estimulo inicia un comportamiento.
El comportamiento especificado para un objeto instancia de esta clase es moverse a un
desplazamiento determinado. El método para moverse vendrá implementado en el
método.
6.3.2. Método
Un método es la especificación e implementación de un servicio u operación que puede
ser requerido a cualquier objeto de la clase. El método muestra, o define, parte del
comportamiento de los objetos representados en la clase.
Un método se implementa en una clase de objetos, e indica cómo debe actuar el objeto
cuando recibe el mensaje vinculado a ese método. Por ejemplo el método para mover un
punto es incrementar en X1 e Y1 las coordenadas del punto. A su vez un método
también puede enviar mensajes a otros objetos.
20
Los métodos describen el comportamiento asociado a un objeto. Debemos considerar
que la ejecución de un método puede conducir a un cambio de estado del objeto.
6.3.3. Definición de un Método
Un método se define por un nombre, por los argumentos o parámetros de entrada que
necesita y por el valor de retorno que se obtiene al ejecutar el comportamiento que
implementa. Estos elementos (valor de retorno, nombre y parámetros) de un método se
conocen como prototipo del método o de una función miembro.
NombreMetodo (parametro1 Tipoparam1..., parametroNTipo paramN)Tipo de retorno
La implementación define el comportamiento del objeto ante la llamada del método
NombreMetodo (parametro1Tipo param1..., parametroNTipo paramN)Tipo de retorno
<Implementación del método>
Podemos distinguir de manera informal varios tipos de métodos dentro de una clase:
Métodos constructores y destructores de objetos.
Métodos de acceso a propiedades: métodos set (establecer) y get (obtener)
Métodos de servicio (en general).
La primera categoría representa los métodos dedicados a cómo crear y eliminar objetos
en. Veremos más adelante estos dos métodos en profundidad.
La segunda categoría corresponde un tipo de métodos muy comunes en cualquier
clase. Los métodos “set y get“ están diseñados para obtener y establecer el valor de los
atributos de un objeto. Un ejemplo típico de este tipo de métodos lo podemos encontrar
en la clase Ordenador anteriormente comentada. Para esta clase se podría haber
diseñado los siguientes métodos set y get.
Ejemplo de métodos set y get para la clase Ordenador.java
public class Ordenador {
private String marca;
private String modelo;
private String procesador;
private int memoria;
private float frecuencia_micro;
private boolean encendido;
public Ordenador(){}
public Ordenador (String mar,String mod,String micro,int men,
float fre){
marca=mar;
modelo=mod;
procesador=micro;
memoria=men;
frecuencia_micro=fre;
}
21
public void encender(){
if (!isEncendido())
setEncendido(true);
}
public String getMarca() {
return marca;
}
public void setMarca(String marca) {
this.marca = marca;
}
public String getModelo() {
return modelo;
}
public void setModelo(String modelo) {
this.modelo = modelo;
}
public String getProcesador() {
return procesador;
}
public void setProcesador(String procesador) {
this.procesador = procesador;
}
public int getMemoria() {
return memoria;
}
public void setMemoria(int memoria) {
this.memoria = memoria;
}
public float getFrecuencia() {
return frecuencia;
}
public void setFrecuencia(float frecuencia_micro) {
this.frecuencia = frecuencia_micro;
}
public boolean isEncendido() {
return encendido;
}
public void setEncendido(boolean encendido) {
this.encendido = encendido;
}
}
22
Es muy importante asignar los valores de los atributos de un objeto siempre por medio
de los métodos de acceso correspondientes, ya que permiten modificar la estructura
interna de la clase sin modificar las interfaces de comunicación con las otras clases y,
por lo tanto, no habrá que modificarlas cuando se realicen cambios que sólo afecten a la
estructura interna. De esta manera, podemos mantener el principio de encapsulamiento
y ocultación.
Dentro de la última categoría denominada métodos de servicio agrupamos todos
aquellos métodos que definen el comportamiento restante de la clase.
6.3.4. Mensajes
Los objetos generan un comportamiento enviándoles mensajes, esto es lo que se
denomina colaboración.
Cuando se crea un programa orientado a objetos, los objetos recibirán, interpretarán y
responderán a mensajes de otros objetos. Usando la terminología presentada en la
sección precedente, un mensaje estimula la ocurrencia de cierto comportamiento en el
objeto receptor.
6.4. Ocultación de información .Visibilidad de los atributos y métodos
Al definir una clase se debe establecer el grado de visibilidad u ocultamiento que va
a tener su estructura respecto al exterior. La visibilidad define por tanto el grado de
acceso que tendrá una clase respecto a otra.
Para poder establecer claramente la visibilidad de los atributos y métodos, los lenguajes
de programación normalmente definen tres niveles de acceso distintos.
Público (public): cualquier clase puede acceder y utilizar cualquier atributo o
método declarado como público de otra clase.
Privado (private): ninguna clase puede acceder a un atributo o método
declarado como privado ni utilizarlo.
Existe un tercer nivel denominado Protegido (protected): cualquier clase heredera
puede acceder a cualquier atributo o método declarado como protegido en la clase padre
y utilizarlo.
Por ejemplo la clase java ordenador.
public class Ordenador {
private String marca;
private String modelo;
private String procesador;
private int memoria;
private float frecuencia_micro;
private boolean encendido;
23
//…
public String obtenerMarca() {
return marca;
}
public void asignarMarca(String marca) {
this.marca = marca;
}
}
En la clase ordenador del ejemplo anterior sólo se puede acceder al atributo marca a
través de los métodos obtenerMarca() y asignarMarca() , ya que por defecto todos los
atributos son prívate(privados) y no pueden ser accedidos desde fuera.
6.5. Propiedades de instancia y propiedades de clase
Como ya hemos mencionado el concepto de clase y objeto aunque están íntimamente
relacionados, guardan
ciertas diferencias importantes.
A la hora de definir los atributos de una clase se puede especificar que determinados
atributos y sus valores sean de clase y no de cada objeto, significa que serán
compartidos y comunes a todos los objetos de una clase y que su valor no depende del
objeto en particular. Por ejemplo en la case ordenador podríamos definir periodo de
garantía como nuevo atributo para la clase, pero si nos fijamos bien este atributo es
compartido por todos los tipos y modelos de ordenadores, luego se podría pensar en el
él como un atributo de clase, no de instancia.
Ejemplo de una atributo de Clase
public class Ordenador {
static int periodo_garantia;
private String marca;
private String modelo;
private String procesador;
private int memoria;
private float frecuencia_micro;
private boolean encendido;
}
Los lenguaje de programación (java y C++) permiten esta construcción através del
modificador static.
6.6. Los métodos constructor y destructor de una clase
A diferencia de una variable convencional, cuando se define una variable y como tipo
una clase, está variable no es un objeto hasta que no se construya (necesita tener
asignados una serie de valores en sus atributos). El compilador necesita saber cómo
construir y posteriormente destruir los objetos de una clase.
24
Los métodos constructores y destructores de una clase definen como construir un
objeto y como destruirlo.
Constructor de clase
El objetivo del constructor es el de inicializar un objeto a través de sus atributos
cuando éste es creado. El siguiente ejemplo muestra la creación de un objeto Circulo
con un determinado radio.
Java
Circulo c1= new Circulo(10);
C++
Circulo *c1= new Circulo(12);
Cuando se crea un objeto (instanciación), se pasan los valores de los parámetros al
constructor utilizando una sintaxis similar a una llamada a un método.
Un constructor es el mecanismo que disponen las clases para inicializar los objetos.
Cuando se ejecuta la instrucción anterior el compilador reservará memoria para albergar
un objeto instancia de la clase Circulo y llamará al código asociado el constructor de
esta clase.
Un constructor se ejecuta cuando se crea una instancia de una clase. El constructor tiene
como propósito la inicialización de los datos miembro o atributos del objeto de la
clase.
Podemos ver un constructor como un procedimiento con las siguientes características
Es un método obligatorio de la clase
Tiene el mismo nombre que la clase
Puede tener cualquier número de argumentos (incluso ninguno).
Puede haber más de un constructor, es decir distintas maneras de crear un objeto
Formato general de un constructor de clase:
NombleClase(lista de argumentos)
{
// Instrucciones de inicialización
}
Ejemplos de constructores en los distintos lenguajes:
Java
public class Contador {
int n;
Contador(){n=0;};
Contador( int v_ini){
n=v_ini;
}
}
http://es.wikipedia.org/wiki/Objeto_%28programaci%C3%B3n%29
25
Ejemplo de constructors en C++
class Contador
{
private:
int n;
public:
Contador(){ n=0;};
Contador (int v_ini){n=v_ini ;}
~Contador();
};
La construcción de un objeto consta de tres etapas:
Se reserva espacio en memoria para la estructura de datos que define la clase.
Se inicializa los campos de la instancia con los valores por defecto. Garantiza
que cada atributo de una clase tenga un valor inicial antes de la llamada al
constructor
Se inicia el código definido por el constructor de clase.
Tipos de constructores
Generalmente existen dos tipos de constructores para una clase
Constructor por defecto (sin argumentos/vacio).
Constructor con argumentos
Constructor por defecto es aquel que inicializa los atributos de un objeto a su valor
por defecto.
Constructor con argumentos es un constructor que recibe a través de argumentos los
valores de inicialización de los atributos del objeto.
En el ejemplo visto anteriormente de la clase contador C++ vemos estos tipos de
constructores.
Ejemplo en C++ de constructor por defecto y con argumentos
class Contador
{
private:
int n;
public:
Contador(){ n=0;}; //constructor por defecto
Contador (int v_ini){n=v_ini ;} //constructor con argumentos
~Contador();
};
Instanciación de la clase Contador
#include <cstdlib>
#include <iostream>
#include "contador.h"
using namespace std;
int main(int argc, char *argv[])
26
{
Contador *cont1= new Contador();
Contador *cont2=new Contador(2);
cont1->incrementar();
cont2->incrementar();
cout<<cont1->getValor();
cout<<cont2->getValor();
system("PAUSE");
}
Destructor
Si lo que queremos es eliminar un objeto previamente creado con un constructor habrá
que llamar directa o indirectamente al método denominado destructor. Aquí también
existen diferencias notables entre los lenguajes. Mientras C++ requiere una llamada
explicita al destructor del objeto, otros lenguajes como Java realizan esta tarea de forma
automática a través de un proceso especial llamado recolector de basura ( garbage
collector) liberando al programador de esta responsabilidad. Aunque la destrucción se
puede realizar explícitamente si se desea.
La primera aproximación si se realiza bien garantiza la liberación de memoria y el
momento de la liberación. La segunda sin embargo libera la memoria pero no ofrece
garantías de cuando se hará.
poligono p1 = new Poligono(10,20,40,15,50,20);
poligono p2 = new Poligono(10,30,20,40,50,60);
p1 = p2; //El objeto p1 deja de estar referenciado , puede ser liberado por
el sistema.
p1 = null;// null: palabra reservada; se utiliza para indicar referencia nula.
El objeto Poligono(10,30,20,40,50,60); deja de estar referenciado
(identificado), puede ser liberado por el sistema.
Destrucción de objetos
Java
cont=null;
C++
delete cont;
Las ventajas que se obtienen de una liberación a través de recolector de basura son la
de una liberación más segura de la memoria reservada, ya es frecuente que los
programadores no borren explícitamente los objetos creados (por descuido), así se
consigue un ahorro significativo en el espacio utilizado.
Ejemplo de destrucción explicita en C++ del objeto Contador
#include <cstdlib>
#include <iostream>
#include "contador.h"
using namespace std;
27
int main(int argc, char *argv[])
{
Contador *cont1= new Contador();
Contador *cont2=new Contador(2);
cont1->incrementar();
cont2->incrementar();
cout<<cont1->getValor();
cout<<cont2->getValor();
delete cont1;
delete cont2;
system("PAUSE");}
Programacion Orientada a Objetos 2015-2016 Campus/Tema 2-parcticas obligatorias 2013_2014 BLOQUE II.pdf
Página 1
UPSAM 2012-2013 Laboratorio de prácticas
Programación. Tema 2 Bloque II Prácticas obligatorias
Normativa:
Las siguientes prácticas son obligatorias para aprobar la asignatura y tienen que ser entregadas
y explicadas antes de la fecha fijada por el profesor.
El material a entregar será un diagrama UML y el código fuente de la práctica en el lenguaje
requerido.
No se corregirán prácticas que no compilen.
Las prácticas se realizará de forma individual.
Página 2
Práctica 1. C++
Se desea diseñar un Sistema de colas con prioridades. Para ello considere el siguiente diagrama.
M
Insertar(
t ) extraer() : t
FIFO
SISTEMA DE COLAS CON PRIORIDADES
COLA PRIORIDAD ALTA
Trabajo{
operacion,
prioridad
}
COLA PRIORIDAD NORMAL
Donde
M es la capacidad máxima de cada cola (Cola prioridad alta (CPA) y Cola prioridad normal
(CPN)). Tome M=5 para las prácticas
FIFO (First in, First out ) es la disciplina que usa el sistema de gestión de la cola: primero en
entrar, primero en salir
Trabajo representa un trabajo con el nombre de una operación (Ej” OP1” )y una prioridad
asociada (ALTA o NORMAL)
Las operaciones permitidas sobre el sistema de colas son:
insertar( Trabajo ): Inserta un trabajo en el sistema de colas, si el trabajo es de prioridad ALTA
irá a la cola denominada CPA, sino a la cola denominada CPN
extraer (): Extrae el trabajo a ejecutar en función de la disciplina de cola empleada (en este
caso FIFO) y considerando que se deben sacar primero los trabajos con prioridad alta. Un
trabajo cuando es sacado desaparece del sistema
estado(): Devuelve el número de mensajes dentro del sistema de colas
Se pide
Diseñe mediante una clase el sistema anterior y realice una representación en UML
Implemente en C++ la clase anterior junto con la clase Mensaje
Realice la siguiente prueba mediante un programa principal
o Cree una instancia del sistema de colas
o Cree tres mensajes: m1( “OP1”,prioridad =alta), m2 ( “OP2”,prioridad =alta), m3(
“OP3”,prioridad =normal)
o Envie al sistema multi-cola los tres trabajos en el orden anterior
o Imprima por pantalla el estado actual del sistema de colas
o Ejecute la operación extraer dos veces del sistema de colas
o Imprima por pantalla el estado actual del sistema de colas
Página 3
Práctica 3. Java
Dentro de la red la Internet, encontramos múltiples dispositivos encargados de gestionar el trasporte de
la información entre los nodos de la red. Los enrutadores o encaminadores (Routers) se encargan de
gestionar las múltiples rutas que puede seguir esta información. Un Router es un elemento de red que
decide que enlace va a seguir el datagrama. Queremos modelar de manera muy simple el
comportamiento de este componente. Para nosotros un Router tendrá las siguientes características:
Dos enlaces de salida.
o Enlace 1=”193.146.145”
o Enlace 2=”193.132.128”
Un buffer que permite almacenar temporalmente los datagramas entrantes. Con una
capacidad limitada a N datagramas IP.
Las operaciones para esta clase serán las siguientes:
Función Descripción
almacenar(DatagramaIP) Sólo se aceptarán datagramasIP si
DataGramaIP direcc_Red_Destino=(Enlace 1 o Enlace2)
o existe capacidad de almacenamiento suficiente
NOTA: devuelve false si no se ha podido almacenar
encaminar Por cada datagramaIP en el buffer
Si DataGramaIP direcc_Red_Destino =Enlace 1 encaminar por el
enlace uno
Si DataGramaIP direcc_Red_Destino =Enlace2 encaminar por el
enlace dos
Sino error interno
El datagramaIP cuando es encaminado cambia su valor de TTL a TTL-1
Nota: IMPRIMA POR PANTALLA el resultado del enrutamiento para
poderlo verlo después en las pruebas
estado de Buffer Numero de datagramas en el buffer
constructor Parámetros Enlaces de salida y capacidad máxima
IMPORTANTE
Represente las direcciones de red de los enlaces como cadenas.
La capacidad se puede indicar con una constante de la siguiente forma
private final int CAPACIDAD=3;
Página 4
Para representar el buffer use el tipo abstracto Vector de Java SDK
Se pide:
1. Diagrama UML de la clase Router
2. Realice la siguiente prueba sobre la clase Router
o Crear un Router con la siguiente configuración(Enlace 1=”193.146.145”,Enlace
2=”193.132.129”,CAPACIDAD=10)
a. Crear tres datagramas con los siguientes datos
IPorigen IPdestino TTL Longitud total DF
Datagrama1 193.123.134.34 193.130.129.34 3 35000 1
Datagrama2 193.123.134.34 193.146.145.56 2 34000 0
Datagrama3 193.123.134.34 193.130.129.58 3 32000 1
b. Almacene cada datagrama en el Router
c. Obtenga el valor de estado del Router
d. Invocar a la operación encaminar() del Router
e. Obtenga el valor de estado
f. Cree ahora una prueba donde se sature la capacidad del router
Programacion Orientada a Objetos 2015-2016 Campus/Tema 2 Prácticas guiadas c__ V2.pdf
Programación 2011-2012 UPSAM
Laboratorio de Programación UPSAM
Tema 2: Práctica guiadas C+++
Nota: Este documento constituye material de apoyo a las prácticas obligatorias que
posteriormente se deberán entregar.
Proceso de creación de un proyecto de programación en Dev C++
1. Crear nuevo proyecto Dev C++
2. Seleccionar el tipo de aplicación C++ : Aplicación de consola
3. Indicamos ahora el directorio se va a guardar el archivo que describe el proyecto (*.dev)
Programación 2011-2012 UPSAM
4. Estructura organizativa creada por Dev C++ para nuestro proyecto
PracticasTema2
5. Crear nuevas clases en el proyecto
Programación 2011-2012 UPSAM
Práctica guiada 1.
Definimos un número racional como un decimal finito o infinito periódico (por ejemplo, el
número decimal finito 0,75 es la representación decimal del número racional ¾
La representación en UML sería la siguiente:
+visualizar()
+Racional(entrada int num, entrada int den)
-numerador : int
-denominador : int
Racional
Clase racional.h
class Racional
{
private:
int numerador,denominador;
public:
Racional(int num,int den);
void VisualizarRacional();
~Racional();
};
Racional.cpp
#include <cstdlib>
#include <iostream>
#include "racional.h"
using namespace std;
Racional::Racional(int num,int den)
{
numerador = num;
if (den == 0) den = 1; // el denominador no puede ser cero
denominador = den;
}
void Racional::VisualizarRacional(){
cout<<numerador<<"/"<<denominador<<endl;
Programación 2011-2012 UPSAM
}
Racional::~Racional()
{
}
Nota: observe como se imprime en pantalla con cout . Para ello es necesario que importe en
este archivo fuente lo siguiente
#include <cstdlib>
#include <iostream>
using namespace std;
Utilice la clase Racional en el programa principal (main.cpp) y cree el numero racional (3/4)
para después visualizarlo en pantalla.
Main.cpp
#include <cstdlib>
#include <iostream>
#include "racional.h"
using namespace std;
int main(int argc, char *argv[])
{
Racional *r1=new Racional(3,4);
r1->VisualizarRacional();
system("PAUSE");
return EXIT_SUCCESS;
}
Cuestiones:
1.-En el método visualizarRacional() existe una dependencia respecto al objeto cout el cual
representa la salida estándar ( la pantalla). Queremos desacoplar totalmente esta clase de
su impresión por pantalla. Para ello añada un nuevo método a la clase que obtenga el valor
decimal del número racional (tipo de dato float) representado e imprima este valor desde
el programa principal por pantalla.
Programación 2011-2012 UPSAM
Práctica guiada 2
Represente mediante una clase un Ordenador (marca, procesador, pantalla, indicador de
encendido o apagado) y diseñe además un método denominado encenderOrdenador() que
imprimirá por pantalla el mensaje “Ordenador ya está encendido” si ya está encendido y si no
es así cambiará el valor de la variable de estado para ponerlo en valor “encendido”.
Diseñe otro método llamado obtenerEstado() que imprima por pantalla el valor de cada uno de
los atributos.
La clase debe tener un constructor con tres argumentos: marca, procesador y pantalla. El estado
inicial del ordenador será de “apagado”.
La representación en UML de esta clase sería:
+encenderOrdenador()
+obtenerEstado()
-marca : String
-procesador : String
-pantalla : String
-ordenadorEncendido : bool
Ordenador
ordenador.h
#include <string>
using namespace std;
class Ordenador
{
private:
string marca;
string procesador;
string pantalla;
bool OrdenadorEncendido;
public:
Ordenador(string mar,string pro,string pan);
void encenderOrdenador();
void obtenerEstado();
~Ordenador();
};
Programación 2011-2012 UPSAM
Nota importante: Observa como se ha declarado una cadena en C++ con la clase string
string marca;
string procesador;
string pantalla;
Para ello hemos importado la librería <string.h>
Programación 2011-2012 UPSAM
Ordenador.cpp
#include "ordenador.h"
#include <cstdlib>
#include <iostream>
using namespace std;
Ordenador::Ordenador(string mar,string pro,string pan)
{
marca=mar;
procesador=pro;
pantalla=pan;
OrdenadorEncendido=false;
}
void Ordenador::encenderOrdenador(){
if (OrdenadorEncendido == true) // si está encendido...
cout <<"El ordenador ya está encendido.";
else // si no está encendido, encenderlo.
{
OrdenadorEncendido = true;
cout <<"El ordenador se ha encendido.";
}
}
void Ordenador::obtenerEstado(){
cout<<"Estado del ordenador:" +marca + " Procesador :" +procesador +
" Pantalla :" +pantalla<< endl;
if (OrdenadorEncendido == true) // si el ordenador está encendido...
cout<<"El ordenador está encendido.";
else // si no está encendido...
cout<<"El ordenador está apagado.";
}
Ordenador::~Ordenador()
{
}
Programación 2011-2012 UPSAM
Nota importante: Observa cómo se concatena una cadena con el operador + en la siguiente
instrucción
cout<<"Estado del ordenador:" +marca + " Procesador :" +procesador +
" Pantalla :" +pantalla<< endl;
Cuestiones:
1.-Igual que el ejercicio anterior desacople totalmente la impresión por pantalla en la clase
ordenador y realice esta tarea en la clase ProgramaPrincipal
Práctica guiada 3.
Representar un Cuenta Bancaría compuesta por un valor de saldo y un tipo de interés. La cuenta
permitirá ingresar y retirar dinero, calcular los intereses de abono, cambiar el tipo de interés e
imprimir el saldo actual de la cuenta.
A la hora de implementar esta clase hay que tener en cuenta que:
Al establecer el tipo de interés se debe considerar que este no puede ser un valor
negativo.
La retirada de dinero sólo se puede hacer si hay saldo suficiente.
Los intereses se calculan con la siguiente expresión
saldo += saldo * tipoDeInteres / 100;
La representación en UML sería la siguiente:
+establecerTipo(entrada ti : double)
+ingresarDinero(entrada cantidad : Double)
+retirarDinero(entrada cantidad : double)
+abonarIntereses()
+imprimirSaldoActual() : String
-saldo : double
-tipo : double
CuentaBancaria
Diseñe un programa principal que cree una cuenta bancaria con los siguientes movimientos:
Ingreso de apertura:1.000.000
Abono de intereses
Ingreso :500.000
Retirada:200.000
Abono de intereses
Y que imprima el saldo resultado resultante.
cuentabancaria.h
Programación 2011-2012 UPSAM
#include <cstdlib>
#include <iostream>
using namespace std;
class CuentaBancaria
{
private:
double tipoDeInteres;
double saldo;
public:
CuentaBancaria();
void establecerTipoDeInteres(double ti);
void ingresarDinero(double ingreso);
void retirarDinero(double cantidad);
double saldoActual();
void abonarIntereses();
~CuentaBancaria();
};
cuentabancaria.cpp
#include "cuentabancaria.h" // class's header file
// class constructor
CuentaBancaria::CuentaBancaria(){
saldo=0.0;
tipoDeInteres=0.0;
}
void CuentaBancaria::establecerTipoDeInteres(double ti)
{
if ( ti < 0)
{
cout<<"El tipo de interés no puede ser negativo";
return; // retornar
}
tipoDeInteres = ti;
Programación 2011-2012 UPSAM
}
void CuentaBancaria::ingresarDinero(double ingreso)
{
saldo += ingreso;
}
void CuentaBancaria::retirarDinero(double cantidad)
{
if ( saldo - cantidad < 0)
{
cout<<"No tiene saldo suficiente";
return;
}
// Hay saldo suficiente. Retirar la cantidad.
saldo -= cantidad;
}
double CuentaBancaria::saldoActual()
{
return saldo;
}
void CuentaBancaria::abonarIntereses()
{
saldo += saldo * tipoDeInteres / 100;
}
// class destructor
CuentaBancaria::~CuentaBancaria()
{
// insert your code here
}
Main.cpp
Programación 2011-2012 UPSAM
#include <cstdlib>
#include <iostream>
#include "cuentabancaria.h"
using namespace std;
int main(int argc, char *argv[])
{
// Abrir una cuenta con 1.000.000 a un 2%
CuentaBancaria *Cuenta01 = new CuentaBancaria();
Cuenta01->ingresarDinero(1000000);
Cuenta01->establecerTipoDeInteres(2);
cout.precision(7);
cout<<Cuenta01->saldoActual()<<endl;
Cuenta01->ingresarDinero(500000);
Cuenta01->retirarDinero(200000);
cout<<Cuenta01->saldoActual()<<endl;
Cuenta01->abonarIntereses();
cout<<Cuenta01->saldoActual()<<endl;
system("PAUSE");
return EXIT_SUCCESS;
}
Práctica guiada 3.
Cree un fichero de texto usando el objeto ofstream e imprima la cadena “Prueba de escritura en
archivo” en él.
#include <cstdlib>
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char *argv[])
{
//llamada al constructor
Programación 2011-2012 UPSAM
ofstream *archivo = new ofstream("c://fichero2.txt", ofstream::out);
if (!(*archivo)){
cout << "fallo en apertura" << endl;
return -1;
}
(*archivo) << "prueba de escritura de fichero "; //escritura en el archivo de texto
archivo->close(); //cierre del archivo
system("PAUSE");
return EXIT_SUCCESS;
}
Nota importante: Observe cómo se utiliza el objeto ofstream.
La escritura se realiza de igual manera que con cout
(*archivo) << "prueba de escritura de fichero ";
Programacion Orientada a Objetos 2015-2016 Campus/Anexo II Tema 4 Herencia clases abstractas y poliformismo C++.pdf
1
Tema 4.
Anexo I. Estudio de la implementación de la herencia,
clases abstractas y polimorfismo en C++.
Para ver como se implementa la herencia, clases abstractas y el polimorfismo vamos a utilizar
el dominio de información de cuentas bancarias empleado en el Tema 2. En nuestra práctica del
Tema 2, nuestro programa manipulaba una clase denominada CuentaBancaria. La clase
CuentaBancaria se muestra en el siguiente diagrama UML.
+ingresar(entrada cantidad : double)
+obtenerSaldo() : double
+obtenerCodigoCuenta() : String
+abonarIntereses()
-saldo : double
#tipoInteres : double
#titular : String
#codigo : String
CuentaBancaria
Supongamos que necesitamos manejar dos tipos de cuentas bancarias: CuentaAhorro y
CuentaRemunerada. Las características de cada cuenta son las siguientes:
Cuenta Remunerada
o En este tipo de cuenta se abonan intereses siempre independientemente del
saldo
CuentaAhorro
o En este tipo de cuenta se abonan intereses sólo sí el saldo es superior a 3000
euros.
Como se puede observar el método abonarIntereses() tiene un comportamiento que está en
función del tipo de cuenta.
Es decir es diferente para cada subclase. Este método debe ser
sobrescrito en cada subclase a fin de dar la implementación adecuada.
Analizando estas clases obtenemos el siguiente diagrama de clases UML
+ingresar(entrada cantidad : double)
+obtenerSaldo() : double
+obtenerCodigoCuenta() : String
+abonarIntereses()
-saldo : double
#tipoInteres : double
#titular : String
#codigo : String
CuentaBancaria
+abonarIntereses()
CuentaAhorro
+abonarIntereses()
CuentaRemunerada
2
1. Implementación de la Generalización/especialización en C++.
La definición de la clase base CuentaBancaria.h es la siguiente:
#include <stdlib.h>
#include <string>
using namespace std;
class CuentaBancaria
{
private:
double saldo;
protected:
string titular;
double tipoInteres;
string codigo;
//Constructor
CuentaBancaria(double saldo, string titular, string codigo, double tipoInteres) {
this->saldo = saldo;
this->titular = titular;
this->codigo = codigo;
this->tipoInteres = tipoInteres;
}
public:
//Método para ingresar dinero en la cuenta
void ingresar(double cantidad){saldo+=cantidad;}
//Método para obtener el saldo
double obtenerSaldo(){return saldo;}
//Método para obtener el código de identificación
string obtenerCodigoCuenta(){return codigo;}
//Método abstracto que necesita ser sobrescrito por cada subclase
virtual void abonarIntereses()=0;
~CuentaBancaria();
};
Observe con atención el siguiente método de la clase CuentaBancaria
virtual void abonarIntereses()=0;
3
El método se define como abstracto (palabra reservada virtual ) y no se le proporciona cuerpo,
esto indica que no tiene ninguna implementación (su cuerpo esta vacio ) y se indica con ”=0”,
la responsabilidad de la implementación de este método corresponde a cada subclase.
Importante: Debido a que CuentaBancaria define un método abstracto la clase en sí se
convierte en una clase abstracta: Una clase abstracta en C++ es aquella que al menos define una
función virtual pura.
1.1. Intento de instancia de una clase abstracta
Si intentamos realizar una instancia de la clase CuentaBancaria el compilador nos avisara que
no se puede instanciar una clase abstracta.
int main(int argc, char *argv[])
{
CuentaBancaria *c001=new CuentaBancaria(100,"Gustavo","C00023",3.2);
system("PAUSE");
return EXIT_SUCCESS;
}
1.2. Definición de una subclase en C++
Sintaxis general:
class <NombreSubclase> : <NombreClaseBase>{
//Cuerpo de la clase
}
El carácter “:” indica que la clase <NombreSubclase> hereda de la clase denominada
<NombreClaseBase>.
Subclase CuentaAhorro.h
#include <string>
#include "cuentabancaria.h"
using namespace std;
class CuentaAhorro : CuentaBancaria {
public:
CuentaAhorro(double saldo, string titular, string codigo, double tipoInteres);
void abonarIntereses();
~CuentaAhorro();
};
4
CuentaAhorro.cpp
#include "cuentaahorro.h"
CuentaAhorro::CuentaAhorro(double saldo, string titular, string codigo, double
tipoInteres):CuentaBancaria(saldo,titular,codigo,tipoInteres)
{
}
void CuentaAhorro::abonarIntereses(){
//Abonar intereses sólo si el saldo es superior a 3000 euros
if (this->obtenerSaldo()>=3000.0){
double cant=this->obtenerSaldo()*(tipoInteres/100);
this->ingresar(cant);
}
}
CuentaAhorro::~CuentaAhorro()
{
}
Definición Subclase CuentaRemunerada.h
#include "cuentabancaria.h"
class CuentaRemunerada: CuentaBancaria
{
public:
CuentaRemunerada(double saldo, string titular, string codigo, double tipoInteres);
void abonarIntereses();
~CuentaRemunerada();
};
CuentaRemunerada.cpp
#include "cuentaremunerada.h"
CuentaRemunerada::CuentaRemunerada(double saldo, string titular, string codigo, double
tipoInteres):CuentaBancaria(saldo,titular,codigo,tipoInteres)
{
}
void CuentaRemunerada::abonarIntereses()
{
//Abonar intereses siempre
5
double cant=this->obtenerSaldo()*(tipoInteres/100);
this->ingresar(cant);
}
CuentaRemunerada::~CuentaRemunerada()
{
}
Prueba de la subclase CuentaAhorro y CuentaRemunerada
#include <cstdlib>
#include <iostream>
#include "cuentabancaria.h"
#include "cuentaahorro.h"
#include "cuentaremunerada.h"
using namespace std;
int main(int argc, char *argv[])
{
CuentaAhorro *c001=new CuentaAhorro(1000,"Luis Eduardo Abad Diaz","C00023",3.2);
CuentaRemunerada *c002=new CuentaRemunerada(3000,"Alfonso Garcia Ruiz","C0043",5.0);
//ingresar dinero en cuentas
c001->ingresar(1000);
c002->ingresar(3000);
//abonar intereses en cuentas
c001->abonarIntereses();
c002->abonarIntereses();
cout<<"saldo cuenta:"<<c001->obtenerCodigoCuenta()<<": "<<c001-
>obtenerSaldo()<<endl;
cout<<"saldo cuenta:"<<c002->obtenerCodigoCuenta()<<": "<<c002-
>obtenerSaldo()<<endl;
system("PAUSE");
return EXIT_SUCCESS;
}
Salida:
saldo cuenta:C00023: 2000.0
saldio cuenta:C0043: 6300.0
Como se puede comprobar la subclase ha heredado los atributos y métodos de la clase base
CuentaBancaria
6
1.3. Análisis de miembros heredados por una subclase
Las subclases CuentaAhorro y CuentaRemunerada han heredado atributos y comportamiento
de la clase base CuentaBancaria. Además cada subclase deber proporcionar una
implementación al método abonarIntereses().Pero ¿Qué hereda exactamente la subclase
CuentaAhorro y CuentaRemunerada? La siguiente tabla muestra un resumen de todo lo
heredado por cada subclase.
Atributos Métodos
private double saldo;
protected String titular;
protected String codigo;
protected double tipoInteres;
void ingresar(double cantidad)
double obtenerSaldo()
String obtenerCodigoCuenta()
Obligación de sobrescribir el método abonarIntereses()
Tabla herencia CuentaBancaria y subclases
IMPORTANTE: Los constructores de una clase base no son heredados por las subclases. Como
se ve en el constructor de la clase base la inicialización correcta de una subclase implica
inicializar la clase base primero. Esto se realiza con la siguiente llamada a super(lista
parametros).
CuentaRemunerada::CuentaRemunerada(double saldo, string titular, string codigo, double
tipoInteres):CuentaBancaria(saldo,titular,codigo,tipoInteres)
{
}
Para instanciar una clase base desde una subclase en general utiliza la siguiente sintaxis:
public <NombreSubclase>(lista parámetros):ContructorClaseBase
{
//Cuerpo del constructor de la subclase
}
1.4. Acceso a los miembros de una clase base
Los métodos de la subclase no tienen acceso a los miembros privados de la clase base, pero sí
a los miembros protegidos y públicos. Por ejemplo en el método abonarIntereses() de la
subclase CuentaRemunerada no tiene acceso directo al atributo saldo ya que este es declarado
privado (prívate) en la clase base CuentaBancaria. Para acceder al atributo saldo la subclase
debe usar el método obtenerSaldo() como se observa en el siguiente fragmento.
void CuentaRemunerada::abonarIntereses()
{
//Abonar intereses siempre
double cant=this->obtenerSaldo()*(tipoInteres/100);
this->ingresar(cant);
}
7
Sin embargo la subclase si tiene acceso al atributo tipoInteres de la clase base ya que este es
declarado como protegido (protected).
Importante: La restricción prívate en los atributos de una clase base puede sorprender pero
es necesaria para mantener la propiedad
de encapsulación de la información. Si se pudiese tener
acceso a los miembros privados de una clase, simplemente se conseguiría heredando. De esta
forma se cumple que el interfaz de una clase es el único medio de manipulación de la clase.
Luego como regla general es un mejor establecer el modificador private para los atributos de
una clase base y que las subclases accedan a ellos a través de una interfaz
Tabla general de visibilidad de miembros entre clases base y subclases:
Un miembro declarado en una clase como
Puede ser accedido desde privado predeterminado protegido público
Su misma clase Sí Sí Sí Sí
Cualquier clase o subclase de su mismo
paquete
No Sí Sí Sí
Cualquier clase de otro paquete No No No Sí
Cualquier subclase de otro paquete No No Sí Sí
Tabla. Javier Ceballos (Java 2)
1.5. Estudio del uso del polimorfismo en C++
En ejemplo anterior declarábamos una clase base denominada CuentaBancaria. Esta clase era
abstracta por que definía un método virtual puro (=0) que no tenía implementación virtual void
abonarIntereses(). La implementación adecuada de abonarIntereses() se proporciona por cada
una de las subclases que heredan de esta clase base.
Vamos a suponer que tenemos una clase denominada ControlIntereses que expone un único
método denominado abonarInteresesCuentas() con una lógica de negocio como lo siguiente:
Todos los días uno de cada mes se procede a abonar los interés correspondientes a cada una de
las cuentas existente en el banco.
El diseño de clases en UML podría ser el siguiente:
+ingresar(entrada cantidad : double)
+obtenerSaldo() : double
+obtenerCodigoCuenta() : String
+abonarIntereses()
-saldo : double
#tipoInteres : double
#titular : String
#codigo : String
CuentaBancaria
+abonarIntereses()
CuentaAhorro
+abonarIntereses()
CuentaRemunerada
+abonarInteresesCuentas()
ControlIntereses *1
El método abonarInteresesCuentas() tiene algunas consideraciones de diseño importantes:
1. La lógica de control de este método considera todas las cuentas existentes de manera
homogénea y a para todas aplica el mismo tratamiento abonarIntereses().
8
2. La lógica de abonoIntereses() es propia de cada subclase e independiente de este
método.
3. Si en el futuro se añade una nueva cuenta este modulo no debería sufrir cambios.
Definición de la clase ControladorIntereses.h
#include <list.h>
#include "cuentaBancaria.h"
class ControladorIntereses
{
private:
list<CuentaBancaria *> cuentas;
public:
ControladorIntereses();
void registrarCuenta(CuentaBancaria *cb);
void abonarInteresesCuentas();
~ControladorIntereses();
};
ControladorIntereses.cpp
#include "controladorintereses.h"
#include "cuentabancaria.h"
ControladorIntereses::ControladorIntereses()
{
}
void ControladorIntereses::registrarCuenta(CuentaBancaria *cb){
cuentas.push_front(cb);
}
void ControladorIntereses::abonarInteresesCuentas()
{
//itereador_lista se posiciona al principio de la lista
list<CuentaBancaria *>::iterator iterador_lista=cuentas.begin();
CuentaBancaria *cb;
//Recorrer cada elemento de la lista
for (;iterador_lista!=cuentas.end();iterador_lista++){
9
cb=*iterador_lista; //Asignar el contenido del iterador
cb->abonarIntereses();
}
}
ControladorIntereses::~ControladorIntereses()
{
}
Ahora probamos el polimorfismo
#include <cstdlib>
#include <iostream>
#include "cuentabancaria.h"
#include "cuentaahorro.h"
#include "cuentaremunerada.h"
#include "controladorintereses.h"
using namespace std;
int main(int argc, char *argv[])
{
//crear cuentas bancarias
CuentaAhorro *ca001=new CuentaAhorro(2000,"Luis Eduardo Abad Diaz","CA001",3.2);
CuentaAhorro *ca002=new CuentaAhorro(4000,"Manuel García Solis","CA002",3.2);
CuentaRemunerada *cr001=new CuentaRemunerada(3000,"Alfonso Garcia
Ruiz","CR003",5.0);
//crear el controlador
ControladorIntereses *ctrl= new ControladorIntereses();
//Registrar cada una de las cuentas en el controlador
ctrl->registrarCuenta(ca001);
ctrl->registrarCuenta(ca002);
ctrl->registrarCuenta(cr001);
//abonar intereses en todas las cuentas
ctrl->abonarInteresesCuentas();
//Comprobar el pago de intereses
cout<<"saldo cuenta:"<<ca001->obtenerCodigoCuenta()<<": "<<ca001-
>obtenerSaldo()<<endl;
10
cout<<"saldo cuenta:"<<ca002->obtenerCodigoCuenta()<<": "<<ca002-
>obtenerSaldo()<<endl;
cout<<"saldo cuenta:"<<cr001->obtenerCodigoCuenta()<<": "<<cr001-
>obtenerSaldo()<<endl;
system("PAUSE");
return EXIT_SUCCESS;
}
Programacion Orientada a Objetos 2015-2016 Campus/Anexo I Tema 4 Herencia_clases abstractas y polimorfismo java.pdf
1
Tema 4.
Anexo I. Estudio de la implementación de la herencia,
clases abstractas y polimorfismo en Java.
Para ver como se implementa la herencia, clases abstractas y el polimorfismo vamos a utilizar
el dominio de información de cuentas bancarias empleado en el Tema 2. En nuestra práctica del
Tema 2, nuestro programa manipulaba una clase denominada CuentaBancaria. La clase
CuentaBancaria se muestra en el siguiente diagrama UML.
+ingresar(entrada cantidad : double)
+obtenerSaldo() : double
+obtenerCodigoCuenta() : String
+abonarIntereses()
-saldo : double
#tipoInteres : double
#titular : String
#codigo : String
CuentaBancaria
Supongamos que necesitamos manejar dos tipos de cuentas bancarias: CuentaAhorro y
CuentaRemunerada. Las características de cada cuenta son las siguientes:
Cuenta Remunerada
o En este tipo de cuenta se abonan intereses siempre independientemente del
saldo
CuentaAhorro
o En este tipo de cuenta se abonan intereses sólo sí el saldo es superior a 3000
euros.
Como se puede observar el método abonarIntereses() tiene un comportamiento que está en
función del tipo de cuenta. Es decir es diferente para cada subclase. Este método debe ser
sobrescrito en cada subclase a fin de dar la implementación adecuada.
Analizando estas clases obtenemos el siguiente diagrama de clases UML
+ingresar(entrada cantidad : double)
+obtenerSaldo() : double
+obtenerCodigoCuenta() : String
+abonarIntereses()
-saldo : double
#tipoInteres : double
#titular : String
#codigo : String
CuentaBancaria
+abonarIntereses()
CuentaAhorro
+abonarIntereses()
CuentaRemunerada
2
1. Implementación de la Generalización/especialización en Java
La definición de la clase base CuentaBancaria,java es la siguiente:
public abstract class CuentaBancaria {
private double saldo;
protected String titular;
protected double tipoInteres;
protected String codigo;
//Constructor
public CuentaBancaria(double saldo, String titular, String codigo, double
tipoInteres) {
this.saldo = saldo;
this.titular = titular;
this.codigo = codigo;
this.tipoInteres = tipoInteres;
}
//Método para ingresar dinero en la cuenta
public void ingresar(double cantidad){
saldo+=cantidad;
}
//Método para obtener el saldo
public double obtenerSaldo(){return saldo;}
//Método para obtener el código de identificación
public String obtenerCodigoCuenta(){return codigo;}
//Método abstracto que necesita ser sobrescrito por cada subclase
abstract void abonarIntereses();
}
Observe con atención el siguiente método de la clase CuentaBancaria
abstract void abonarIntereses();
El método se define como abstracto (abstract), esto indica que no tiene ninguna
implementación
(su cuerpo esta vacio), luego la responsabilidad de la implementación
corresponde a cada subclase.
Importante: Debido a que CuentaBancaria define un método abstracto debe declararse como
abstracta (abstract class)
3
1.1. Intento de instancia de una clase abstracta
Si intentamos realizar una instancia de la clase CuentaBancaria el compilador nos avisara que
no se puede instanciar una clase abstracta.
public class ProgramaPrincipal {
public static void main(String[] args) {
//El intento de una instancia de una clase abstracta dará error
CuentaBancaria c001=new CuentaBancaria(100,"Gustavo","C00023",3.2);
}
}
1.2. Definición de una subclase en Java.
Sintaxis general:
public class <NombreSubclase> extends <NombreClaseBase>{
//Cuerpo de la clase
}
La palabra reservada extends indica que la clase <NombreSubclase> hereda de la clase
denominada <NombreClaseBase>.
Por defecto toda clase en Java es una subclase de la clase base denominada Object.
Subclase CuentaAhorro.java
public class CuentaAhorro extends CuentaBancaria {
//Constructor
public CuentaAhorro(double saldo, String titular, String codigo, double tipoInteres)
{
super(saldo, titular, codigo, tipoInteres);
}
void abonarIntereses() {
//Abonar intereses sólo si el saldo es superior a 3000 euros
if (this.obtenerSaldo()>=3000.0){
double cant=this.obtenerSaldo()*(tipoInteres/100);
this.ingresar(cant);
}
}
}
4
Definición Subclase CuentaRemunerada.java
public class CuentaRemunerada extends CuentaBancaria{
//Constructor
public CuentaRemunerada(double saldo, String titular, String codigo, double
tipoInteres) {
super(saldo, titular, codigo, tipoInteres);
}
public void abonarIntereses(){
//Abonar intereses siempre
double cant=this.obtenerSaldo()*(tipoInteres/100);
this.ingresar(cant);
}
}
Prueba de la subclase CuentaAhorro
public class ProgramaPrincipal {
public static void main(String[] args) {
CuentaAhorro c001=new CuentaAhorro(1000,"Luis Eduardo Abad Diaz","C00023",3.2);
CuentaRemunerada c002=new CuentaRemunerada(3000,"Alfonso Garcia
Ruiz","C0043",5.0);
//ingresar dinero en cuentas
c001.ingresar(1000);
c002.ingresar(3000);
//abonar intereses en cuentas
c001.abonarIntereses();
c002.abonarIntereses();
//ver los saldos
System.out.println("saldo cuenta:"+c001.obtenerCodigoCuenta()+":
"+c001.obtenerSaldo());
System.out.println("saldo cuenta:"+c002.obtenerCodigoCuenta()+":
"+c002.obtenerSaldo());
}
}
Salida:
saldo cuenta:C00023: 2000.0
saldio cuenta:C0043: 6300.0
5
Como se puede comprobar la subclase ha heredado los atributos y métodos de la clase base
CuentaBancaria
1.3. Análisis de miembros heredados por una subclase
Las subclases CuentaAhorro y CuentaRemunerada han heredado atributos y comportamiento
de la clase base CuentaBancaria. Además cada subclase deber proporcionar una
implementación al método abonarIntereses().Pero ¿Qué hereda exactamente la subclase
CuentaAhorro y CuentaRemunerada? La siguiente tabla muestra un resumen de todo lo
heredado por cada subclase.
Atributos Métodos
private double saldo;
protected String titular;
protected String codigo;
protected double tipoInteres;
void ingresar(double cantidad)
double obtenerSaldo()
String obtenerCodigoCuenta()
Obligación de sobrescribir el método abonarIntereses()
Tabla herencia CuentaBancaria y subclases
IMPORTANTE: Los constructores de una clase base no son heredados por las subclases.Como
se ve en el constructor de la clase base la incialización correcta de una subclase implica
inicializar la clase base primero.Esto se realiza con la siguiente llamada a super(lista
parametros).
public CuentaAhorro(double saldo, String titular, String codigo, double tipoInteres) {
super(saldo, titular, codigo, tipoInteres);
}
Para instanciar una clase base desde una subclase en general utiliza la siguiente sintaxis:
public <NombreSubclase>(lista parámetros){
super(lista parámetros);
//Cuerpo del constructor de la subclase
}
1.4. Acceso a los miembros de una clase base
Los métodos de la subclase no tienen acceso a los miembros privados de la clase base, pero sí
a los miembros protegidos y públicos. Por ejemplo en el método abonarIntereses() de la
subclase CuentaRemunerada no tiene acceso directo al atributo saldo ya que este es declarado
privado (prívate) en la clase base CuentaBancaria. Para acceder al atributo saldo la subclase
debe usar el método obtenerSaldo() como se observa en el siguiente fragmento.
public void abonarIntereses(){
//Abonar intereses siempre
double cant=this.obtenerSaldo()*(tipoInteres/100);
this.ingresar(cant);
6
}
Sin embargo la subclase si tiene acceso al atributo tipoInteres de la clase base ya que este es
declarado como protegido (protected).
Importante: La restricción prívate en los atributos de una clase base puede sorprender pero
es necesaria para mantener la propiedad de encapsulación de la información. Si se pudiese tener
acceso a los miembros privados de una clase, simplemente se conseguiría heredando. De esta
forma se cumple que el interfaz de una clase es el único medio de manipulación de la clase.
Luego como regla general es un mejor establecer el modificador private para los atributos de
una clase base y que las subclases accedan a ellos a través de una interfaz
Tabla general de visibilidad de miembros entre clases base y subclases:
Un miembro declarado en una clase como
Puede ser accedido desde privado predeterminado protegido público
Su misma clase Sí Sí Sí Sí
Cualquier clase o subclase de su mismo
paquete
No Sí Sí Sí
Cualquier clase de otro paquete No No No Sí
Cualquier subclase de otro paquete No No Sí Sí
Tabla. Javier Ceballos (Java 2)
1.5. Estudio del uso del polimorfismo en Java
En ejemplo anterior declarábamos una clase base denominada CuentaBancaria. Esta clase era
abstracta por que definía un método que no tenía implementación abstract void
abonarIntereses(). La implementación adecuada de abonarIntereses() se proporciona por cada
una de las subclases que heredan de esta clase base.
Vamos a suponer que tenemos una clase denominada ControlIntereses que expone un único
método denominado abonarInteresesCuentas() con una lógica de negocio como lo siguiente:
Todos los días uno de cada mes se procede a abonar los interés correspondientes a cada una de
las cuentas existente en el banco.
El diseño de clases en UML podría ser el siguiente:
+ingresar(entrada cantidad : double)
+obtenerSaldo() : double
+obtenerCodigoCuenta() : String
+abonarIntereses()
-saldo : double
#tipoInteres : double
#titular : String
#codigo : String
CuentaBancaria
+abonarIntereses()
CuentaAhorro
+abonarIntereses()
CuentaRemunerada
+abonarInteresesCuentas()
ControlIntereses *1
El método abonarInteresesCuentas() tiene algunas consideraciones de diseño importantes:
7
1. La lógica de control de este método considera todas las cuentas existentes de manera
homogénea y a para todas aplica el mismo tratamiento abonarIntereses().
2. La lógica de abonoIntereses() es propia de cada subclase e independiente de este
método.
3. Si en el futuro se añade una nueva cuenta este modulo no debería sufrir cambios.
Definición de la clase ControladorIntereses.java
import java.util.LinkedList;
public class ControladorIntereses {
LinkedList cuentas;
public ControladorIntereses()
{
this.cuentas =new LinkedList();
}
public void registrarCuenta(CuentaBancaria cb){
cuentas.add(cb);
}
public void abonarInteresesCuentas(){
CuentaBancaria cb=null;
//Obtener nuemro de cuentas
int tam=cuentas.size();
for (int i=0;i<tam;i++){
cb=(CuentaBancaria)cuentas.get(i);
//Por cada cuenta abonar intereses
cb.abonarIntereses();
}
}
}
Ahora probamos el polimorfismo
public class ProgramaPrincipal {
public static void main(String[] args) {
//crear cuentas bancarias
CuentaAhorro ca001=new CuentaAhorro(2000,"Luis Eduardo Abad Diaz","CA001",3.2);
8
CuentaAhorro ca002=new CuentaAhorro(4000,"Manuel García Solis","CA002",3.2);
CuentaRemunerada cr001=new CuentaRemunerada(3000,"Alfonso Garcia
Ruiz","CR003",5.0);
//crear el controlador
ControladorIntereses ctrl= new ControladorIntereses();
//Registrar cada una de las cuentas en el controlador
ctrl.registrarCuenta(ca001);
ctrl.registrarCuenta(ca002);
ctrl.registrarCuenta(cr001);
//abonar intereses en todas las cuentas
ctrl.abonarInteresesCuentas();
//Comprobar el pago de intereses
System.out.println("saldo cuenta:"+ca001.obtenerCodigoCuenta()+":
"+ca001.obtenerSaldo());
System.out.println("saldo cuenta:"+ca002.obtenerCodigoCuenta()+":
"+ca002.obtenerSaldo());
System.out.println("saldo cuenta:"+cr001.obtenerCodigoCuenta()+":
"+cr001.obtenerSaldo());
}
}
Programacion Orientada a Objetos 2015-2016 Campus/Tema_5._Tratamiento_de_errores_excepciones_v3.pdf
Tema 5
Tratamiento de
errores con
excepciones
UPSAM 2010-2011
Contenido
1. Concepto de Excepción ......................................................................................................... 3
2. Manejo de excepciones. ........................................................................................................ 3
3. Tratamiento de Excepciones ................................................................................................. 4
4. Lanzamiento de Excepciones ................................................................................................ 5
5. Manejadores de excepciones ................................................................................................ 6
6. Declaración de Excepciones .................................................................................................. 8
1. Concepto de Excepción
Las Excepciones son anomalías (condiciones de error no esperadas) producidas durante la
ejecución de un programa. Por ejemplo división por cero, punteros a zonas de memoria
ilegales, punteros no inicializados correctamente, acceso a una zona de memoria no permitida,
índices de arrays fuera de rango, errores de E/S,etc.
Normalmente las excepciones suspenden la ejecución del programa, emitiendo en el sistema
el mensaje de error correspondiente. Los lenguajes orientado a objetos como C++ y Java
tienen construcciones específicas para tratar con excepciones. Cuando un programa detecta
una excepción, se notifica al resto del sistema elevando (raising) o lanzando (throwing) un
excepción. En alguna parte existe un código especifico que maneja y captura (catch) la
excepción.
De la misma forma que el concepto de Clase ha cambiado la forma de organizar y programar,
las excepciones han cambiado la forma de manejar las condiciones de error en el flujo de
control de los programas.
2. Manejo de excepciones.
Muchos lenguajes de programación como C, incorporan un tratamiento de errores basado en
el valor de retorno de las funciones. Así el valor devuelto por las funciones se utiliza para
determinar si una función se ha ejecutado satisfactoriamente (Por ejemplo si devuelve -1
indica error y en otro caso es interpretado como ejecución correcta).
Ejemplo. Fragmento de código y tratamiento de errores tradicional.
int codError=0;
codError=leerArchivo(archivo);
if (codigoError!=0){
switch(codigoError)
}
case -1: //No se encontró el archivo
//…
break;
case -2: //El archivo está corrupto
break;
case -3://El dispositivo no está listo
break;
default://…
}
}
else{
//procesar los datos leídos
}
El problema de esta forma de tratamiento es que el código de tratamiento está mezclado junto
con el flujo normal. El tratamiento de errores con excepciones es un método que clasifica los
tipos de errores y estructura el código de una manera más clara de cara a realizar este
tratamiento.
3. Tratamiento de Excepciones
El modelo de excepciones en la mayoría de los lenguajes de programación orientados a
objetos se basa en los siguientes mecanismos: lanzar (throw) y capturar (catch):
1. Cuando se detecta una excepción, el programa lanza (throw) una excepción. El
lanzar una excepción hace que el programa realice un salto antes de continuar la
ejecución.
2. La excepción se captura (catch) mediante un manejador de excepciones (handler).
Los manejadores de excepciones comienzan con la palabra catch y se declaran al final
de un bloque try ( en C++ y Java).
En resumen un código bajo este mecanismo puede lanzar una excepción directamente o
indirectamente, en un bloque try utilizando la expresión throw. La excepción se maneja
invocando un manejador adecuado seleccionado de una lista de manejadores encontrados
inmediatamente después del bloque try . De forma general un bloque de código con
tratamiento de excepciones tiene la siguiente forma general.
try{
//El código de este bloque puede producir alguna excepción
}
catch( TipoExcepción1 e){
//Código de tratamiento de la excepción
}…
catch (TipoExcepcionN e){
}
La estructura en C++ y Java exige que cualquier excepción que ocurra, debe ser lanzada
dentro de un bloque try. El bloque try es una sentencia que especifica el conjunto de
sentencias que al ejecutarse podrían lanzar una excepción y si es así que manejadores
tratarían las excepciones.
El manejador de excepciones es el bloque de código diseñado para manejar una condición
anómala y excepcional. Debería haber al menos un manejador por cada tipo de excepción que
se pueda lanzar.
El manejo de excepciones como hemos dicho antes ofrece una forma de separar
explícitamente el código que trata las condiciones de error y el código de control de flujo
normal de la aplicación haciéndola más legible. El siguiente fragmento de código muestra el
tratamiento de los errores del ejemplo anterior con el mecanismo de excepciones.
try{
//Código de acceso y tratamiento de archivo
leerArchivo(archivo);
} catch(ArchivoDesconocido e){
//Tratamiento si el archivo es desconocido
}catch(DispositivoError e){
//Tratamiento si el dispositivo da algún tipo de error
}catch(ArchivoCorrupto e){
//Tratamiento si el archivo esta dañado
}
4. Lanzar una Excepción (throw)
Las excepciones se lanzan utilizando la sentencia throw
throw tipoexcepcion;
Donde tipoexcepción es una expresión que determina el tipo de la excepción.Este tipo es
cualquier tipo válido en el lenguaje .Este objeto se utiliza para inicializar la variable del
manejador de expresiones (clausula catch).
Ejemplo:
// exceptions_trycatchandthrowstatements.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;
int main() {
char *buf;
try {
buf = new char[512];
if( buf == 0 )
throw "Memory allocation failure!";
}
catch( char
* str ) {
cout << "Exception raised: " << str << '\n';
}
}
El operando throw es sintácticamente similar a una sentencia return
En esencia los pasos que se siguen son los siguientes:
1. Se lanza la excepción
2. Se busca un manejador adecuado en función de su orden de aparición y se comprueba
su tipo (cada manejador tiene un tipo diferente )
3. Si el manejador se encuentra, el control del programa se transfiere al manejador
4. Si no se encuentra ningún manejador, se elevará esta al bloque try superior si lo
hubiere si no el programa terminará la ejecución.
5. Manejadores de excepciones
Las excepciones lanzadas dentro de un bloque son capturadas usando clausulas catch
asociadas a un bloque try.
5.1. Bloques try-catch
Un bloque try -catch tiene la siguiente sintaxis:
try{
//Sentencias
}catch(Exceppcion1 e){}
catch(Exceppcion2 e){}
catch(ExceppcionN e){}
Un bloque try es un contexto para indicar cual son la instrucciones a considerar y los
manejadores que se invocarían si se produjesen determinadas anomalías en esas
instrucciones.
El orden en el que se especifican los manejadores determina el orden en que se prueban los
mismos cuando se lanza una excepción .Siempre existe un bloque try junto a un bloque de
manejadores catch.
La clausula catch es muy similar a una declaración de función de un argumento sin tipo de
retorno.
Una excepción se captura cuando su tipo coincide con el tipo de la sentencia catch. Una vez
que existe correspondencia de tipos, el control del programa se transfiere al manejador. El
manejador especifica que acciones deben realizarse para tratar la anomalía del programa.
Ejemplo en C++
class desbordamiento{
public:
desbordamiento();
};
float división(float a ,float b)
{
if (b==0) throw “division por 0”;
if (a>LIMITE1 && b<LIMITE2) throw new desbordamiento();
return a/b;
}
void main()
{
Try{
division(5,3);
}catch( char *e){
Cout<<e<<endl;
}catch(desbordamiento *e){
}
}
Ejemplo 2
//Recibe cierto valor numérico
try{
if (n % 2 == 1){
// n es impar
throw new ArithmeticException();
}else
// Código normal para valores pares
}catch (ArithmeticException e){
// Tratar el caso especial de valores impares
}
6. Declaración de Excepciones
Sintácticamente una especificación de excepciones es parte de la declaración de funciones y
tiene el siguiente formato.
tipo_ retorno nombre_funcion( parámetros) throw lista tipos de excepciones
Consideraciones:
La lista de excepciones es la lista de tipos que una expresión throw puede tener en la
función.
Cualquier función o bloque de código que invoque esta función deberá
obligatoriamente capturar estos tipos de excepciones.
Ejemplo1 .C++
void escrituraDisco throw (DeviceError,WritError);
…
void enviarMail throw (direcc_incorrecta,fallo_sistema);
Ejemplo 2.Java
public escribirArchivo( String nombre_fichero) throws IOException
public class Conexion{
public Conexion(Stringurl) throws RedNoDisponible, ServidorNoEncontrado,
RecursoNoDisponible
{ …
}
publicStringleerLinea() throws RedNoDisponible
{
…}
public void cerrar()
{ …}
}
Programacion Orientada a Objetos 2015-2016 Campus/Tema 2-parcticas obligatorias 2013_2014 BLOQUE I.pdf
Página 1
UPSAM 2012-2013
Programación. Tema 2 Prácticas obligatorias
Normativa:
Las siguientes prácticas son obligatorias para aprobar la asignatura y tienen que ser entregadas
y explicadas antes de la fecha fijada por el profesor.
El material a entregar será un diagrama UML y el código fuente de la práctica en el lenguaje
requerido.
No se corregirán prácticas que no compilen.
Las prácticas se realizará de forma individual.
Página 2
Práctica 1. Java
Represente un Semáforo de Tráfico mediante una clase. El estado del semáforo podrá ser ROJO,
AMBAR y VERDE. La clase deberá permitir cambiar el estado interno del semáforo mediante una única
operación denomina cambiar estado. Esta operación modificará en cada llamada el estado interno del
semáforo de la siguiente forma (VERDE->AMBAR->ROJO VERDE…). Además la clase semáforo debe
definir una operación que permita obtener el valor del estado actual.
Se pide
Representación UML de la clase Semáforo
Diseñe una clase que represente un semáforo (use un tipo enum para representar el estado)
El estado inicial del semáforo debe ser VERDE
Mediante un programa principal pruebe la siguiente secuencia
o Crear un semáforo, cambiar su estado interno a ROJO, mostrar por pantalla el estado
interno del semáforo
NOTA: Para esta práctica investigue como declarar y usar un tipo enum en java
Práctica 2. Java
Represente un Servidor mediante una clase. Para ello considere la siguiente información
Un servidor escucha solicitudes de conexión en un puerto determinado (Ej 8080) y en una
dirección IP (Ej 10.16.1.20 use una cadena para representarla)
El nombre del servidor representa el servicio que ofrece (Ej servidor de correo, servidor de
archivos, servidor de base de datos, etc).
Un servidor mantiene internamente un recuento del número de hilos activos en cada momento
La capacidad máxima del servidor ( en número de hilos) viene definida por la constante
MAX_CON ( que se configura al crear el servidor)
Cada hilo se corresponde con una solicitud de servicio activa. El servidor no atenderá
solicitudes si ha llegado al límite de su capacidad MAX_CON
Se pide:
Representación en UML de la clase Servidor
Implemente en Java la clase Servidor
Defina en la clase Servidor la operación solicitar servicio, cuyo efecto es el de incrementar en
uno el número de hilos activos que están procesando solicitudes dentro del servidior o
devuelve -1 si es rechazada
Defina en la clase Servidor la operación estadoCapacidad que devuelve la capacidad actual en
cada momento
Realice un programa principal que basándose en la clase Servidor realice lo siguiente
o Crear dos servidores : servidor base de datos (SBD), servidor de archivos(SA) con
capacidades máximas de 3 y 5 hilos respectivamente
o Realizar 4 solicitudes de servicio al servidor de base de datos y 2 al de archivos
o Mostrar por pantalla la capacidad actual de cada servidor
Página 3
Práctica 3. Java
Se requiere que la Cuenta Bancaria (ver prácticas guiadas) tenga asociada además información de la
nómina del cliente (sólo será necesario la cantidad mensual cobrada) de manera que se pueda calcular
el riesgo de determinadas operaciones. Para ello se incorpore a la cuenta el método calcularRiesgo
(float cuota-prestamo) que devolverá el grado de riesgo que tiene el préstamo a un cliente en función
de su nómina. La lógica de este método es la siguiente:
Si <cuota_prestamo> representa el 50% o más de la nómina ,el riesgo es alto
Si <cuota_prestamo> representa menos del 50% y más del 31% ,el riesgo es medio
Si <cuota_prestamo> representa 30% o menos ,el riesgo bajo
Se pide:
Representación en UML de la clase CuentaBanacaria
o Diseñe un programa principal que dada una CuentaBancaria (saldo=100
,interés=0.0.3,nomina=1000)
o Que solicite por teclado una cuota y compruebe el riesgo que representa esa
cantidad frente a la nómina de la cuenta indicada.
Practica 4.C++
Diseñe una clase C++ que represente una Calculadora. La calculadora es un poco especial y debe
permitir realizar lo siguiente:
Realizar operaciones aritméticas :suma, resta, división ,multiplicación
Tienen una operación denominada deshacerOperacion que deshará sólo la última operación
realizada.
La operación reset. Que establece a cero el estado interno de la calculadora.
Para ver el resultado de la operación se utilizará un método denominado verestado que
devuelve el valor de la calculadora tras la última operación realizada.
IMPORTANTE
El estado interno de la calculadora debe poder representar la última operación realizada y el
resultado antes de hacer esta última operación.
La calculadora opera con números reales (tipo double)
Todas las operaciones aritméticas son procedimientos.
Para ver el resultado hay que invocar al método verResultado()
Considerando que se trabaja con una notación infija <operando1> operaciónX <operando2>. La clase
calculadora debe definir dos prototipos diferentes para las funciones aritméticas:
operacionX(operando1,operando2) :
o Si el estado interno es diferente de 0 se pone a 0 antes de hacer la operación
o La función resuelve internamente la expresión <operando1> operaciónX
<operando2>
Página 4
operaciónX(operando2): Este método considera el estado interno de la última operación
como operando 1 .
X representa las operaciones posibles que
serán:suma(SUM),resta(RES),multiplicación(MUL),división(DIV)
Veamos a continuación todo esto con un ejemplo .Si queremos calcular ((1+5)-3) la secuencia de
invocación de operaciones sobre la calculadora será:
Operación sumar 1 y 5
Operación restar 3 //Toma el resultado almacenado internamente como operando1 y
le suma 3.
Ver el resultado //Mostraría 3
Deshacer operación //Devolvería 6
Deshacer operación // Sigue devolviendo 6 no deshace 1+5
Nota importante: Deshacer operación sólo puede mostrar el resultado anterior a la última llamada de
operacionX realizada que en el ejemplo anterior sería operacionRES(3).
Se pide:
Diagrama UML de la clase Calculadora
Un programa principal que pruebe la siguiente secuencia de operaciones
o (((((((5+1)-3)deshacerOP)+6)/4)deshacerOP)+12) resultado 24
Práctica 4. Java
Un datagrama IP es una unidad de transferencia y organización en las redes de comunicación IP .El
datagrama IP se divide en campos de control (cabecera) y datos .La siguiente figura muestra el detalle
de su estructura.
Versión Longitud de cabecera Tipo de Servicio Longitud total
Identificador Flags Offset
TTL Protocolo Checksum de cabecera
Dirección IP origen del datagrama
Dirección IP destino del datagrama
Opciones Relleno
Datos
Figura. Estructura de un datagrama IP
Considerando sólo la parte de control, nos centraremos sólo en los siguientes campos:
Campo de
control
Descripción y observaciones
Longitud total: Tamaño en bytes del datagrama(cabecera y datos). Máximo valor 65535 octetos
Flag DF Simplificamos este campos a los siguiente:
o Si vale DF=0 significa que se puede fragmentar
o Si vale DF=1 significa que no se puede fragmentar
Dirección IP
origen y destino
Una dirección IP es una etiqueta numérica que identifica, de manera lógica y
jerárquica, a una interfaz (elemento de comunicación/conexión) de un dispositivo
http://es.wikipedia.org/wiki/Interfaz_de_red
Página 5
(DIR.IP ORIGEN Y
DIR.IP.DESTINO)
(habitualmente una computadora) dentro de una red que utilice el protocolo IP
(Internet Protocol), A través de Internet, los ordenadores se conectan entre sí
mediante sus respectivas direcciones IP.
Ejemplo de dirección IP destino 192.146.123.45
o IMPORTANTE :Suponiendo siempre una red de clase C tenemos
que 192.146.123 es la dirección de red
TTL
Time to live .Indica el máximo número de enrutadores que un paquete puede
atravesar. Cada vez que algún nodo procesa este paquete disminuye su valor en 1
como mínimo, una unidad.
NOTA IMPORTANTE
Las direcciones IP serán representadas con tipo cadena s (Ej “192.156.124.45”)
Para obtener la red de la dirección IP investigue las funciones lastIndexOf(…) y substring(…) de la clase
String en Java.Para ello considere que la dirección de red de la IP “192.156.124.45” sería “192.156.124”
La clase Datagrama deberá contemplar la siguiente funcionalidad:
Construir un Datagrama con todos y cada uno de los campos
Que se pueda modificar el estado de la propiedad TTL del datagrama.
Obtener el valor de longitud del datagrama.
Y comprobar si el paquete se puede fragmentar. El método devolverá true si se puede y false
en otro caso
Se pide:
clase UML de la clase DatagramaIP
Realice una clase denominada ProgramaPrincipal que realice lo siguiente:
o Construya un datagrama (46000,1,”10.16.0.2”,”10,16.0.15”,5)
o Cambie el estado del su TTL a 4.
o Imprima por pantalla si el datagrama de puede fragmentar.
o Imprimir por pantalla la dirección de red de la dirección origen
http://es.wikipedia.org/wiki/Computadora
http://es.wikipedia.org/wiki/Red_de_computadoras
http://es.wikipedia.org/wiki/Protocolo_de_Internet
Programacion Orientada a Objetos 2015-2016 Campus/T4 paracticas obligatorias parte 1v4 corregida.pdf
1
UPSAM 2011-2012 Laboratorio de prácticas
Programación. Prácticas obligatorias
Normativa:
Las siguientes prácticas son obligatorias para aprobar la asignatura y tienen que ser entregadas y
explicadas antes de la fecha fijada por el profesor.
El material a entregar será un diagrama UML por cada clase que se diseñe y el código fuente de
la práctica en el lenguaje requerido.
No se corregirán prácticas que no compilen.
Las prácticas son individuales.
2
Práctica 1.Java
Un programa de dibujo simple trabaja con una serie de tipos de figuras geométricas como son el Circulo y el
Cuadrado .Se debe poner calcular sobre cuqluier tipo de Figura el valor del área
Se pide:
a) Diagrama de clases UML
b) Una Clase ProgramaPrincipal que defina el siguiente método prototipo :
private void calcularArea(…..){
//obtener el área de la figura e imprimirla por pantalla
}
c) En el método main() realice lo siguiente:
Crear un cuadrado lado=4
Crear un circulo con radio=6
obtener el área del circulo y cualdrado através del método calcularArea(..)
mencionado anteriormente.
Práctica 2. Java
Un módulo de control de tareas ejecuta diversos tipos de tareas. Una Tarea se define a partir de un script o
guion (cadena) que indica las instrucciones que hay que ejecutar, un valor de prioridad (valor 1..3) y un
GUID (numérico). El módulo trabaja fundamentalmente con dos tipos de Tareas: Tarea Planificada y
Tarea Normal. Las características de cada tipo de tarea son las siguientes.
Tarea Normal: Cuando el modulo de control la ejecute (llamada a ejecutar ()) , se ejecutará siempre
que se cumpla la condición GUID=0 y prioridad > 2 dentro de la tarea.
Tarea Planificada: Es una tarea que tiene una fecha de ejecución (día (numérico) y mes
(numérico)), y cuando el modulo de control la ejecute sólo se ejecutará si su fecha interna
(previamente establecida) coincide con la fecha del instante de ejecución. Si se intenta ejecutar la
tarea fuera de esa fecha sencillamente no pasará nada.
Un ejemplo de la tarea de planificación podría ser día=1 mes=12 lo que indicaría que la tarea sólo se
ejecutaría dentro del módulo si ocurre el 1 de diciembre.
Todas las tareas tienen además un indicador de estado(0/1) que indica si la tarea ya ha sido ejecutada
estado=1 o no luego estado=0.
La clase modulo de control de tareas dispone de dos métodos: registrar(Tarea) y ejecutarTareas(). El
primer método registra una tarea dentro del módulo para ser ejecutada posteriormente y el segundo método
ejecuta todas y cada una de las tereas registradas hasta ese momento, además cambia el estado interno de cada
tarea ( 1 si se ejecuto o 0 sino).
Se pide:
Modele la relación de generalización /especialización
de las tareas del enunciado así como la
asociación de estas con la clase módulo de control.
Cree una clase ProgramaPrincipal que
o Cree un array de tamaño 3
o Cree 1 Tarea Ordinaria (T1) y 2 Tareas Planificadas (T2,T3)
o Establezca la fecha en la que tiene que ejecutar la T2 (Ejemplo fecha de hoy) y T3 (día
tres del mes que viene).
o Registre las tareas en el módulo de control de tareas
o Ejecute todas las tareas y compruebe el estado de cada tarea. Muestre por pantalla este
estado
3
Consideraciones sobre la práctica:
Para obtener el día y el mes se debe usar la clase GregorianCalendar (import java.util.Calendar y
import java.util.GregorianCalendar) . El siguiente fragmento de código permite obtener el día y mes
actual. Utilice este código para la Tarea Planificada
GregorianCalendar fechaActual=new GregorianCalendar();//crea un objeto representando la fecha actual
int diaMes=fechaActual.get(Calendar.DAY_OF_MONTH);//Obtiene el día del mes actual
int Mes=fechaActual.get(Calendar.MONTH);//Obtiene el mes del año actual
El atributo script es de tipo cadena y tiene el siguiente formato:
“I1:comando1,I2: comando2,…”.
Se considera que el comportamiento del método ejecutar de una tarea es el siguiente
Prototipo String ejecutar()
o devuelve la cadena “EJECUCIÓN : script ={ I1:comando1,I2:comando2}” si la
ejecución puede realizarse o la cadena “ NO ES POSIBLE LA EJECUCIÓN” en
otro caso.
Práctica 3.C++
Un sistema Domótico usa los siguientes tipos de sensores entre otros: Humedad y Temperatura. En general un
Sensor se caracteriza por las siguientes propiedades:
Un identificador de dispositivo( cadena “ID00N”)
Lectura (numérico )
Unidades de interpretación de la lectura (HumendadRelativa % ,Temperatura(Celsius) (cadena)
Estado actual (cadena)
De manera general un Sensor se puede activar, leer. Los estados posibles para cada tipo de sensor se
muestran en la tabla siguiente:
Tipo de sensor Estado (inicial) Estados posibles
Sensor Humedad “Operativo” “Error”,”Operativo”
Sensor Temperatura “Operativo” “Error”,”Operativo”,”Perdida
Contacto”
Cada sensor implementa la operación activar() cambia el estado del sensor en de la siguiente forma:
Sensor Humedad: Estado =”Operativo”
Sensor Temperatura: Estado =”Operativo”
La operación double leer() de cada sensor tiene el siguiente comportamiento
Sensor de Humedad y Sensor Temperatura: Si el sensor está en estado “Operativo” devuelve la
última lectura obtenida en otro caso -1.
Además se dispone de una clase denominada Detector que permite obtener el vector de estado a través de
un método denominado obtenerEstado(), de todos los sensores registrados(método registrar(Sensor)) .la
clase Detector además implementa un método denominado activarSensores() que activa todos y cada uno
de los sensores registrado.
4
Se pide :
Cree un modelo UML de las clases implicadas en el ejemplo
Cree una clase ProgramaPrincipal que en su método main realice lo siguiente:
o instancie 2 objetos de tipo Sensor, uno de cada tipo.
o Instanciar una objeto Detector
o Registrar los dos sensores en el ámbito del módulo Detector a través del método
registrar (Sensor )).
o Y realizar la siguiente operación con el objeto Detector :
Activar todos los sensores.
Obtener el vector de estado
Como ejemplo considere la siguiente salida en el programa principal
<ID001:Estado:”Operativo”:Lectura:13.4,
ID002:Estado:”Error”:Lectura:_,
ID003:Estado:”Operativo”:Lectura:30:” ,….>
Programacion Orientada a Objetos 2015-2016 Campus/TEMA 1 Introduccion Programacion clásica y POOv2.pdf
1
Tema 1
Programación clásica
y Programación
Orientada a Objetos
2
Índice de contenidos
1. Programación clásica y crisis de software ............................................................................. 3
2. La orientación a objetos ........................................................................................................ 4
3. Reutilización del código ........................................................................................................ 6
4. Programación y abstracción .................................................................................................. 8
5. Lenguajes de programación orientados a objetos ................................................................ 10
5.1. Clasificación de los lenguajes de programación ......................................................... 12
5.1.1. Lenguajes de primera generación ........................................................................ 12
5.1.2. Lenguajes de segunda generación ....................................................................... 12
5.1.3. Lenguajes de tercera generación ......................................................................... 13
5.1.4. Lenguajes de cuarta generación o actuales .......................................................... 13
6. Lenguajes Orientados a Objetos ..................................................................................... 14
6.1. Características básicas de los lenguajes de programación orientados a objetos .......... 14
7. Bibliografía ..................................................................................................................... 16
3
1. Programación clásica y crisis de software
A finales de los años 60, en una conferencia organizada por la Comisión Científica de
la OTAN se reconoció abiertamente que había un problema en el desarrollo de los
sistemas software. Las aplicaciones que se construían, o bien no llegaban nunca a
completarse con éxito, o bien, además de ser muy costosas, y de estar mal estructuradas
eran prácticamente imposibles de mantener, como consecuencia la mayoría de las
aplicaciones eran poco fiables. Esta situación fue denominada crisis del software.
Las causas de esta crisis tienen el origen en la complejidad intrínseca de las
aplicaciones software, que deriva fundamentalmente de cuatro elementos que se
pueden identificar en aplicaciones de mediana y gran envergadura:
La complejidad de gestionar el proceso de desarrollo: Normalmente el equipo
de desarrollo no suele tener claro desde el primer momento qué se espera de la
aplicación, ya que los usuarios no siempre consiguen transmitir sus necesidades
y expectativas. Esto obliga a realizar multitud de cambios en los requisitos
durante el desarrollo, lo cual dificulta considerablemente este proceso. Si la
captura de requisitos fuera eficiente y el usuario los pudiera verificar mediante
prototipos en fases tempranas del desarrollo, esta dificultad y el coste respectivo
se reducirían.
La complejidad del dominio y de la solución hace que haya que descomponer
la aplicación en gran cantidad de módulos y, por lo tanto, que se requieran
grandes equipos de desarrollo. Hay que tener en cuenta que hablamos de
aplicaciones que modelan el mundo real. Esto comporta la dificultad de
gestionar un equipo de desarrollo con muchos miembros, manteniendo una
unidad e integridad del diseño de la aplicación.
Ausencia de estándares: casi siempre que se construye una aplicación, se
identifican partes que se podrían resolver de manera parecida a como se hizo en
aplicaciones anteriores, pero la ausencia de estándares en la industria del
software hace que aquello que ya se ha implementado sea difícilmente
reutilizable.
Caracterizar el comportamiento de sistemas discretos es difícil. Estos
sistemas pueden tener un número muy grande de estados. Por este motivo, las
pruebas no se pueden considerar nunca completas, ya que es prácticamente
imposible simular
todas las situaciones por las que puede pasar el sistema.
Esta complejidad, unida a la falta de una metodología a la hora de desarrollar las
aplicaciones, hacía que los desarrollos basados en las estructuras de datos clásicas
presentaran los siguientes inconvenientes:
Limitaciones en el modelado de problemas no estructurados, ya que el
método utilizado para construir las aplicaciones está basado en el hecho de
descomponer la aplicación en una jerarquía de módulos funcionales ideados para
transformar unas entradas determinadas en salidas bien definidas. Este tipo de
4
diseños no facilitan resolver problemas complejos desestructurados. El enfoque
tradicional, se trata de un enfoque más apropiado para resolver problemas
estructurados, en los que se puede describir el código según algoritmos de
transformación de datos.
Dificultad a la hora de reutilizar el código, ya que los módulos se
caracterizaban por la transformación de unos datos en concreto, cosa que los
hacía muy dependiente de la aplicación original.
Mantenimiento difícil y costoso, ya que los módulos estaban muy orientados a
tratar los datos y normalmente, durante su desarrollo, no se tenían en cuenta
posibles cambios futuros.
Reducción de la calidad de las aplicaciones, medida en flexibilidad, eficiencia,
fiabilidad y robustez, criterios que trataremos en el módulo siguiente. La calidad
se reduce a medida que se hacen modificaciones, ya que éstas complican cada
vez más el diseño original, con lo cual se resta flexibilidad al desarrollo y cada
vez resulta más complejo introducir nuevos cambios.
2. La orientación a objetos
La orientación a objetos se podría definir como el conjunto de disciplinas que
desarrollan y modelan sistemas software a partir de componentes (objetos), lo que
facilita la construcción de sistemas complejos.
Podemos decir que la orientación a objetos es una nueva forma de resolver problemas
de estructuración y representación más ajustada al mundo real y, por lo tanto, más
próxima al proceso mental de las personas.
La orientación a objetos está basada en tres aspectos organizativos próximos al
razonamiento humano, ya que establecen conexiones entre los siguientes elementos:
Un objeto y sus características; por ejemplo, un coche tiene un color, un
número de puertas, un tipo de motor, etc.
Un objeto y otros objetos con los que se asocia o interactúa (que al mismo
tiempo se relacionan con otros objetos); por ejemplo, el coche tiene ruedas (con
unas características propias), un motor, etc.
Un objeto general y otros objetos que comparten sus mismas propiedades
pero más específicos; por ejemplo, un coche puede ser deportivo o familiar.
La programación orientada a objetos es una técnica para construir aplicaciones más
antigua de lo que puede parecer. Si bien pasó desapercibida hasta principios de la
década de los noventa, su origen se remonta a 1967, cuando dos noruegos, Ole-Johan
Dahl y Kristen Nygaard, idearon los conceptos básicos de la programación orientada a
objetos tal y como se conoce hoy.
5
El atractivo de la orientación a objetos es que proporciona conceptos y herramientas
con las cuales se modela, de forma a aproximada a como entendemos la realidad,
es decir basada en objetos con propiedades.
Como apuntaban Ledbetter y Cox (1985).
La programación orientada a objetos permite una representación más directa del
modelo de mundo real en el código. El resultado es que la transformación llevada
a cabo de los requisitos del sistema (definidos en términos del usuario) a la
especificación del sistema (definido en términos del la computadora) se reduce
considerablemente.
Teniendo en cuenta todo lo que hemos explicado hasta ahora, podemos decir que la
orientación a objetos va más allá de la implementación de aplicaciones. Podemos decir
que define toda una filosofía para analizar y diseñar sistemas dinámicos.
Antes de empezar a implementar cualquier problema, es necesario examinar qué
requisitos existen y desde la perspectiva de la programación orientada a objetos usar
clases y objetos que aproximen el dominio del problema. Una vez obtenido el modelo
del problema, será necesario refinar y adaptar el modelo al lenguaje de programación
que se utilizará, dado que no todos los lenguajes implementan los conceptos de la
programación orientada a objetos de la misma manera.
A continuación, mostraremos un ejemplo que nos permitirá comprobar las diferencias
entre el desarrollo orientado a objetos y el desarrollo procedimental.
Problema
Tenemos que hacer una aplicación para gestionar las nóminas de los profesores de una facultad y la
dotación económica que recibe cada departamento.
En la facultad, cada profesor imparte docencia en una asignatura, cobra de acuerdo con las horas
impartidas y pertenece a un departamento.
Según la carga docente, los departamentos tienen una dotación económica u otra. Para nuestra aplicación,
necesitaremos saber los datos que modelan los departamentos (nombre, director, secretario, dirección
postal, los profesores que hay asignados, etc.) y una operación que nos diga cuál es la dotación económica
de los departamentos (para evaluarla, necesitaremos saber el número de horas de docencia de todos sus
profesores).
De los profesores, aparte de los datos identificadores (el nombre y los apellidos, el despacho, el correo
electrónico, etc.), necesitaremos poder consultar el nombre de las asignaturas que imparten y las horas de
docencia que realizan en estas asignaturas.
De las asignaturas, habrá que saber el nombre y las horas de docencia.
Solución con el desarrollo clásico
Por una parte, tendríamos que definir las estructuras de datos que necesitaríamos en nuestra aplicación
(departamento, profesor y asignatura) y, para cada una de éstas, deberíamos definir una estructura de
datos para almacenarlas todas.
Después necesitaríamos una estructura de datos que nos permitiera representar las relaciones entre los
departamentos y los profesores, y otra para representar la relación entre los profesores y las asignaturas.
Finalmente, tendríamos que implementar las operaciones que nos permitieran realizar las funcionalidades
pedidas, y también otras operaciones que nos sirviesen para “navegar” entre las relaciones almacenadas.
Solución con el desarrollo orientado a objetos
6
En este caso, lo que tendríamos que hacer es identificar cada entidad (departamento, profesor y
asignatura) y utilizar tres módulos diferentes en los que se definirían las propiedades de cada entidad y se
implementarían las funcionalidades por separado.
Por ejemplo, en la operación del departamento de cálculo de las horas lectivas (para calcular la
asignación), en vez de tener que navegar por toda la estructura de datos para saber si un profesor es o no
del departamento, puesto que cada departamento ya está
relacionado con sus profesores, sólo haría falta pedirles las horas lectivas y sumarlas.
Una vez definidas estas entidades en nuestra aplicación de gestión, tendríamos tantos objetos de tipo
Profesor como profesores haya, cada uno de los cuales con los datos que lo definen, y tantos objetos
de tipo Departamento como departamentos haya en el sistema.
En este caso, no sería necesaria una estructura que representara las relaciones, ya que los objetos ya están
relacionados entre sí.
A la vista del ejemplo anterior, podríamos decir que la orientación a objetos permite
agrupar las funcionalidades referentes a un mismo tipo de objeto en una misma
entidad y, por lo tanto, localizar posibles problemas es mucho más rápido y hacer
modificaciones es más sencillo.
Algunos argumentos que podríamos esgrimir de forma rápida para usar
la programación
orientada a objetos son la mejora de la calidad de las aplicaciones, la escalabilidad y
adaptabilidad de éstas, y también la disminución del tiempo y el coste de desarrollo.
Gran parte de estas ventajas provienen de otra fundamental: el concepto de reutilización
del código, que veremos en el apartado siguiente.
3. Reutilización del código
Cuando se construye un automóvil, o un dispositivo electrónico, se ensamblan una serie
de piezas independientes, Existen piezas que se diseñan una vez y se reutilizan para
distintos modelos, en vez de fabricarlos cada vez que se necesita construir un nuevo
circuito u automóvil. En la construcción de software surge la cuestión. ¿Por qué no se
utilizan componentes ya construidos para formar parte de otros sistemas?
Las técnicas orientadas a objetos proporcionan un mecanismo para construir
componentes de software reutilizables que posteriormente podrán ser interconectados
entre sí para formar grandes proyectos de software. Esta propiedad de poder utilizar
elementos de software programados y probados previamente durante la construcción de
aplicaciones nuevas se denomina reutilización o reusabilidad.
Podemos resumir las ventajas principales que se obtienen de reutilizar un código de la
siguiente forma:
Disminución del esfuerzo de mantenimiento. Ya que se utilizan componentes
previamente probados en otros sistemas. Además puesto que todo el código que
afecta a una funcionalidad concreta está en un mismo módulo, las tareas de
mantenimiento se basan en modificaciones específicas y lo que es má
importante están acotadas.
Mayor velocidad en el desarrollo de aplicaciones, favorecida principalmente
por el hecho de poder reutilizar componentes totalmente funcionales .
7
Aumento de la fiabilidad de los programas. El módulo reutilizable habrá
superado pruebas de funcionamiento previas, con lo que su fiabilidad habrá sido
comprobada.
Abaratamiento de costes favorecido por el hecho de no tener querealizar
nuevos desarrollos, ya que se pueden aprovechar de proyectos anteriores.
Para ejemplarizar mejor el concepto de reutilización de código, podemos hablar de las
interfaces gráficas de usuario. Esta colección de elementos que utilizamos cuando
desarrollamos aplicaciones gráficas (menús, botones, cuadros de texto, etc.) no los
programamos cada vez que realizamos un proyecto, sino que los tomamos de un
repositorio que los creadores del sistema operativo o el lenguaje de programación nos
ofrecen. Si los tuviéramos que implementar nosotros, aparte de que deberíamos tener un
gran conocimiento sobre este tema, necesitaríamos mucho más tiempo, ya que estos
elementos los utilizamos muchas veces en nuestro programa.
El desarrollo de los elementos de las interfaces gráficas ha sido realizado por un grupo
de programadores una única vez, y todos nosotros sacamos provecho del mismo. Por lo
tanto, podemos decir que, en este caso, aprovechamos los siguientes aspectos de la
programación orientada a objetos:
Eficiencia de nuestra aplicación para tratar la interfaz gráfica, ya que suponemos
que estos elementos son lo más eficientes posible.
Fiabilidad de la interfaz gráfica, ya que estos elementos están muy probados y,
por lo tanto, no presentarán un comportamiento inesperado.
Consistencia de la interfaz de usuario, dado que todos los elementos tienen unas
características parecidas.
Más velocidad de desarrollo y menos costes. Como hemos comentado, estos
elementos ya nos vienen dados y, por lo tanto, ahorramos tiempo y dinero.
Eliminación de los costes de mantenimiento, ya que estos elementos, puesto
que ya están hechos, no necesitan –ni pueden– ser modificados.
8
4. Programación y abstracción
El proceso de abstracción es común a todas las ciencias, y como vemos forma parte del
proceso de razonamiento humano.Las personas normalmente comprenden el mundo
construyendo modelos de partes del mismo: un modelo mental es una vista simplificada
de cómo funcionan las cosas. Los modelos abstraen las características básicas o
esenciales para un propósito particular y desechan las irrelevantes. A este proceso se
denomina abstracción.
Veamos la definición de abstracción del Diccionario de la Real Academia Española
(D.R.A.E).
abstracción.
(Del lat. abstractĭo, -ōnis).
1. f. Acción y efecto de abstraer o abstraerse.
abstraer.
(Del lat. abstrahĕre).
1. tr. Separar por medio de una operación intelectual las cualidades de un objeto para
considerarlas aisladamente o para considerar el mismo objeto en su pura esencia o
noción.
2. intr. Prescindir, hacer caso omiso. Abstraer DE examinar la naturaleza de las cosas.
U. t. c. prnl.
La abstracción es esencial para el funcionamiento de la mente humana normal y es una
herramienta muy potente para tratar la complejidad.
En general un programa de ordenador no es más que una descripción abstracta de
un procedimiento o fenómeno que sucede en el mundo real. La relación entre
abstracción y lenguaje de programación es doble: por un lado se utiliza el lenguaje de
programación para escribir un programa que es una abstracción del mundo real; por otro
lado se utiliza el leguaje de programación para describir de un modo abstracto el
comportamiento físico de la computadora que se está utilizando (Ej usando números
decimales en lugar de números binarios, variables en lugar de celdas de memoria
direccionadas directamente).
Como describe Wulf: << Los humanos hemos desarrollado una técnica
excepcionalmente potente para tratar la complejidad: abstraernos de ella. Incapaces de
dominar en su totalidad los objetos complejos, se ignora los detalles no esenciales,
9
tratando en su lugar con el modelo ideal del objeto y centrándonos en el estudio de sus
aspectos esenciales>>.
Algunas reglas interesantes para realizar abstracciones en programación orienta a
objetos son las siguientes:
En primer lugar se debe identificar los objetos principales.
Tener en cuenta que la abstracción es un proceso iterativo de ensayo-y-error, con
aproximaciones y refinamientos sucesivos.
Posteriormente se debe definir propiedades y métodos esenciales de cada
objeto. En definitiva características relevantes o esenciales en el dominio del
problema.
Si existen objetos semejantes ,clasificar los objetos según sus
semejanzas/diferencias
Por último se debe establecer cómo se relacionan entre sí, cómo se comunican
(eventos).
En la década de los cincuenta, uno de los pocos mecanismos de abstracción para
ordenadores era el lenguaje ensamblador y lenguaje máquina. Posteriormente los
lenguajes de programación de alto nivel ofrecieron un nuevo nivel de abstracción.
Los diferentes paradigmas de programación han aumentado su nivel de abstracción,
comenzando desde los lenguajes máquina, los más próximos al ordenador y más
lejanos a la comprensión humana; pasando por los lenguajes de comandos, los
imperativos, la orientación a objetos (OO).
La abstracción ofrecida por los lenguajes de programación se puede dividir en dos
categorías: abstracción de datos (pertenecientes a los datos) y abstracción de control
(perteneciente a las estructuras de control)
El siguiente diagrama se puede observar la evolución de los lenguajes de programación.
10
Existe como vemos una evolución constante en el nivel de abstracción apreciable a lo
largo de la historia y evolución de los lenguajes de programación.
5. Lenguajes de programación orientados a objetos
El primer lenguaje orientado a objetos fue SIMULA 67. Fue creado por científicos
noruegos (Nygaard y Gahl) que, después de probar otros lenguajes de tercera
generación, decidieron crear un lenguaje
que les permitiera realizar todo lo que los otros
no les permitían a causa de las limitaciones que tenían. SIMULA 67 fue el primer
lenguaje de programación en el que se introdujeron los conceptos de clase y objeto.
Más tarde, a principios de los años setenta, en los laboratorios de Xerox, un equipo de
desarrollo intentó implementar un prototipo de ordenador para niños, conocido como
DynaBook, que utilizaba por primera vez el concepto de interfaz gráfica de usuario.
Enseguida los desarrolladores relacionaron las ideas de la programación orientada a
objetos con las piezas del ordenador nuevo, ya que se podía definir perfectamente el
funcionamiento de cada elemento gráfico como un objeto, porque tenía un
comportamiento muy definido y respondía a interacciones muy concretas.
Para implementar el proyecto DynaBook, elaboraron un lenguaje de programación
nuevo denominado Smalltalk, que estaba fuertemente influido por SIMULA 67. Este
lenguaje introducía el concepto de herencia de manera explícita. La herencia es el
mecanismo que permite que un objeto comparta las características y el comportamiento
de otro objeto, del cual se dice que hereda.
El concepto de herencia es muy valioso para reutilizar código, ya que determinados
objetos, aunque no son idénticos, sí que tienen características comunes. Por ejemplo,
toda persona tiene un nombre y una fecha de nacimiento. Estas propiedades se utilizarán
tanto si dentro de nuestra aplicación la persona es una estudiante como si es un profesor.
En próximos temas, lo estudiaremos más detalladamente.
A partir de este momento, los lenguajes de programación orientados a objetos van
incorporando características nuevas que los hacen evolucionar hasta los lenguajes de
programación que conocemos hoy en día.
En la década de los ochenta, Bjarne Stroustrup, empleado de AT&T Labs., amplió el
lenguaje de programación C para adaptarlo a la programación orientada a objetos.
Inicialmente se denominó C with classes, pero finalmente se llamó C++. Este lenguaje
está basado en el SIMULA 67, con respecto a la orientación a objetos, y en el C, en
cuanto a la sintaxis.
Desde que aparecieron los lenguajes orientados a objetos, éstos han ido aportando
funcionalidades nuevas a la programación orientada a objetos en versiones sucesivas,
como las clases abstractas, los métodos static o la genericidad. Veremos todos estos
conceptos en los próximos módulos.
11
Actualmente hay muchos lenguajes de programación orientada a objetos, unos
descendientes de lenguajes procedimentales que se han adaptado al paradigma de la
programación orientada a objetos (POO) y otros que se han creado desde el principio
pensando en este paradigma. Por poner algunos ejemplos, aparte de C++, SIMULA 67 y
Smalltalk, que ya hemos mencionado antes, podemos mencionar Perl 5, Python, Java,
C#, Delphi, Eiffel, etc.
El siguiente gráfico muestra la evolución y la relación existente entre distintos lenguajes
de programación.
12
5.1. Clasificación de los lenguajes de programación
Los lenguajes de programación han tenido grandes transformaciones durante su historia.
Hay muchas clasificaciones que, según diferentes criterios, así lo muestran. Una de las
más conocidas es la clasificación de Wegner, que divide los lenguajes de
programación de alto nivel de acuerdo con el orden de aparición y las
características comunes. Concretamente, la clasificación consta de lenguajes de
primera generación, de segunda generación, de tercera generación y la generación de los
lenguajes actuales.
5.1.1. Lenguajes de primera generación
Lenguajes que se desarrollaron entre 1954 y 1958. Con éstos se produjo un salto en la
abstracción a la hora de programar, ya que anteriormente se utilizaba el lenguaje
ensamblador, que requería un buen conocimiento de la máquina en la que se ejecutaría
el código. Algunos ejemplos de estos lenguajes son Fortran I, Algol 58 o IPL V: todos
estaban basados en codificar de la mejor manera expresiones matemáticas y, por lo
tanto, su dominio de aplicación estaba centrado en el cálculo y las aplicaciones
científicas y de ingeniería.
Los programas que se hacían con estos lenguajes se basaban en subprogramas que
compartían los datos al ser ejecutados.
Esta situación presentaba problemas serios porque cualquier error en el funcionamiento
de un subprograma se propagaba al resto de la aplicación. Aparte, de que cuando el
programa era demasiado grande, cualquier cambio se traducía en una tarea muy costosa
que no siempre se conseguía llevar a cabo sin complicar todavía más el diseño original.
5.1.2. Lenguajes de segunda generación
Aparecieron a finales de la década de los cincuenta y a principios de los sesenta. En
estos lenguajes, se empezó a poner énfasis en la abstracción algorítmica y la
programación se extendió poco a poco a otros dominios del mundo real. Algunos de los
lenguajes pertenecientes a esta generación son Cobol, Algol 60 o Fortran II. En el
13
terreno de la estructuración de los programas, la segunda generación empieza a
introducir el concepto de procedimientos dentro de los subprogramas, con lo cual
aparecen los conceptos de paso de parámetros y de visibilidad de las variables.
5.1.3. Lenguajes de tercera generación
En esta época, los costes del hardware continúan abaratándose, lo cual hace que las
aplicaciones informáticas lleguen cada vez más a ámbitos más distintos. Esta
diversificación en los campos de trabajo comporta que se tenga que trabajar con tipos
de datos diferentes de los puramente matemáticos, y cambiantes de una aplicación a
otra. Por este motivo, en esta época aparece el concepto de tipo abstracto de datos que
permite al programador especificar un tipo adecuado para cada problema y dotarlo de
significado mediante un conjunto de operaciones sobre este tipo de dato.
Este nuevo concepto resuelve algunos problemas de la programación de aplicaciones
grandes, ya que de esta manera distintos desarrolladores pueden implementar módulos
diferentes que se pueden compilar por separado y, finalmente, combinarlos. A pesar de
todo, durante esta época el concepto de abstracción de los datos no fue demasiado
utilizado como tal, sino que simplemente permitía agrupar distintas funcionalidades en
módulos diferentes. Otro concepto que no existía era el de la visibilidad de los datos.
Por lo tanto, nada impedía que diferentes módulos modificasen directamente los datos
de otros módulos.
5.1.4. Lenguajes de cuarta generación
Estos son lenguajes de programación que no son de propósito general, como los
descritos anteriormente. Han sido diseñados para algún propósito específico. Entre
14
otros, están Sculptor ( lenguaje de dirigido por dominio) o lenguajes de especificación
como SQL.
Aunque no existe consenso sobre lo que es un lenguaje de cuarta generación (4GL).
Una característica relevante podría ser el hecho de necesitar menos líneas de código
debido a su poder de abstracción, y son lenguajes en los que no se indican detalles de
implementación, simplemente se especifica lo que se desea obtener , dejando al
interprete el implementar como hacerlo
Los 4GL se apoyan en unas herramientas de más alto nivel de abstracción denominadas
herramientas de cuarta generación. A groso modo los 4GL podrían abarcar:
Lenguajes de presentación, como lenguajes de consultas y generadores de
informes.
Lenguajes especializados, como hojas de cálculo y lenguajes de bases de datos.
Generadores de aplicaciones que definen, insertan, actualizan y obtienen datos
de la base de datos.
Lenguajes de muy alto nivel que se utilizan para generar el código de la
aplicación.
6.
Lenguajes Orientados a Objetos
Se denominan lenguajes orientados a objetos todos aquellos lenguajes de programación
que exhiben en mayor o menor medida una serie de características que veremos a
continuación, pero su rasgo esencial es que utilizan el concepto de clase de objetos
como mecanismo de representación de abstracciones y de organización del código.
6.1. Características básicas de los lenguajes de programación orientados
a objetos
Tipificación estricta de datos
Tipificar es el proceso de declarar el tipo de información que puede contener una
variable. Este hecho implica que si dos expresiones están relacionadas, tanto si se trata
de una asignación como si se trata de cualquiera de las operaciones que se pueden hacer
sobre expresiones, tienen que coincidir en tipo. Si no lo hacen, el compilador genera un
error en tiempo de compilación.
Encapsulamiento
Es un mecanismo que permite agrupar datos y operaciones relacionadas en una
misma entidad (clase ). Esta propiedad facilita que aparezcan otras características de la
programación orientada a objetos como la reutilización.
15
El encapsulamiento está relacionado con la ocultación de la información y con la
separación de una implementación y su especificación. Proporcionando un
acoplamiento más débil.
De esta manera, y debido a la ocultación de la información, los usuarios de una clase
disponen de unos métodos que permiten consultar y modificar el comportamiento de la
clase, pero no tienen acceso directo a los datos internos.
Para aclarar los conceptos de encapsulamiento y ocultación, podemos pensar en la clase
Date( existente en Java). Una fecha se puede descomponer en día, mes y año, y puede
tener unos métodos para acceder al día, al mes y al año y otros para modificar la fecha
que almacena.
Cualquier objeto que intente consultar o establecer la fecha de este objeto, lo tendrá que
hacer mediante las operaciones destinadas a esta finalidad y no podrá acceder
directamente a los atributos que representan el día, el mes y el año.
La ventaja de esta situación es que si en cualquier momento tenemos que cambiar la
representación interna de los datos almacenados, no será necesario modificar ninguna de
las aplicaciones que utilizan objetos de tipo Date, ya que, a pesar de cambiarse su
estructura interna, no se modifica su interfaz.
Otra de las ventajas del encapsulamiento es la posibilidad de ocultar operaciones
internas de la clase que no deben ser visibles por objetos externos a ésta. Por ejemplo,
podríamos tener un método que, dada una fecha, comprobase si ésta es correcta y que
sólo sea accesible internamente.
16
Esta operación oculta puede servir para asegurarnos de que no insertamos fechas
erróneas o para realizar cualquier otra operación que se utiliza internamente pero que no
se quiere que se ejecute desde otras partes del programa.
Genericidad
Esta propiedad permite la definición de módulos de programa (clases) con un alto grado
de reusabilidad. La genericidad es básica para reutilizar código. Estos módulos se
diseñan con parámetros formales genéricos, qué se instanciarán posteriormente con
parámetros reales específicos. Esta permite definir métodos que tienen como parámetros
elementos de cualquier tipo.
Un ejemplo de genericidad lo tendríamos si quisiéramos definir un objeto que fuera un
vector o una lista que pudiera recibir elementos de cualquier tipo. Esto tiene sentido
porque el comportamiento de este objeto siempre es el mismo (obtener, borrar, insertar,
etc.), independientemente del tipo u objeto contenido. De esta manera, lo podríamos
usar una vez como vector de enteros, otra como vector de caracteres, etc.
Herencia
Propiedad que nos permite definir una clase según otra u otras de manera que la clase
que hereda tenga el mismo comportamiento y las mismas características que la clase de
la cual hereda, más las características y el comportamiento de la propia clase.
Por ejemplo, podríamos definir una clase Persona con los atributos y métodos
correspondientes y dos clases más, Estudiante y Profesor, que hereden de la clase
Persona. Las tres clases tendrían una parte común, y las clases Estudiante y Profesor
añadirían otros métodos y atributos específicos para cada una de éstas.
Polimorfismo
El polimorfismo está estrechamente vinculado con la herencia. Se puede definir como la
propiedad por la cual se pueden realizar tareas diferentes invocando la misma
operación, y esta se realizará según el tipo de objeto sobre el cual se invoca.
Continuamos con el ejemplo anterior de los Estudiantes y los Profesores. Si tuviéramos
un método denominado obtenerCreditos, en el caso de un estudiante, debería devolver
el número de créditos de los que éste está matriculado, y en el caso de un profesor, nos
tendría que devolver el número de créditos de las asignaturas que imparte. Las tareas
necesarias para devolver el resultado según el tipo de objeto serían diferentes.
Éstas son las características principales de los lenguajes de programación orientada a
objetos, aunque hay lenguajes que incorporan otras características propias adicionales,
como la gestión de objetos en memoria (de C# y Java) o la gestión de excepciones.
7. Bibliografía
Object -Oriented Construction 2nd .Bertrand Meyers .ISE California
Java 2.Curso de programación. Fco Javier Ceballos.Ra-Ma
17
Programación orientada a objetos. Joan Arnedo Moreno. Daniel Riera I
Terrén.www,uoc.edu
Programming Languages.1976 Wegner
Programación Orientada a Objetos .Luis Joyanes Aguilar.Mc Graw Hill
Programacion Orientada a Objetos 2015-2016 Campus/TEMA 3 Relaciones entre clases v2.pdf
1
Tema 3
Relaciones entre
clases
2
Índice de contenidos
1. Introducción .......................................................................................................................... 3
2. Relaciones (asociaciones) entre clases .................................................................................. 3
2.1. Asociaciones entre clases .............................................................................................. 3
2.2. Cardinalidad/Multiplicidad ........................................................................................... 5
2.3. Navegabilidad de las asociaciones ................................................................................ 7
2.4. Tipos de asociaciones entre clases ................................................................................ 8
2.4.1. Asociaciones reflexivas ............................................................................................. 8
2.4.2. Asociaciones de agregación compartida ................................................................... 9
2.4.3. Agregación de composición ...................................................................................... 9
2.4.4. La clase asociación .............................................................................................. 10
3
1. Introducción
Hasta ahora hemos abordado los conceptos de la POO centrándonos en el concepto de
clase de forma aislada. Cuando nos enfrentamos al diseño de un sistema complejo lo
normal es que aparezcan numerosos conceptos o abstracciones relacionadas entre sí.
Una relación entre clases representa una conexión semántica entre objetos que son
instancia de esa clase y esta relación podrá tener distintas propiedades.
En este tema veremos cómo abstraer y modelar las relaciones semánticas entre clases,
viendo los distintos tipos de relaciones existentes y estudiando cada una de ellas.
Como
notación emplearemos UML ( anexo I) que es un lenguaje de representación
independiente del lenguaje de programación que usemos (C++ o Java en nuestro caso).
2. Relaciones /Asociaciones entre clases
Un aspecto muy importante del proceso de diseño es definir las relaciones que se tienen
que establecer entre las clases que representan el modelo del dominio del problema
que pretendemos resolver de la forma más completa posible. La idea es que las clases(
sus objetos) colaboran entre sí para realizar algún tipo de proceso o funcionalidad que es
preciso llevar a acbo.
Para representar las clases y sus relaciones utilizaremos como hemos mencionado antes
un diagrama de clases en UML (Unified Modeling Language). Un diagrama de
clases representa las clases y las asociaciones existentes entre ellas y las relaciones que
existirán entre los objetos . Las relaciones entre clases también se denominan
asociaciones.
2.1. Asociaciones entre clases
Dadas dos clases A y B una asociación representa algún tipo de relación existente
entre dichas clases en el dominio del problema. La asociación se representa en UML
con un segmento que une las dos clases (ver Figura 1).
Clase A Clase B
Asociación
Figura 1 Asociación entre clases
La asociación representa una relación estructural entre objetos normalmente de clases
diferentes y es la más común de las relaciones. Aunque es frecuente que la mayoría de
las asociaciones sean binarias, pueden existir relaciones ternarias o n-arias.
4
Las conexiones que describe una asociación dan lugar a interacciones entre los objetos
que comúnmente se denomina colaboración. En esencia, una asociación es una relación
entre clases que indica cómo se pueden enlazar juntas las instancias de las clases,
además de que estas van a colaborar para desarrollar alguna funcionalidad.
Por ejemplo, dado el siguiente diagrama de clases
Estudiante Curso
**
Una posible instancia de estos objetos sería la siguiente:
Pedro:Estudiante
Laura:Estudiante
Seguridad:Curso
Ingenieria sw:Curso
Otros ejemplos de asociaciones entre clases:
ColaMensajes Mensaje
Circuito Componente
Al observar estos ejemplos surgen las siguientes cuestiones. ¿Un objeto de tipo Mensaje
con cuantos objetos de tipo ColaMensajes está relacionado y viceversa? O de la misma
forma ¿Con cuántos objetos de tipo Circuitos puede estar relacionado un objeto de tipo
Componente?.
Al plantearnos las relaciones entre clases surgen cuestiones como la
cardinalidad/multiplicidad y la direccionalidad de la relación (sentido de la relación)
,conceptos estos que proporcionan información adicional acerca de la asociación. Más
adelante veremos estos importantes conceptos en detalle.
5
Implementación de la asociación entre clases
Dado el siguiente diagrama de clases (Figura 2
-nombre
-apellidos
Profesor
-nombre_dept
Departamento
* 1
Figura 2 Diagrama de clases
Vamos a ver como se implementaría en un lenguaje de programación Java :
public class Profesor {
String nombre;
String apellidos;
Departamento dept;
Profesor (){}
}
public class Departamento {
String nombre_dept;
List empleados;
Departamento(){}
public void setProfesor(Profesor p){
empleados.add(p);
}
}
2.2. Cardinalidad/Multiplicidad
Dado que una asociación representa una interconexión entre instancias de objetos de
dos clases, se podría indicar, cuántos objetos de cada clase pueden estar involucrados en
la asociación. Para ello se debe definir la cardinalidad (o multiplicidad) de la
asociación.
Cardinalidad/Multiplicidad: es el número de objetos de un extremo de la asociación
que están enlazados con un objeto del otro extremo. Por ejemplo, un objeto de tipo
Departamento puede emplear (relación) a muchos objetos de tipo Empleado. La
multiplicidad de la relación de Departamento con Empleado es "una a muchos": 1 .. *.
Podemos indicar el valor máximo y mínimo de la cardialidad , la cardinalidad máxima y
mínima se ponen encima o debajo de la línea que representa la asociación y,
dependiendo de los valores, tienen un significado u otro.
Clase A Clase B
1..* *
cardinalidades
Figura 3 Representación de cardinalidades
La cardinalidad es una de restricción introducida en el modelo para representar más
fielmente la realidad (Ejemplo un estudiante sólo puede estar matriculado en un único
6
curso). Esto tendrá su implicación en la implementación de la asociación en el
lenguaje de programación.
La cardinalidad se puede expresar mediante diferentes notaciones:
Número exacto (1, 3, etc.). Expresa que un objeto de la clase Clase A tiene que
estar relacionado obligatoriamente con el número de objetos que indica la
cardinalidad de la clase Clase B .
Clase A Clase B
1
Rango de valores (0..1, 3..5, etc.). Con esta notación, indicamos el número
mínimo y máximo de objetos con los que puede estar relacionado un objeto en el
otro extremo de la relación. En el ejemplo un objeto instancia de la ClaseA sólo
podrá estar relacionado como mucho con 3 instancias de la clase B y como
mínimo lo estará con una.
Clase A Clase B
1..3
Muchos (*).Indica que la instancia de la clase Clase A puede estar relacionada
con cualquier número de instancias de la clase Clase B (o incluso con ninguna)
equivale a escribir 0..*.
Clase A Clase B
*
Combinaciones de las anteriores.
A continuación (Figura 4) se muestran algunos ejemplos de cardinalidades.
ColaMensajes Mensaje
*1
Cuenta Titular
21
Almacen Motor
*1
Pieza
1 *
7
Figura 4 Ejemplo de multiplicidades/cardinalidades
2.3. Navegabilidad de las asociaciones
Otra característica ( o restricción) que podemos expresar en una relación de clases
dentro de un diagrama UML es la navegabilidad de la relación. Esta restricción indica
cómo se debe interpretar la asociación. La navegación indica el sentido en el cuál es
posible recorrer la asociación.
Las dos opciones o tipos de navegabilidad posibles son las siguientes:
Asociación unidireccional: sólo una de las dos clases tiene constancia de la existencia
de la otra. Y consecuentemente sólo un objeto tendrá “constancia del otro”.
Persona Pais
El sentido semántico de esta restricción es el de que sólo un tipo de objeto participante en la
relación va a interpretar la relación , en el otro no es necesario.
Implementación en Java
public class Persona {
String nombre;
String apellidos;
Departamento dept;
Pais pais;
Persona(){};
}
public class Pais {
String nombre;
int población;
String religion_mayoritaria;
}
Como se puede observar en la implementación anterior, desde un objeto de tipo
Persona se tendrá acceso a uno de tipo País, pero no al revés.
Asociación Bidireccional: los dos tipos de objetos pueden interpretar la relación
existente.
-nombre
-apellidos
-dni
Ciudadano
-Nserie
-clase
-claves
CertificadoDigital
1 1
Ejercicio. Plantee como seria la implementación en Java de esta asociación
bidireccional
8
2.4. Tipos de asociaciones entre clases
2.4.1. Asociaciones reflexivas
Las asociaciones reflexivas son un tipo de asociación binaria, en la que las dos clases
origen y destino son del mismo tipo. Como muestra este tipo de relación, no hay
ninguna restricción que impida que una instancia de una clase se relacione con otras
instancias de la misma clase.
Se representan con una relación que entra y sale de la misma clase .Para poder
identificar correctamente los roles de los dos
extremos de la asociación, hay que
definirlos con un texto aclaratorio.
Clase
Figura 5 . Asociación reflexiva
Un ejemplo de esta asociación seria una clase que representa un directorio en un sistema
de archivos ( Figura 6).
Directorio
*
1
subdirectorio
Figura 6.Asociación Reflexiva
Implementación en C++
La implementación de una clase reflexiva del ejemplo anterior sería de la siguiente
forma
class Directorio;
class Directorio
{
char *nombre;
Directorio raiz;
List<*Directorio> subdirectorios;
public:
Directorio();
~Directorio();
};
9
2.4.2. Asociación de agregación compartida
Las asociación de agregación modela la relación semántica “todo/parte” o bien “ x
tiene un y” o “ y es parte de x”. A continuación podemos el esquema general de este
tipo de asociación.
Todo Partees parte de
El rol “Parte” puede pertenecer a más de un agregado (de ahí el nombre de agregación
compartida. La existencia del “Todo” está supeditada a la de las Partes. y la destrucción
del “Todo” no implica la destrucción de las Partes.
Ejemplo : un Equipo de trabajo se compone de diferentes Personas. Una misma
Persona puede ser miembro de más de un equipo de trabajo.
Otro ejemplo podría ser la relación de una Universidad con sus distintas Facultades
como se muestra en el siguiente diagrama:
Universidad Facultades parte de
*
La relación anterior lleva implícito el significado que si una de las partes desaparece la
parte Todo continua existiendo, de ahí que a este tipo de agregación se le denomina
débil. En el ejemplo una Facultad podría desaparecer y la Universidad seguiría
existiendo.
2.4.3. Agregación de composición
Una agregación de composición es aquella que posee (<<contiene>>) a sus partes y
entraña una fuerte dependencia de propiedad sobre ellas. Se dice entonces que la
agregación es fuerte lo que constituye la principal diferencia con la agregación
anterior.
La característica de agregación fuerte confiere a la asociación la característica de que
el Todo no puede existir sin las partes ni viceversa. El ciclo de vida de las Partes está
estrechamente ligado al del Todo, de forma que si este se destruye se destruyen también
las Partes. La cardinalidad en el lado Todo tiene que ser uno, mientras que en el lado
Parte puede ser un intervalo.
10
Todo Parte N
Parte 1 Parte 2
1
1
1
Figura 7 Relación de composición
A diferencia de la agregación simple la agregación de composición requiere que las
partes no puedan tener una relación de agregación con a otra clase ( no pueden estar
compartidas).
Ejemplos de agregación de composición pueden ser los siguientes diagramas de clases:
Avión Motor
Asientos Fuselaje
1
*
1 1
1 1
Figura 8 Ejemplos de asociación por composición
2.4.4. La clase asociación
En muchas ocasiones en el diseño, surge la necesidad de representar explícitamente una
relación entre clases como otra clase más debido a su relevancia y por que la propia
relación tiene características que deben ser reflejadas.
Una clase asociación es una clase que representa una relación entre clases y tiene la
suficiente relevancia en el problema para tener su propia representación. Es decir la
clase asociación nos permite caracterizar la propia relación existente entre clases.
Para ver esto considere el siguiente ejemplo (Figura 9):
Cliente Producto
-fecha
-cantidad
-importe
Pedido
* *
Figura 9 . Representación de una clase asociación
11
En este ejemplo la asociación entre la clase Cliente y Producto tiene tanta relevancia en
el dominio que se opta por modelarla como una clase más. Esto tiene la ventaja de que
se pueden especificar atributos y métodos que describen la propia relación.
Programacion Orientada a Objetos 2015-2016 Campus/T3 paracticas obligatoriasv3 2013_2014.pdf
1
UPSAM 2013-2014 Laboratorio de prácticas
Programación. Prácticas obligatorias
Normativa:
Las siguientes prácticas son obligatorias para aprobar la asignatura y tienen que ser entregadas
y explicadas antes de la fecha fijada por el profesor.
El material a entregar será un diagrama UML por cada clase que se diseñe y el código fuente
de la práctica en el lenguaje requerido.
No se corregirán prácticas que no compilen.
Las prácticas son individuales.
2
Práctica 1. Java
El sistema solar está formado fundamentalmente por una estrella (el Sol) y ocho planetas (o nueve para
los nostálgicos de Plutón) que giran en orbitas elípticas alrededor del Sol, además de estos astros en el
sistema solar existen: satélites, asteroides, cometas y meteoritos.
En esta práctica se pretende modelar el sistema solar de manera muy simplificada, con el sol, los
planetas y sus satélites más importantes con las siguientes características para cada astro:
Estrella
Atributos Funciones
Nombre
Radio ecuatorial
Temperatura del núcleo
Distancia respecto a la tierra
Obtener la temperatura en grados centígrados
Obtener el valor de cada uno de los atributos
Planeta
Atributos Funciones
Nombre
Radio ecuatorial
Temperatura media diurna
Distancia respecto al sol
Distancia respecto a la tierra
Conjunto de Satélites (si los tiene).
Valor temperatura en grados centígrados
Número de satélites
Valor del tamaño del planeta
Obtener distancia al sol
Atrapar astro en el campo gravitatorio
Satélites de planetas:
Atributos Funciones
Nombre
Radio
Obtener el valor nombre y el radio
Nota: esta información está incompleta a nivel de clase, el alumno deberá completar las funciones que
posteriormente sean necesarias para cada clase.
3
Nota: Aunque existen planetas con muchos satélites (Saturno tiene 23), sólo se desean reflejar los más
importantes (ver anexo práctica).
Consideraciones:
El sistema solar se debe formar de la siguiente manera:
1. Inicialmente está vacío
2. En el momento de la llamada al método denominado bigBang() dentro de la clase
SistemaSolar , se creará el Sistema Solar a partir de los planetas y satélites recogidos en el
anexo de la práctica con la siguiente secuencia:
a. Primero se forman las masas planetarias: planetas interiores y planetas exteriores
b. Segundo se forman las masas de los astros (Satélites) que posteriormente serán
capturados uno a uno por la fuerza gravitatoria de los planetas. Se debe considerar lo
siguiente en la captura:
i. La captura de un astro por el campo gravitatorio de un planeta se hará con la
operación : atraparAstroEnCampoGravitatorio(Satelite ) de la clase Planeta.
Ejemplo Tierra.atraparAstroEnCampoGravitatorio(luna) donde luna es un
objeto de tipo Satelite
A partir de ese momento el astro se convierte en un satélite del planeta.
Se pide:
A. Modelo de clases UML completo del Sistema solar. El sistema solar debe ser modelado como
una composición de Planetas y Satélites
B. Implementar métodos en la clase Sistema Solar para obtener los siguientes datos:
o Menor distancia de un planeta respecto al sol
o Nombre del planeta que menos satélites tiene
o Temperatura del planeta más caliente del sistema solar
o El Nombre del planeta más pequeño del sistema solar
o Número de satélites de un planeta del sistema solar determinado( es un parámetro)
C. En el programa principal realice el siguiente test de funcionalidad
o Crear el sistema solar
El estado inicial del Sistema Solar debe ser vacío
o Activar la operación bigBang ().es estado del sistema solar será el de estar formado
por todos los planetas y satélites
o Obtenga la siguiente información del sistema solar
- Número de satélites
de Jupiter
- Nombre del planeta que menos satélites tiene
- El nombre del Planeta que más temperatura tiene y el más pequeño
- Los satélites que tiene cada planeta ( los nueve) con la siguiente salida por
pantalla
*******************
“Tierra” : NSatelites =1
“Marte” :NSatelites=2 …
4
ANEXO DE LA PRÁCTICA
Datos del sistema solar
Sol
Parámetro Valor
Radio ecuatorial ( 510 km) 7
Temperatura media (K)
-del núcleo
15 x 610
Distancia de la tierra ( 810 km) 1,496
Planetas interiores
Parámetros Mercurio Venus Tierra Marte
Radio ecuatorial ( 310 Km) 2,439 6,052 6,378 3,396
Numero de satélites conocidos 0 0 1 2
Distancia media del sol ( 610 Km) 57,9 108,2 149,6 227,940
Distancia media de la tierra( 610 Km) 91,7 41,4 ----- 78,4
Temperatura media (K) diurna 700,15 Nubes (243,15 K)
Suelo (753,15 K)
289 293,15
Planetas exteriores o Jovianos.
Parámetros Júpiter Saturno Urano Neptuno
Radio ecuatorial ( 310 Km) 70,85 60 25,4 24,3
Numero de satélites conocidos 16 23 15 8
Distancia media del sol ( 610 Km) 778,3 1.429,4 2.871 4.504,3
Distancia media de la tierra( 610 Km) 628,8 1.277,4 2.719,7 4.347,4
Temperatura media (K) diurna 303,15 Nubes
(148,15 )
Nubes
(80,15)
Nubes
(120,15)
Principales Satélites( o lunas) de los planetas del sistema solar ( nota :no están todos)
Nombre Radio (Km) Planeta sobre el que
orbita
5
Luna 1738 Tierra
Phobos 17,5 Marte
Deimos 12,5 Marte
Io 1.801 Júpiter
Europa 1.569 Júpiter
Ganimedes 2.631 Júpiter
Calisto 2.400 Júpiter
Titán 2.575 Saturno
Mimas 390 Saturno
Tetis 525 Saturno
Dione 560 Saturno
Rea 764 Saturno
Hiperión 180 Saturno
lapetus 720 Saturno
Phoebe 110 Saturno
Oberón 582,60 Urano
Titania 788 Urano
Umbriel 584 Urano
Triton 1.350 Neptuno
Proteo 210 Neptuno
Nereida 170 Neptuno
Programacion Orientada a Objetos 2015-2016 Campus/Tema 3 Práctica 2 2013_2014.pdf
UPSAM 2013-2014 Laboratorio de prácticas
Programación. Prácticas obligatorias
Práctica 2 Java /C++
1
1
1
1
1
111
1
1
1
2 2
2
2
2
22
22
2
33
3 3
33
Matriz Raster
Imagen terreno
satélite idealizada
N
M
Escala=1:50000
Zona=A23
Provincia=Madrid
Término municipal=Escorial
Datos cartográficos
de la cobertura
3
Figura 1 imagen satélite con una representación codificada en formato Raster
Una imagen satélite de una cobertura geográfica contiene características del terreno (Ej .zonas de bosques,
zonas casas, zonas de cultivos, zonas fluviales, etc). La imagen se codifica en un formato denominado Raster
como un conjunto celdas donde cada valor de celda ser corresponde con el valor codificado de cada
característica. Para esta práctica los valores son los siguientes
celda (zona rio)=1
celda (zona pinos)=2
celda (zona cultivo)=3
celda (zona habitada)=5
El resultado es una matriz que representa la imagen satélite codificada. La codificación servirá para
realizar determinados tipos de algoritmos.
Consideraciones para la práctica
Se deberán abstraer las clases: Cobertura Geográfica y Matriz Raster. Sabiendo que la relación entre ambas
clases es la de que una Cobertura geográfica tiene asociada una única Matriz Raster. Se debe modelar como
una composición
A continuación se muestran datos relevantes de estas entidades.
Clase Características
Cobertura
Geográfica
Información cartográfica de interés (escala ,zona, provincia, término municipal)
forma codificada de la imagen de su cubiertaMatriz con datos Raster
Matriz Raster Matriz bidimensional de tamaño N x M de números enteros (supondremos siempre una
matriz cuadrada N=M)
Sobre el tipo de operaciones que podremos realizar en cada entidad se resumen en la siguiente tabla
Clase Operaciones
Cobertura
geográfica
Constructores necesarios
AlgoritmoAPermite obtener si existen ríos en la cobertura geográfica ( devuelve true o
false)
AlgoritmoBPermite obtener si el área de influencia de un rio en la cubierta pudiera afectar a
una zona de cultivo si este se desbordara
AlgoritmocPermite obtener si el área de influencia de un rio en la cubierta pudiera afectar a
una zona habitada si este se desbordara
Matriz Raster Constructores necesarios
Obtener (i,j) . Permite obtener el valor codificado que tiene la posición (i,j) de la matriz
Para implementar las operaciones considere la siguiente información:
- Clase Cobertura Geográfica AlgoritmoA: true si existe un rio en la cobertura
- Clase Cobertura Geográfica AlgoritmoB: obtener si el área de influencia de un rio pudiera afectar
a una zona de cultivo si este se desbordara
- Clase Cobertura Geográfica AlgoritmoC: obtener si el área de influencia de un rio pudiera afectar
a una zona habitada si este se desbordara
Se define el área de influencia de una característica (Ej rio desbordado)(Figura 2) como el conjunto de
celdas adyacentes a las celdas que forman la característica en cuestión. Por ejemplo el área de influencia del
rio para el ejemplo seria la siguiente:
1
1
1
1
1
111
1
1
1
Área de influencia del rio
Figura 2 Área de influencia de la característica rio
Para la determinación del área de influencia se usa el siguiente algoritmo:
Por cada celda (i ,j) perteneciente a la característica Ck marcar los pixeles : celda(i,j-1)
,celda(i,j+1), celda(i-1,j) , celda(i+1,j) siempre y cuando no exista un celda de la propia
característica en estas posiciones.
De forma general una característica Cv estará afectada por otra característica Ck si Ck en su área de influencia
afecta a algúna celda perteneciente a Cv
El algoritmo anterior para la imagen de ejemplo daría como resultado que el área de influencia del rio afecta a
la característica zona urbana=3. Esto se muestra en la siguiente figura:
1
1
1
1
1
111
1
1
1
3 3 3
3
33
3
Se pide:
Realice un modelo de clases y represéntelo usando UML
Implemente las clases Cobertura Geográfica y Matriz Raster en Java o C++
Realice la siguiente prueba en la clase Programa Principal
o Cree el mapa del ejemplo con las clases Cobertura y Matriz Raster (suponga una imagen de
10x10) , para ello use los códigos que se han mencionado
o Imprima por pantalla si una supuesta crecida del rio afectaría a alguna zona urbana o de
cultivo
NOTA: Este algoritmo sólo es aplicable si existe un rio en la cobertura