Está en la página 1de 91

6 Diseño orientado a objetos

En las etapas de captura de los requisitos y del análisis orientado a objetos se han
centrado en aprender a realizar la definición del proyecto sin decir cómo. En esta otra
etapa se pondrá el énfasis en implantar las especificaciones con eficiencia y fiabilidad.

En el Proceso Unificado, UP, por cada iteración, tendrá lugar una transacción
desde un enfoque centrado en los requisitos, a un enfoque centrado en el diseño y en la
implementación.
Iteración i
Iteración i+1

Requisitos
Requisitos
Diseño tiempo
Diseño
Implementación
Prueba Implementación
Prueba
Integración
Pruebas de sistema Integración
Pruebas de sistema

4 semana (por ejemplo) El sistema crece en


cada iteración

Figura 6. 1 evolución del proyecto por iteraciones

Dpto. Electrónica, Automática e Informática Industrial 143


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

El diseño orientado a objetos requiere tener conocimientos en:

 Los principios de asignación de responsabilidades.

 Y en los patrones de diseño.

Para el desarrollo de las técnicas de diseño se emplearán los diagramas de


interacción y los diagramas de clase de diseño, DCD. Ambos artefactos pertenecen a la
disciplina UP de Modelado del Diseño.

Este capítulo se organiza en tres apartados. El primero tratará sobre las bases del
diseño y de la implementación, para luego pasar a entrar de lleno en el diseño con
patrones. Los apartados segundo y tercero se estudiarán los patrones GRASP y GoF
respectivamente.

6.1 De los diagramas de clase de diseño a la implementación

Los diagramas de clase de diseño, DCD, se crean en paralelo con los diagramas
de interacción. En los DCD se encuentran reflejados:

 Las clases, asociaciones y atributos.

 Los patrones.

 Los interfaces, con sus operaciones y constantes.

 Los métodos o servicios.

 La información acerca del tipo de atributos.

 La navegabilidad.

 Las dependencias.

A diferencia de las clases conceptuales1 del AOO, las clases de diseño de los
DCD muestran las esencias de las futuras clases implementadas o de software.

El primer paso para crear DCD es identificar aquellas clases que participan en la
solución del paquete a diseñar. Se pueden encontrarlas examinando el modelo del
dominio, donde algunas clases conceptuales pueden ser coactadas a clases de diseño.
También pueden ser localizadas en los diagramas de interacción y listando las clases
que se mencionan.

1
Clases Conceptuales ≡ abstracciones de conceptos del mundo real

144 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

El siguiente paso es dibujar un diagrama de clases e incluir los atributos que se


identificaron previamente en el modelo del dominio que también se utilizan en el
diseño.

En cuanto a los servicios, éstos se pueden identificar analizando los diagramas


de interacción y observando los nombres de los mensajes mandados entre los objetos.

El mensaje “create()” es una forma de independizar UML de los lenguajes de


programación. En C++, implica la asignación dinámica con el operador new, seguido de
una llamada al constructor.

Los métodos de acceso a la información capaces de recuperar (getX()) o de


establecer (setX()) los valores de atributo, son definidos automáticamente o
manualmente. En algunos lenguajes, como Java, es un estilo común tener un get() y un
set() por cada atributo y declarar todos los atributos privados.

Un mensaje a un multiobjeto se interpreta como un mensaje al propio objeto


contenedor/colección. Normalmente, estas interfaces o clases de
contenedores/colecciones son elementos de las librerías predefinidas y no es útil mostrar
explícitamente estas clases en el DCD.

Los tipos de los atributos, argumentos de los servicios y los valores de retorno se
podrían mostrar. La cuestión sobre si se muestra o no esta información se debe
considerar en el siguiente contexto:

“Si se emplea alguna herramienta CASE con generación automática del código,
son necesarios todos los detalles. Si se hace para que lo lean los
desarrolladores, los detalles podrían influir negativamente por el ruido visual
que produce tanta información en los DCD.”

En el Modelo del Dominio sólo se expresaba relaciones semánticas entre las


clases conceptuales, intentando definir un diccionario visual del problema. Por el
contrario, en DCD se eligen las asociaciones de acuerdo al criterio de necesito conocer.
Es en el DCD donde tiene sentido emplear toda la rica notación de UML sobre
relaciones entre clases de diseño.

La visibilidad y las asociaciones entre clases de diseño se dan a conocer en el


DCD. Para su determinación se ayudara con los diagramas de interacción.

Ejemplo 6.1

Determinar el DCD del paquete del dominio de RespuestaFrecuencia


considerando el modelo del dominio y los diagramas de interacción, obtenidos
de anteriores ejercicios.

Dpto. Electrónica, Automática e Informática Industrial 145


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

RespuestaFrecuencia
modulos : std::vector<double>
argumentos : std::vector<double> FDT
frInicio : float grado : int
frFinal : float esta formado por
frIntervalo : float
esta definido por1

2 Polinomio
necesita conocer coeficientes : std::vector<double>
FiltroLineal
tipoFiltro : int

Patrón
Fachada

: Ingeniero Vista : CoordinadorRespFr


electrónico Patrón
creador
introducirCircuito()
ponerCircuito()

create()
: FiltroLineal

create()
: FDT

introducirParametrosRespFr()

ponerParamResFr()

create()
: RespuestaFrecuencia

getModuloRespFr( )

getModuloRespFr()

visualizarBode()

Del diagrama de interacciones se observa que el coordinador del paquete del


dominio recibe dos mensajes (ponerCircuito() y ponerParamResFr()). Del primero se
observa que se creará el objeto filtro con sus características que según el modelo del
dominio será la FDT de un sistema tipo SISO, constituyéndolo con dos polinomios. Por
tanto, son asociaciones de necesito conocer y relaciones del todo con las partes. Se les
pondrá flechas de navegación y de tipo de agregación y composición. No se ha elegido,
para este caso, relaciones de generalización, por que es más robusto la composición que
la herencia. Nótese que en las clases conceptuales no se ha colocado ninguna relación
jerárquica.

Cuando se recibe la información sobre los parámetros de respuesta en


frecuencia, el coordinador se lo pasará a RespuestaFrecuencia y éste deberá de calcular
el Bode. La conexión entre RespuestaFrecuencia y FiltroLineal, necesaria en el cálculo

146 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

de Bode, se realizará por argumento en este servicio, generando una relación de


dependencia.

CoordinadorRespFr
RespuestaFrecuencia
ponerCircuito() frecuenciaInicio : float
ponerParamResFr() frecuenciaFin : float
getModuloRespFr() intervaloFrec : float
modulo : vector<double>
argumento : vector<double>
Patrón Experto
getModuloRespFr() (GRASP)

FiltroLineal FDT 1 2 Polinomio


tipoFiltro : int grado : unsigned coeficientes : vector<double>

6.1.1 Determinación de la visibilidad

La visibilidad es la capacidad de un objeto de tener una referencia a otro objeto.


Para que un objeto emisor envíe un mensaje a un objeto receptor2, el receptor debe ser
visible al emisor. Hay cuatro formas comunes de alcanzar la visibilidad:

1. Visibilidad de atributo: El emisor tiene entre sus atributos al receptor. Es una


visibilidad permanente porque persiste mientras existan el emisor y el
receptor. Ésta es una visibilidad muy común en AOO/D. Se usa el
estereotipo <<association>> para definir esta visibilidad en UML.

2. Visibilidad de argumento: El objeto-atributo es pasado como argumento en


un servicio solicitado a otro objeto. La visibilidad de parámetro desde el
emisor al receptor existe cuando el emisor pasa como un parámetro el
atributo al receptor. Es una visibilidad relativamente temporal. Es la segunda
forma más común en AOO/D. Es habitual transformar la visibilidad de
argumento en visibilidad local. El estereotipo UML para su identificación es
<<parameter>>.

3. Visibilidad local: El receptor es declarado dentro de algún servicio del


emisor. La visibilidad local, desde el emisor al receptor, existe cuando el
receptor se declara como un objeto local en un método del emisor. Es una
visibilidad relativamente temporal. Es la tercera forma de visibilidad más
común en POO. Se declara con el estereotipo <<local>>.

4. Visibilidad global: Cuando el receptor es un objeto global y puede ser


manejado por cualquier objeto de la aplicación. Es una visibilidad
permanente en el tiempo y es la forma menos común de visibilidad. El

2
Recuerde que el objeto receptor es el que realiza la operación.

Dpto. Electrónica, Automática e Informática Industrial 147


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

método para conseguir visibilidad global es utilizar el patrón Singleton


[GoF]. Se declara con el estereotipo <<global>>.

La navegabilidad implica visibilidad, normalmente, visibilidad del atributo. Las


asociaciones en DCD deberían adornarse con las flechas de navegación necesarias. Es
aquí donde tiene más sentido destacar las distintas relaciones que se establecen en
UML. Así, por ejemplo, en los diagramas de clase de diseño, la relación de dependencia
es útil para describir la visibilidad entre clases que no son de atributos, esto es, para
declarar una visibilidad de parámetro, local o global.

6.1.2 Modelo de implementación

Una vez finalizado los DCD se dispone de los suficientes detalles para generar el
código de la capa del dominio de los objetos.

Los artefactos UP creados durante el trabajo de diseño -diagramas de interacción


y los DCDs- se utilizarán como entradas en el proceso de generación de código.

En UP se define el Modelo de Implementación. Éste contiene los artefactos de


implementación como el código fuente, las definiciones de bases de datos, las páginas
XML/HTML, etc.

Una ventaja del AOO/D y la POO, cuando se utiliza UP, es que proporciona una
guía de principio a fin, esto es, se presenta un conjunto de artefactos, procedimientos y
técnicas que van desde los requisitos hasta la generación del código.

Durante el trabajo de diseño se tomaron algunas decisiones. Ahora, en la fase de


implementación, no es una etapa de generación de código trivial, más bien lo contrario.
En realidad, los resultados generados durante el diseño son un primer paso incompleto.
Durante la fase de producción del código aparecerán nuevas cuestiones que habrán de
resolverse in situ. Es una tarea que costará mucho tiempo, pero mucho menos que si no
se hubiera puesto esfuerzo en la captura de los requisitos, en el análisis y en el diseño.
Producir código sin realizar estos estudios es algo que es improductivo, tedioso y
extremadamente peligroso.

Después de haber acabado una iteración de UP, es deseable, para la siguiente


vuelta del ciclo, que los diagramas generados se actualicen de manera semiautomática
con el trabajo surgido de la implementación. Éste es un aspecto de la ingeniería inversa
(ver ejemplo 6.2). El código producido hará actualizar el modelo definido en el Proceso
Unificado.

6.1.3 Transformación del diseño al código

La transformación en un lenguaje OO requiere la escritura del código fuente


para:
 La definición de las clases e interfaces.
 Las definiciones de los métodos o servicios.

148 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

Para la creación de las definiciones de las clases se emplearán básicamente los


DCD. Las definiciones de los métodos y de los atributos simples son inmediatas de
obtener a partir de los DCD y de los diagramas de interacción. Sin embargo, para los
atributos complejos se emplearán atributos de referencia. Los atributos de referencia se
deducen de las asociaciones y de la navegabilidad de los diagramas de clases. Los
atributos de referencia de una clase a menudo están implícitos, en lugar de explícitos.
Cuando el atributo es simple, por ejemplo, un carácter, éste está de forma explícita en la
definición de la clase; pero cuando es complejo, como por ejemplo una frase, se utiliza
una referencia a una instancia de ese atributo complejo.

En este curso se ha ignorado el manejo de las excepciones en el desarrollo de la


solución. De hecho no se plantea la inserción de código para el control de excepciones.
Sin embargo, habrá que contemplarlas en la producción de código industrial.

El método para traducir los diagramas de clases de diseño y los diagramas


interacción a código se basará en el procedimiento “eXtreme Programming”, XP. Se
basa en escribir el código de pruebas antes que el código de producción. La secuencia es
escribir un poco de código de prueba, luego escribir un poco de código de producción,
hacer las pruebas y cuando éste se hayan superado, entonces se escribirá más código de
prueba y más de producción y así sucesivamente. Se empezará por implementar desde
las clases menos acopladas a las más acopladas.

Ejemplo 6.2

Implementar la aplicación Respuesta en Frecuencia v0.0.0

Primero se realizará el código de prueba, utilizando el diagrama de secuencia del


sistema (DSS), los contratos de operación y los diagramas de interacción empleados
anteriormente. Por tanto, se implementará la función main() y la clase Vista:

// De: "Apuntes de Informática Industrial" Carlos Platero.


// (R) 2004 Con licencia GPL.
// Ver permisos en licencia de GPL

#include <iostream>
using namespace std;

#include "../Dominio/CoordinadorFrecELAI.h"

class VistaFrecuenciaELAI
{
tipoFiltro elTipo;
float resistencia;
float condensador;
float frecInicial, frecFinal, frecIntervalo;
CoordinadorFrecELAI elCoordinador;

public:
void introducirCircuito(void);
void introducirParametrosRespFr(void);
}; /*VistaFrecuencia.h*/

Dpto. Electrónica, Automática e Informática Industrial 149


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

// De: "Apuntes de Informática Industrial" Carlos Platero.


// Ver permisos en licencia de GPL
#include "../../include/Vista/VistaFrecuenciaELAI.h"
void VistaFrecuenciaELAI::introducirCircuito(void)
{
cout << "Elegir entre:\n1.Filtro paso bajo primer orden.\n2.Filtro paso alto";
int eleccion;
cin >> eleccion;
elTipo = eleccion == 1 ? LF_1 : HF_1;
cout << "\nValor de la resistencia: ";
cin >> resistencia;
cout << "\nValor del condensador: ";
cin >> condensador;
elCoordinador.ponerCircuito(elTipo,resistencia,condensador);
}
void VistaFrecuenciaELAI::introducirParametrosRespFr(void)
{
cout << "\nCual es la frecuencia inicial [Hz]: ";
cin >> frecInicial;
cout << "\nCual es la frecuencia final [Hz]: ";
cin >> frecFinal;
cout << "\nCual es el intervalo empleado para el cálculo [Hz]: ";
cin >> frecIntervalo;
elCoordinador.ponerParamResFr(frecInicial,frecFinal,frecIntervalo);

//Visualizar los resultados


std::vector<double> elVectorModulo;
std::vector<double>::iterator iteradorModulo;
elCoordinador.getModuloRespFr(elVectorModulo);
iteradorModulo = elVectorModulo.begin();
for (unsigned i =0; i<elVectorModulo.size(); i++)
std::cout<<*(iteradorModulo+i)<<std::endl;
cout << "Pulsar cualquier tecla para finalizar";
}
void main(void)
{
VistaFrecuenciaELAI laVista;
laVista.introducirCircuito();
laVista.introducirParametrosRespFr();
}

El siguiente paso será escribir el código de las clases menos acopladas a las que
más lo están. Se implementarán por el siguiente orden: Polinomio, FDT, FiltroLTI,
RespuestaFrecuencia y Coordinador:

// De: "Apuntes de Informática Industrial" Carlos Platero.


// Ver permisos en licencia de GPL
#ifndef _POLINOMIO_INC_
#define _POLINOMIO_INC_
#include <vector>

class Polinomio
{
std::vector<double> coeficientes;
public:
Polinomio(){}
Polinomio(unsigned grado, double *pCoef)
{
for (unsigned i=0;
i<=grado;coeficientes.push_back(*(pCoef+i)),i++);
}
double getCoeficiente(unsigned n)
{return( coeficientes[n]));}
};

#endif /*Polinomio.h*/

150 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

// De: "Apuntes de Informática Industrial" Carlos Platero.


// Ver permisos en licencia de GPL
#ifndef _FDT_INC_
#define _FDT_INC_
#include "Polinomio.h"
class FDT
{
unsigned grado;
Polinomio numerador;
Polinomio denominador;
public:
FDT(unsigned n, double *pNum, double *pDen):
grado(n),numerador(n,pNum),denominador(n,pDen){}
unsigned getGrado(void){return grado;}
double getCoefNum(unsigned n)
{return n<=grado ? numerador.getCoeficiente(n) : 0;}
double getCoefDen(unsigned n)
{return n<=grado ? denominador.getCoeficiente(n) : 0;}
};

#endif /*FDT.h*/

// De: "Apuntes de Informática Industrial" Carlos Platero.


// Ver permisos en licencia de GPL
#ifndef _FILTRO_LINEAL_INC_
#define _FILTRO_LINEAL_INC_
#include "FDT.h"
typedef enum{LF_1,HF_1} tipoFiltro;

class FiltroLineal
{
tipoFiltro elTipo;
FDT *pFDT;
public:
FiltroLineal(tipoFiltro, float, float);
unsigned getGradoFiltro(void){return pFDT->getGrado();}
double getCoefNum(unsigned n)
{return pFDT != NULL ? pFDT->getCoefNum(n) : 0;}
double getCoefDen(unsigned n)
{return pFDT != NULL ? pFDT->getCoefDen(n) : 0;}
~FiltroLineal(){if(pFDT) delete pFDT;}
};

#endif /*FiltroLineal.h*/

// De: "Apuntes de Informática Industrial" Carlos Platero.


// Ver permisos en licencia de GPL
#ifndef _RESPFREC_INC_
#define _RESPFREC_INC_
#include <vector>
#include "FiltroLineal.h"
class RespuestaFrecuencia
{
float freInicio, freFinal, freIntervalo;
std::vector<double> modulo;
std::vector<double> argumento;
double calcularModulo(float,FiltroLineal *);
double calcularArgumento(float,FiltroLineal *);
public:
RespuestaFrecuencia(float,float,float,FiltroLineal *);
float getFrInicio(void){return freInicio;}
float getFrFinal(void){return freFinal;}
float getFrIntervalo(void){return freIntervalo;}
void getModuloRespFr(std::vector<double> &elVectorModulo)
{elVectorModulo = modulo;}
};

#endif /*RespuestaFrecuencia.h*/

Dpto. Electrónica, Automática e Informática Industrial 151


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

// De: "Apuntes de Informática Industrial" Carlos Platero.


// Ver permisos en licencia de GPL
#ifndef _COORDINFRECELAI_INC_
#define _COORDINFRECELAI_INC_
#include "FiltroLineal.h"
#include "RespuestaFrecuencia.h"

class CoordinadorFrecELAI
{
FiltroLineal *pFiltro;
RespuestaFrecuencia *pRespFr;

public:
int ponerCircuito(tipoFiltro ,float , float );
int ponerParamResFr(float,float,float);
int getModuloRespFr(std::vector<double> &);
~CoordinadorFrecELAI()
{if(pFiltro) delete pFiltro; if(pRespFr) delete pRespFr;}

};

Las algoritmias de los métodos serán implementados en los fuentes de las clases.
También de la menos acopladas a la de más acoplamiento. Se usa un código de test para
visualizarlo en consola.

// (R) 2006 Con licencia GPL.


// Ver permisos en licencia de GPL
#include "../../include/Dominio/FiltroLineal.h"
FiltroLineal::FiltroLineal(tipoFiltro tipo, float resistencia, float condensador)
{
elTipo = tipo;
double numerador[2]; double denominador[2];
if (elTipo == LF_1) {
numerador[0]=1; numerador[1]=0;
}
else{
numerador[0]=0; numerador[1]=resistencia*condensador;
}
denominador[0]= 1;denominador[1]=resistencia*condensador;
pFDT = new FDT(1,numerador,denominador);
}

// De: "Apuntes de Informática Industrial" Carlos Platero.


// Ver permisos en licencia de GPL
#include "../../include/Dominio/RespuestaFrecuencia.h"
#include <iostream>
#include <math.h>
#define PI 3.1416
#define PRUEBA_BODE
#ifdef PRUEBA_BODE
#include <iostream>
#endif
RespuestaFrecuencia::RespuestaFrecuencia(float frInicio, float frFin,float
frIntervalo,FiltroLineal *pFiltro)
{
for (float f=frInicio; f< frFin; f+=frIntervalo)
{
modulo.push_back(this->calcularModulo(f,pFiltro));
//argumento.push_back(this->calcularArgumento(f,pFiltro));
//A implementar
}
#ifdef PRUEBA_BODE
this->iteradorModulo = modulo.begin();
for (unsigned i =0; i<modulo.size(); i++)
std::cout<<*(iteradorModulo+i)<<std::endl;
#endif
}

double RespuestaFrecuencia::calcularModulo(float frecuencia,FiltroLineal *pFiltro)


{
//...
}

152 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

// De: "Apuntes de Informática Industrial" Carlos Platero.


// Ver permisos en licencia de GPL
#include "../../include/Dominio/CoordinadorFrecELAI.h"
int CoordinadorFrecELAI::ponerCircuito(tipoFiltro elTipo,float resistencia,
float condensador)
{
pFiltro = new FiltroLineal(elTipo,resistencia,condensador);
return(0);
}
int CoordinadorFrecELAI::ponerParamResFr(float frInicio,float frFinal,
float frIntervalo)
{
if (pFiltro == NULL) return (-1);
pRespFr = new RespuestaFrecuencia(frInicio,frFinal,frIntervalo,pFiltro);
return(0);
}
int CoordinadorFrecELAI::getModuloRespFr(std::vector<double> &elVectorModulo)
{
if (pRespFr == NULL) return (-1);
pRespFr->getModuloRespFr(elVectorModulo);
if(pFiltro) delete pFiltro;
if(pRespFr) delete pRespFr;
return (0);
}

Una vez depurada la aplicación se procederá a aplicar ingeniería inversa para


obtener el nuevo DCD y pasar a la siguiente iteración UP:

RespuestaFrecuencia

calcularModulo() vector<double>
VistaFrecuenciaELAI
calcularArgumento()
RespuestaFrecuencia() 1
introducirCircuito() getFrInicio()
introducirParametrosRespFr() getFrFinal()
getFrIntervalo() <<typedef>> 1
getModulo() tipoFiltro Polinomio
1
getModuloRespFr()
Polinomio()
Polinomio()
CoordinadorFrecELAI 1 getCoeficiente()
FDT 2
CoordinadorFrecELAI() FiltroLineal
ponerCircuito() 1
ponerParamResFr() FDT()
FiltroLineal()
getModuloRespFr() 1 FDT()
1 getGradoFiltro()
~CoordinadorFrecELAI() 1 1 getGrado()
getCoefNum()
getCoefNum()
getCoefDen()
getCoefDen()

Dpto. Electrónica, Automática e Informática Industrial 153


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

6.2 Diseño de objetos con responsabilidad

Para el diseño OO se requiere tener conocimientos en:

• Los principios de asignación de responsabilidades

• Patrones de diseño

Para su desarrollo se emplearán los diagramas de interacción y los diagramas de


clase, ambos artefactos forman parte del Modelo del Diseño.

Las responsabilidades están relacionadas con las obligaciones de un objeto en su


comportamiento. Estas obligaciones son de dos tipos: a) los objetos deben saber qué
información manejan (conocer) y b) las cosas qué deben de hacer.

Las responsabilidades se asignan a las clases de los objetos durante la etapa de


diseño. Una responsabilidad no es lo mismo que un método o servicio, pero los métodos
se implementan para llevar a cabo las responsabilidades. El objetivo de este capítulo es
ayudar a aplicar sistemáticamente los principios fundamentales para asignar
responsabilidades a los objetos.

6.2.1 Patrones

En tecnología de objetos, un patrón es una descripción de un problema y su


solución; a la que se le da un nombre y se puede aplicar a nuevos contextos. Son guías
sobre el modo en el que debería asignarse las responsabilidades a los objetos.

Resumiendo:

1. La asignación habilidosa de responsabilidad es extremadamente


importante en el diseño de objetos.

2. La decisión acerca de la asignación de responsabilidades tiene lugar


durante la creación de los diagramas de interacción, de los DCD y,
posteriormente, en la programación.

3. Los patrones son pares problemas/solución con un nombre que


codifican buenos consejos y principios relacionados, con frecuencia,
con la asignación de responsabilidades.

Dos tipos de patrones se explicarán: los patrones GRASP y los GoF. GRASP es
el acrónimo de General Responsibility Assignment Software Patterns. Se tratarán los
patrones: Experto en Información, Creador, Alta Cohesión, Bajo Acoplamiento,
Controlador, Polimorfismo, Indirección, Fabricación Pura y Variaciones Protegidas.

154 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

Mientras GoF es la abreviatura de Gangs of Four, de los que se tratarán los patrones:
Adaptador, Factoría, Singleton, Estrategia, Composición y Observador.

6.3 Patrones GRASP

6.3.1 Experto en Información

Problema: ¿Cuál es el principio general para asignar responsabilidades?

Solución: Asignar la responsabilidad al que tenga la información.

El patrón Experto indica qué hacen los objetos con la información que
contienen. Sucede muchas veces que la información está dispersa por diferentes clases
de objetos. Esto implica que hay muchos expertos con información “parcial” que
colaboran en la tarea, mediante el paso de mensajes para compartir el trabajo. Por
ejemplo, en el problema 3 del capítulo anterior, cuando había que mandar a dibujar la
urbanización, esta tarea era dividida por cada casa y cada casa por su tejado y bloque.

Para asignar la responsabilidad se emplearán las clases del DCD. En una primera
iteración, se utilizará el Modelo del Dominio, en versiones posteriores se consultaran
los DCD generados en las iteraciones anteriores. La idea es ir ampliando o actualizando
las nuevas clases del diseño.

Si se empieza el trabajo del diseño y no hay nada en el modelo del diseño se


buscará los expertos en información en el Modelo del Dominio. Hay que hacer una tabla
de responsabilidades generando las primeras clases de diseño.

Ejemplo 6.3

Realizar una tabla de responsabilidad sobre la aplicación


RespuestaFrecuencia
Clase de diseño Información Responsabilidad
Filtro Tiene la FDT del filtro y el tipo de Definir matemáticamente la estructura
filtro del filtro
RespuestaFrecuencia Los parámetros de frecuencia Aplicar los algoritmos para calcular
el Bode

RespuestaFrecuencia
frecuenciaInicio : float
frecuenciaFin : float
intervaloFrec : float
modulo : vector<double>
argumento : vector<double>
Patrón Experto
calcularBode() (GRASP)

FiltroLineal FDT 1 2 Polinom io


tipoFiltro : int grado : unsigned coeficientes : vector<double>

Dpto. Electrónica, Automática e Informática Industrial 155


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

En algunos casos, el Experto en Información genera problemas de acoplamiento


y cohesión. Este problema se pone de manifiesto cuando el patrón Experto une los datos
del dominio, con los datos de la presentación. Por ejemplo, en una hoja de cálculo, el
Experto uniría los datos de la base de datos con la presentación en barras gráficas. El
patrón Experto se equivoca. Siempre hay que mantener separado la lógica de la
aplicación con la lógica de la base de datos o la lógica del dominio con el de la vista.

Beneficios del patrón experto:

• Mantiene el encapsulamiento de la información, puesto que los objetos


utilizan su propia información para llevar a cabo las tareas.

• Se distribuye el trabajo entre clases, haciéndolas más cohesivas y ligeras,


lo que conlleva a que sean más fáciles de entender y de mantener.

Este patrón también se conoce como: “Colocar la responsabilidad con los datos”,
“Eso que conoces, hazlo”, “Hacerlo yo mismo”, “Colocar los servicios con los atributos
con los que trabaja”.

6.3.2 Creador

Problema: ¿Quién debería ser el responsable de la creación de una nueva


instancia de una clase?

Solución: Asignar a la clase B la responsabilidad de crear una instancia de clase


A si se cumple uno o más de los siguientes casos:

• B contiene objetos de A

• B se asocia con objetos de A

• B registra instancias de objetos de A

• B utiliza más estrechamente objetos de A

• B tiene datos de inicialización que se pasarán a un objeto de A

El patrón creador está relacionado con la asociación y especialmente con la


agregación y la composición (relación del Todo con las Partes).

Estas responsabilidades se asignarán durante la elaboración de los diagramas de


interacción. A veces se encuentra el creador buscando las clases que tienen los datos de
inicialización que se pasará durante la creación.

156 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

A menudo, la creación requiere una complejidad significativa, como utilizar


instancias recicladas por motivos de rendimientos o crear instancias de forma
condicional. En estos casos, es aconsejable delegar la creación a una clase auxiliar
denominada Factoría. Esta clase deriva del patrón Factoría (GoF) que se verá más
adelante.

Ejemplo 6.4

Utilizar el patrón creador en la aplicación RespuestaFrecuencia

: CoordinadorRespFr
Patrón
creador

ponerCircuito()

create()
: FiltroLineal

create()
: FDT

ponerParamResFr()

create()
: RespuestaFrecuencia

// De: "Apuntes de Informática Industrial" Carlos Platero.


// Ver permisos en licencia de GPL
#include "../../include/Coordinador/CoordinadorFrecELAI.h"

int CoordinadorFrecELAI::ponerCircuito(tipoFiltro elTipo,float resistencia,


float condensador)
{
pFiltro = new FiltroLineal(elTipo,resistencia,condensador);
return(0);
}
int CoordinadorFrecELAI::ponerParamResFr(float frInicio,float frFinal,
float frIntervalo)
{
if (pFiltro == NULL) return (-1);
pRespFr = new RespuestaFrecuencia(frInicio,frFinal,frIntervalo,pFiltro);
return(0);
}
int CoordinadorFrecELAI::getModuloRespFr(std::vector<double> &elVectorModulo)
{
if (pRespFr == NULL) return (-1);
pRespFr->getModuloRespFr(elVectorModulo);
if(pFiltro) delete pFiltro;
if(pRespFr) delete pRespFr;
return (0);
}

Dpto. Electrónica, Automática e Informática Industrial 157


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

6.3.3 Alta Cohesión

Problema: ¿Cómo mantener la complejidad manejable?

Solución: Asignar una responsabilidad de manera que la cohesión permanezca


alta.

En AOO/D, la cohesión es una medida de la fuerza con la que se relacionan los


elementos de un conjunto o paquete y del grado de focalización de sus
responsabilidades. Un elemento, concepción genérica de UML, de alta responsabilidad
y que no hace gran cantidad de trabajo, tiene alta cohesión. Estos elementos pueden ser
clases, paquetes, subsistemas, etc.

Una clase con baja cohesión hace muchas cosas no relacionadas o tareas
relacionadas pero con mucho trabajo. Las clases de baja cohesión adolecen de los
siguientes problemas:

 Difíciles de entender.

 Difíciles de reutilizar.

 Difíciles de mantener.

 Delicadas, constantemente afectadas por los cambios.

A menudo las clases con baja cohesión representan bien un grado grande de
abstracción o bien se les han asignado demasiadas responsabilidades que deberían
haberse delegado en otras clases.

Se establece que existe alta cohesión funcional cuando los elementos de un


componente “trabajan todos juntos para proporcionar algún comportamiento bien
delimitado”.

Como regla empírica, una clase con alta cohesión tiene un número relativamente
pequeño de métodos, con funcionalidad altamente relacionada y no realiza mucho
trabajo. En el caso de que la tarea sea extensa, colaborará con otros objetos para
compartir el esfuerzo.

El Bajo Acoplamiento y la Alta Cohesión son viejos principios del diseño SW.
Otros de estos principios es promover el diseño modular. La modularilidad es la
propiedad del sistema de haberse descompuesto en un conjunto de módulos cohesivos y
débilmente acoplados. En UML se emplea la vista de gestión del proyecto para la
aplicación de la modularidad. Con un doble motivo: a) organización de las tareas entre
los desarrolladores que van a participar en el proyecto y b) diseño de componentes
altamente cohesivas y con bajo acoplamiento.

Ejemplo 6.5

158 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

Visión arquitectónica o de gestión de la aplicación de Respuesta en


Frecuencia.

Los paquetes se deben de diseñar de forma altamente cohesiva y con bajo


acoplamiento. Esta tarea también servirá para la organización del trabajo entre los
desarrolladores de la aplicación.

MFC (.NET)
ActiveX-Bode VistaFrecuencia
ELAI

DominioFrecunc
iaEla STL-ANSI C++

En la práctica, el nivel de cohesión no se puede considerar de manera aislada a


otras responsabilidades y a otros principios como son los patrones Experto y Bajo
Acoplamiento.

Beneficios:

 Se incrementa la claridad y facilita la comprensión del diseño

 Se simplifica el mantenimiento y las mejoras

 Se soporta a menudo bajo acoplamiento

 El grano fino de funcionalidad altamente relacionada incrementa la


reutilización. Un paquete o clase altamente cohesiva puede ser aplicado en
otro contexto.

Ejemplo 6.6

Un simulador de sistemas LTI-SISO requiere para su definición la FDT del


sistema. Por tanto, se puede emplear las clases de FDT y Polinomio que se han definido
en la Respuesta en Frecuencia para esta otra aplicación. Una definición de clases
altamente cohesivas muestra su facilidad de reutilización.

Dpto. Electrónica, Automática e Informática Industrial 159


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

Polinomio
FDT 1 2
(f rom Analy sis Model)
CoordinadorSimulador coeficientes
<<create>> setCoeficientesFDT()
introducirModelo()
especificarExcitacion()
mostrarSalida()
notificarResultados() Simulador_LTI
(f rom Analy sis Model)
tiempoFinal
intervaloTiempo
valoresSalida

SenyalExcitacion calcularSalida()
tipoSenyal <<create>> setSimulador(laFDT : FDT, laSenyalExc : SenyalEscalon)

<<create>> setSenyalExcitacion()

6.3.4 Bajo Acoplamiento

Pregunta: ¿Cómo soportar el bajo impacto del cambio e incrementa la


reutilización?

Solución: Asignar una responsabilidad de manera que el acoplamiento


permanezca bajo.

El acoplamiento es una medida de la fuerza con que un elemento está conectado


a, o tiene conocimiento de, o confía en otros elementos.

Una clase con alto acoplamiento confía en muchas otras clases. Tales clases
podrían no ser deseables; adolecen de los siguientes problemas:

 Son difíciles de mantener de manera aislada.

 Los cambios en estas clases fuerzan cambios locales.

 Son difíciles de reutilizar.

En general, las clases que son muy genéricas y con una alta probabilidad de
reutilización alta, deberían de tener un acoplamiento especialmente bajo. Por ejemplo,
en el anterior ejercicio, se han colocado las clases Polinomio y FDT para una aplicación
de Simulación que habían sido definidas en Respuesta en Frecuencia. Ambas se
caracterizan por un Bajo Acomplamiento.

No suele ser problema el acoplamiento alto entre objetos estables y elementos de


generalización. Se entiende como objetos estables aquellos que provienen de las
librerías estándar, tales como las STL o el uso de frameworks como las MFC o las Qt.

6.3.5 Controlador

Problema: ¿Quién debe ser el responsable de gestionar un evento de entrada al


sistema?

160 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

Solución: Asignar la responsabilidad a una clase que represente una de las


siguientes opciones:

 Representa el sistema global, dispositivo o subsistema. Se le llamará


Controlador de Fachada.

 Representa un escenario de caso de uso. A menudo se denominan


Coordinador o Manejador o Sesión acompañado con el nombre del caso de
uso. Utilice la misma clase controlador para todos los eventos del sistema en
el mismo escenario de caso de uso.

Un controlador es un objeto que no pertenece al interfaz o vista, responsable de


recibir o manejar los eventos del sistema. Un controlador define el método para la
operación del sistema. Sus servicios pueden ser establecidos a partir del Diagrama de
Secuencia del Sistema, DSS, o de los contratos de operación.

No sólo el interfaz genera eventos, también puede hacerlo el tiempo, si es una


aplicación en tiempo real. Otro caso son las aplicaciones de control de procesos; los
sensores y/o dispositivos generan interrupciones que se deben de atender. El controlador
es una especie de fachada del paquete que recibe los eventos externos y organiza las
tareas.

Un error típico del diseño de los controladores es otorgarles demasiadas


responsabilidades. Normalmente, un controlador debería delegar en otros objetos el
trabajo que se necesita hacer, coordinar o controlar la actividad. No realiza mucho
trabajo por sí mismo.

La primera categoría de controlador es el controlador de fachada que representa


al sistema global, dispositivo o subsistema. Los controladores de fachada son adecuados
cuando no existen “demasiados” eventos del sistema. Si se elige un controlador de casos
de uso, entonces hay un controlador diferente para cada caso de uso.

Antiguamente se empleaban los conceptos de objetos frontera, objetos entidad y


objetos control. Precisamente los objetos control eran los manejadores de los casos de
uso y que se describen en este patrón. Las otras dos categorías pertenecían a los que se
relacionaban con la vista (frontera) y a los objetos del dominio (entidad).

Resumiendo, el controlador recibe la solicitud del servicio desde una capa


superior y coordina su realización, normalmente delegando a otros objetos, aumentando
el potencial para reutilizar. Asegura que la lógica de la aplicación no se maneja en la
capa interfaz. También el controlador o coordinador sirve para analizar la “máquina de
estado” del sistema o de caso del uso, según sea el tipo.

Un corolario importante del patrón Controlador es que los objetos interfaz y la


capa interfaz, no deberían ser responsables de manejar los eventos del sistema.
Incrementa el potencial de reutilización.

Dpto. Electrónica, Automática e Informática Industrial 161


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

El patrón Controlador crea un objeto artificial que no procede del análisis del
dominio. Se dice que es una Fabricación Pura, detalle que se analizará más adelante. La
implementación del Controlador hace uso de los patrones GRASP de Fabricación Pura
y de Indirección.

Signos de un controlador saturado:

 Existe una única clase controlador que recibe todos los eventos del sistema.

 El propio controlador realiza muchas de las tareas necesarias para llevar a


cabo los eventos del sistema, sin delegar trabajo. Lleva a la violación de los
patrones del Experto y Alta cohesión.

 Un controlador tiene muchos atributos y mantiene información significativa


sobre el sistema o el dominio.

Remedios:

1. Añadir más controladores.

2. Delegar las tareas a otros objetos.

3. Este patrón está relacionado con el patrón Comand [GoF],


Fachada [GoF], Capas [POSA] y Fabricación Pura [GRASP].

Ejemplo 6.7

Emplear un Coordinador para la aplicación de Respuesta en Frecuencia


que organice las tareas del escenario de caso de uso y que delegue las tareas.

162 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

: VistaFrecuenciaELAI
: FiltroLineal

1: ponerCircuito(tipoFiltro, float, float)


3: ponerParamResFr(float, float, float)

create()
2: FiltroLineal(tipoFiltro, float, float)

: CoordinadorFrecELAI 4: RespuestaFrecuencia(float, float, float, FiltroLineal*)

create()

: RespuestaFrecuencia

RespuestaFrecuencia

calcularModulo() vector<double>
VistaFrecuenciaELAI
calcularArgumento()
RespuestaFrecuencia() 1
introducirCircuito() getFrInicio()
introducirParametrosRespFr() getFrFinal()
getFrIntervalo() <<typedef>> 1
getModulo() tipoFiltro Polinomio
1
getModuloRespFr()
Polinomio()
Polinomio()
CoordinadorFrecELAI 1 getCoeficiente()
FDT 2
CoordinadorFrecELAI() FiltroLineal
ponerCircuito() 1
ponerParamResFr() FDT()
FiltroLineal()
getModuloRespFr() 1 FDT()
1 getGradoFiltro()
~CoordinadorFrecELAI() 1 1 getGrado()
getCoefNum()
getCoefNum()
getCoefDen()
getCoefDen()

// De: "Apuntes de Informática Industrial" Carlos Platero.


// Ver permisos en licencia de GPL
#include "../../include/Dominio/CoordinadorFrecELAI.h"
int CoordinadorFrecELAI::ponerCircuito(tipoFiltro elTipo,float resistencia,
float condensador)
{
pFiltro = new FiltroLineal(elTipo,resistencia,condensador);
return(0);
}
int CoordinadorFrecELAI::ponerParamResFr(float frInicio,float frFinal,
float frIntervalo)
{
if (pFiltro == NULL) return (-1);
pRespFr = new RespuestaFrecuencia(frInicio,frFinal,frIntervalo,pFiltro);
return(0);
}
int CoordinadorFrecELAI::getModuloRespFr(std::vector<double> &elVectorModulo)
{
if (pRespFr == NULL) return (-1);
pRespFr->getModuloRespFr(elVectorModulo);
if(pFiltro) delete pFiltro;
if(pRespFr) delete pRespFr;
return (0);
}

Dpto. Electrónica, Automática e Informática Industrial 163


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

6.3.6 Polimorfismo

Problema: ¿Cómo manejar las alternativas basadas en tipo?, ¿Cómo crear


componentes software conectables (pluggable)? o ¿Cómo se puede sustituir un
componente servidor por otro, sin afectar al cliente?

Solución: Cuando las alternativas o comportamientos relacionados varían según


los tipos de los datos, asignar la responsabilidad para el comportamiento utilizando
operaciones polimórficas, de forma que varía el comportamiento según el tipo. No hay
que realizar comprobaciones acerca del tipo del objeto. No se requiere emplear la lógica
condicional para llevar a cabo alternativas diferentes basadas en el tipo.

Si en un diseño se emplea las sentencias lógicas de bifurcación if-else o switch-


case, cada nueva variación requiere la modificación de esta lógica. Este enfoque
dificulta que el programa se extienda con facilidad.

El polimorfismo trata de asignar el mismo nombre de servicio pero con


diferentes objetos. El polimorfismo es un principio fundamental para designar cómo se
organiza el sistema para gestionar variaciones similares. Según el polimorfismo un
diseño basado en la asignación de responsabilidades puede extenderse fácilmente para
manejar nuevas variaciones.

Los desarrolladores diseñan sistemas con interfaces y polimorfismos para futuras


necesidades frente a posibles variaciones desconocidas.

Beneficios:

 Se añaden fácilmente las extensiones necesarias para nuevas variaciones.

 Las nuevas implementaciones se pueden introducir sin afectar a los clientes.

Ejemplo 6.8

En el ejemplo de la urbanización, ¿cómo asignaría la responsabilidad de


una casa con garaje? .

Se diseñaría una relación de generalización-especialización y se aplicaría


polimorfismo en el servicio de dibuja().

164 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

Casa

Casa()
<<virtual>> ~Casa()
setPosicion()
<<virtual>> dibuja()

CasaConGaraje

CasaConGaraje()
dibuja()

6.3.7 Indirección

Problema: ¿Donde asignar una responsabilidad, para evitar el acoplamiento


directo entre dos o más lógicas de la aplicación?, ¿Cómo desacoplar los objetos de
manera que se soporte el Bajo Acoplamiento y el potencial de reutilización permanezca
alto?.

Solución: Asignar la responsabilidad a un objeto intermedio entre dos o más


elementos o paquetes de manera que no se acoplen directamente.

Algunos patrones como Adaptador (GoF), Controlador (GRASP), Observador


(GoF) y muchas Fabricaciones Puras se generan debido a la Indirección. El motivo de la
Indirección normalmente es el Bajo Acoplamiento y la Alta Cohesión. Se añade un
intermediario para desacoplar los servicios.

Beneficios:

 Disminuir el acoplamiento entre componentes y/o paquetes

Patrones relacionados: Variaciones Protegidas, Bajo Acoplamiento, muchos


patrones GoF.

La mayoría de los intermediarios de Indirección son Fabricaciones Puras.

Ejemplo 6.9

En los programas de simulación donde los objetos cambian de dinámica


al chocar con otros objetos, ¿cómo se asignarían las responsabilidades?.
Supóngase que se desea simular cómo una esfera en caída libre es arrojada
desde una cierta altura respecto al suelo.

Dpto. Electrónica, Automática e Informática Industrial 165


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

Suelo y Esfera son clases conceptuales y por tanto candidatas a clases de diseño.
Para evitar el acoplamiento entre ambas clases se añade un grado de indirección. Se
creará una clase interacción que resuelva la responsabilidad de la interacción entre las
instancias de las dos clases.

Interaccion

interaccionEsferaSuelo()

Esfera Suelo

6.3.8 Fabricación Pura

Problema: ¿Qué objetos deberían de tener las responsabilidades cuando no se


quiere violar los objetivos de Alta Cohesión y Bajo Acoplamiento, pero las soluciones
que ofrece el Experto no son adecuadas?

Solución: Asignar responsabilidades altamente cohesivas a una clase artificial o


de conveniencia que no represente un concepto del dominio del problema. Algo
inventado para soportar Alta Cohesión, Bajo Acoplamiento y Reutilización.

El diseño de objetos se puede dividir, en general, en dos grandes grupos

1. Los escogidos de acuerdo a una descomposición de la representación.

2. Los seleccionados según una descomposición del comportamiento.

La descomposición en representación se emplea por que favorece el objetivo de


salto en la representación reducida. En la descomposición por comportamiento se asigna
responsabilidades agrupando comportamiento o algoritmos, sin estar relacionado con un
concepto del dominio del mundo real. En el caso de la aplicación Respuesta en
Frecuencia, las clases Polinomio, FDT, FiltroLineal, RespuestaFrecuencia están
basadas en una descomposición de la representación del universo del problema. En
cambio, la clase CoordinadorRespuestaFrELAI está tomada como una descomposición
del comportamiento.

Véase el siguiente problema: el almacenamiento de instancias en una base de


datos. El Experto en Información decidiría que estuviera donde están los datos. Sin
embargo, rompería los patrones de Alta Cohesión y Bajo Acoplamiento, ya que quiebra
la separación de la lógica del problema de la lógica de la base de datos. La solución es
crear una nueva clase que sea capaz de almacenar los objetos en algún tipo de
almacenamiento persistente; al que se llamará Almacenamiento Persistente. Esta clase
no es parte del dominio, sino algo creado artificialmente o fabricado para facilitar las
cosas al desarrollar el software.

166 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

Muchos de los patrones DOO que se van a ver son ejemplos de Fabricación
Pura: Adaptador, Estrategia, Observador, etc. También lo es el Controlador (GRASP) o
Fachada (GoF).

Tiene como beneficio el Bajo Acoplamiento y la Alta Cohesión. Usualmente,


una Fabricación Pura asume responsabilidades de las clases del dominio a las que se les
asignaría esas responsabilidades en base al patrón Experto; pero que no se las da, debido
a que disminuiría en cohesión y aumentaría la dependencia.

La Fabricación Pura emplea el patrón de Indirección, al asignar a una clase


artificial o de comportamiento, las responsabilidades de una clase del dominio para
evitar el acoplamiento y mantener alta la cohesión.

Ejemplo de Fabricación Pura:

 Guardar información en una base de datos.

 Separar la lógica del dominio de la vista (Observador-GoF).

 Coordinador o fachada.

Ejemplo 6.10

Guardar la información de la respuesta en frecuencia en un fichero.

Para mantener alta la cohesión y no romper la lógica del dominio con el de la


base de datos se emplea una Fabricación Pura. Empleando un grado de Indirección se
introduce la clase AlmacenamientoPersistente. Ésta se encargará de guardar la
información en disco. También habrá que añadir este nuevo servicio al Coordinador.

AlmacenamientoPersistente

AlmacenamientoPersistente()

<<local>> <<parameter>>

CoordinadorFrecELAI
RespuestaFrecuencia
ponerCircuito()
ponerCircuito() getModulo()
ponerParamResFr() calcularModulo()
guardarResultadosRespFr() calcularArgumento()
ponerParamResFr() RespuestaFrecuencia()
~CoordinadorFrecELAI() getFrInicio()
getFrFinal()
getFrIntervalo()

Dpto. Electrónica, Automática e Informática Industrial 167


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

: AlmacenamientoPersistente

: VistaFrecuenciaELAI 3: getModulo(float)

1: guardarResultadosRespFr(const char*)

: RespuestaFrecuencia
create()

2: AlmacenamientoPersistente(RespuestaFrecuencia*, const char*)

: CoordinadorFrecELAI

// De: "Apuntes de Informática Industrial" Carlos Platero.


// (R) 2005 Con licencia GPL.
// Ver permisos en licencia de GPL
#include "../../include/Dominio/AlmacenamientoPersistente.h"

AlmacenamientoPersistente::AlmacenamientoPersistente(RespuestaFrecuencia *pRespFr,
const char * pNomFich)
{
ofstream os(pNomFich);
os <<"Modulo de la respuesta en frecuencia"<<endl;
float fr;
for (fr = pRespFr->getFrInicio();fr <= pRespFr->getFrFinal();
fr+=pRespFr->getFrIntervalo())
os << fr << " :" << pRespFr->getModulo(fr)<<endl;
}

int CoordinadorFrecELAI::guardarResultadosRespFr(const char *pNomFich)


{
if (pRespFr == NULL) return (-1);
AlmacenamientoPersistente elAlmacen(this->pRespFr,pNomFich);
return (0);
}

6.3.9 Variaciones Protegidas

Problema: ¿Cómo diseñar objetos, subsistemas y sistemas de manera que las


variaciones e inestabilidades en estos elementos no tengan un impacto negativo en otros
elementos?

Solución: Identifique los puntos de variaciones previstas e inestabilidad; asigne


responsabilidades para crear una interfaz estable alrededor de ellos. Añadiendo
Indirección, Polimorfismo y una interfaz se consigue un sistema de Variaciones
Protegidas, VP. Las distintas implementaciones del componente y/o paquete ocultan las
variaciones internas a los sistemas clientes de éste. Dentro del componente, los objetos
internos colaboran en sus tareas con una interfaz estable.

El principal objetivo de este patrón es proteger a los clientes de las variaciones


de mejoras de los servicios dados por el componente servidor. Para tal fin, se definen

168 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

los puntos calientes y a éstos se les cubre con una interfaz estable, permitiendo variar el
componente sin interferir en las aplicaciones clientes.

Hay que distinguir dos tipos de variaciones:

 Puntos de variación: variaciones en el sistema actual que debe de


soportar a la vez.

 Puntos de evolución: puntos especulativos de variación que podrían


aparecer en el futuro, pero que no están presentes en los requisitos
actuales.

La aplicación de Variaciones Protegidas tiene un esfuerzo de diseño que siempre


hay que considerar. Si la necesidad de flexibilidad y protección de cambios es realista,
entonces está motivada la aplicación de VP. Un diseño debe ser un compromiso entre el
coste de cambio y su probabilidad.

La mayoría de los patrones y principios de diseño son mecanismos para


Variaciones Protegidas, entre los que se encuentran: Polimorfismo, Indirección,
Encapsulamiento y la mayoría de los patrones GoF.

Ejemplo 6.11

Realizar una aplicación que calcule el área de figuras geométricas. En


esta primera versión sólo se considera círculos y rectángulos.

Debido a que el programa debe de crecer, el concepto de Figura debe ser


aplicado de forma genérica (interfase). Se utiliza un punto de variación. De otro lado, la
subclase concreta se cargará dependiendo de la elección del usuario. Se empleará un
Método de Fabricación GoF (se verá más adelante).

Dpto. Electrónica, Automática e Informática Industrial 169


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

<<interface>>
IFiguras
<<abstract>> getArea()
<<typedef>>
<<static>> MetodoFabricacionFiguras()
tipoFigura

Rectangulo
Circulo lado1 : double
radio : double lado2 : double

Circulo() Rectangulo()
<<virtual>> getArea() <<virtual>> getArea()

#ifndef _AREAS_FIGURA_INC_
#define _AREAS_FIGURA_INC_
typedef enum tipoFig {CIRCULO, RECTANGULO} tipoFigura;
class IFiguras
{
public:
virtual double getArea() = 0;
static IFiguras* MetodoFabricacionFiguras
(tipoFigura, double, double);
};
class Circulo: public IFiguras
{
double radio;
friend class IFiguras;
Circulo(double param1):radio(param1) {}
public:
virtual double getArea()
{return (3.1416*radio*radio);}
};

class Rectangulo: public IFiguras


{
double lado1;
double lado2;
friend class IFiguras;
Rectangulo(double param1, double param2):
lado1(param1),lado2(param2) {}
public:
virtual double getArea()
{return (lado1*lado2);}
};

#endif

170 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

IFiguras* IFiguras::MetodoFabricacionFiguras(tipoFigura elTipo,


double param1,double param2 = 0)
{
if (elTipo == CIRCULO) return new Circulo(param1);
else if(elTipo == RECTANGULO) return new Rectangulo(param1,param2);
else return 0;

//////////////////////////////////////////////////////////////////////////

void CAreasFiguraDlg::OnCalcular()
{
UpdateData(TRUE);
IFiguras *pFigura= IFiguras::MetodoFabricacionFiguras(
this->m_Figura == true ? CIRCULO : RECTANGULO,
this->m_Param1,this->m_Param2);

this->m_Area = pFigura->getArea();
delete pFigura;
UpdateData(FALSE);

Otra aplicación de Variaciones Protegidas está en los intérpretes de líneas de


comando (script). El cliente tiene su sintaxis que se mantiene aunque varíe el servidor.
Hay muchos sistemas que ofrecen una línea de comandos para interactuar con él. Por
ejemplo, se podría pensar en Matlab. Es conocido que los comandos de versiones
anteriores se pueden usar en las nuevas. El cliente las utiliza pero no sabe si éstas han
sido mejoradas. Más aun, los desarrolladores confían en estos servicios y crean
aplicaciones, de más alto nivel, basadas en ellas.

Ejemplo 6.12
El código entregado corresponde con la implementación del patrón
comando, de manera que encapsula un objeto y el cliente lo ve como si fuese
una función (muy utilizado en lenguajes script). Se pide:
1. Ingeniería inversa: Diagrama de clases.
2. Ingeniería inversa: Diagrama de secuencias.
3. Resultado de su ejecución en la consola.
4. Indicar los patrones GRASP empleados en este patrón.
5. Diseñar e implementar la clase Saludo, de manera que se
despida al añadirse al macro.

Dpto. Electrónica, Automática e Informática Industrial 171


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

#include <iostream>
#include <vector>
using namespace std;

class Comando
{
public:
virtual void ejecutar() = 0;
};

class Hola : public Comando


{
public:
void ejecutar() { cout << "Hola "; }
};

class Mundo : public Comando


{
public:
void ejecutar() { cout << "Mundo! "; }
};

class Patron : public Comando


{
public:
void ejecutar() { cout << "Soy el comando patron!"; }
};

class Macro
{
vector<Comando*> Comandos;
public:
void incluir(Comando* c) { Comandos.push_back(c); }
void realizar() {
for(int i=0;i<Comandos.size();i++)
Comandos[i]->ejecutar();
}
};

int main()
{
Macro macro;
macro.incluir(new Hola);
macro.incluir(new Mundo);
macro.incluir(new Patron);
macro.realizar();
}

172 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

1) <<interface>>
vector<Comando*> Comando
<<abstract>> ejecutar() : void

2) Macro

incluir()
realizar()

principal Hola Patron


: Macro
Mundo
ejecutar() ejecutar()
ejecutar()
incluir(new Hola)
: Hola

incluir(new Patron) : Patron

realizar( )
ejecutar( )

ejecutar( )

3) Hola Mundo! Soy el comando patron!

4) El patrón Comando emplea Variaciones Protegidas (GRASP), de


forma que el cliente no ve las modificaciones que está realizando el
servidor.
<<interface>>
vector<Comando*> Comando
5) <<abstract>> ejecutar() : void

Macro
Saludo
incluir()
realizar() ejecutar()

Hola
Patron
ejecutar() Mundo
ejecutar()
ejecutar()

class Saludo : public Comando


{
public:
void ejecutar() { cout << " Un saludo. "; }
};

La sostenibilidad depende de las VP. Estas aplicaciones se basan en el principio


de sustitución de Liskov:

“El software que hace referencia a un tipo T debería de trabajar correctamente


con cualquier implementación o subclase T que la sustituya”.

Dpto. Electrónica, Automática e Informática Industrial 173


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

Por otro lado, uno de los patrones antiguos GRASP era “No hable con
Extraños” o Ley de Demeter. Significa evitar crear diseños que recorran largos caminos
de la estructura de los objetos. No se podía enviar mensajes a objetos distantes,
indirectos o extraños. Tales diseño son frágiles con respecto a los cambios en las
estructuras de los objetos. Pero ahora, con el principio de Variaciones Protegidas, se
sustituye a No hable con Extraños.

No hable con Extraños establecía que un método, sólo, debería enviar mensajes
a los siguientes objetos:

1. A él mismo (objeto this).

2. A un parámetro de un servicio propio.

3. A un atributo de él.

4. A una colección de él.

5. A un objeto creado en un método propio.

La intención es evitar el acoplamiento entre un cliente con objetos indirectos.

Beneficios de No hable con Extraños:

• Se añaden fácilmente las extensiones que se necesitan.

• Se puede introducir nuevas implementaciones sin afectar a los


clientes.

• Se reduce el acoplamiento.

• Se puede disminuir el impacto o coste de los cambios.

Hay que saber escoger las batallas. En sistemas maduros, la estructura es más
estable y se puede hablar con extraños. En cambio, en sistemas nuevos es recomendable
utilizar este antiguo patrón GRASP3.

Ejemplos de Variaciones Protegidas

 Las máquinas virtuales son ejemplos complejos de Indirección para


conseguir VP.

3
Para los programadores noveles se aconseja utilizar este patrón. Empléese en el trabajo de curso.

174 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

 Lectura y escritura de datos de sistemas externos.

 Diseños dirigidos por un intérprete.

Ejemplo 6.13

La aplicación de Respuesta en Frecuencia no depende sólo del


algoritmo de calcular el módulo y argumento de un filtro, sino también de su
visualización en un diagrama de Bode. Realizar un diseño para el paquete de
representación gráfica.

Tal cual se presentó en la vista de gestión, la aplicación tenía un paquete para la


visualización del diagrama de Bode. Se había elegido una solución basada en software
prefabricado (ActiveX).

MFC (.NET)
ActiveX-Bode VistaFrecuencia
ELAI

DominioFrecunc
iaEla STL-ANSI C++

Entre las posibles soluciones tecnológicas actuales, se ha elegido NTGraph4. En


un diseño robusto, esta inserción supone un punto caliente. Por varios motivos:

 En el futuro se puede cambiar de componente, esto es, salto a otra nueva


tecnología.

 Ampliación de nuevos servicios en la representación del diagrama de


Bode.

La aplicación de Variaciones Protegidas supone, de momento, el uso de patrones


GoF: Adaptador y Factoría; mientras en GRASP implica Indireción, Polimorfismo y
Fabricación Pura.

4
http://www.codeproject.com/miscctrl/ntgraph_activex.asp

Dpto. Electrónica, Automática e Informática Industrial 175


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

<<interface>>
IAdaptadorVisualizar Interfaz
CRespFrMFCDlg estable
<<abstract>> InicializarPlotXY()
<<abstract>> PintarPlotXY()
<<static>> factoriaVisualizadores()
Solución
Cliente del tecnológica
paquete
Visualizador AdaptadorVisualCNTGraph
CNTGraph
AdaptadorVisualCNTGraph()
Constructor privado <<virtual>> InicializarPlotXY()
Método de Fabricacion <<virtual>> PintarPlotXY()
(GoF)

// De: "Apuntes de Informática Industrial" Carlos Platero.


// (R) 2005 Con licencia GPL.
// Ver permisos en licencia de GPL

#include "../../ntgraph.h"

//Tipos de visualizadores
enum PlataformaVisual{NTGRAPH} ;

class IAdaptadorVisualizar
{
public:
virtual void InicializarPlotXY(void) = 0;
virtual void PintarPlotXY(float,float,float, double *)= 0;
//Factoria de Visualizadores
static IAdaptadorVisualizar *factoriaVisualizadores(enum PlataformaVisual,
CNTGraph *p1 = NULL);

};

class AdaptadorVisualCNTGraph : public IAdaptadorVisualizar


{
CNTGraph *graph;
AdaptadorVisualCNTGraph(CNTGraph *gr): graph(gr){}
friend class IAdaptadorVisualizar;
public:
virtual void InicializarPlotXY(void);
virtual void PintarPlotXY(float,float,float, double *);
};

176 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

6.4 Patrones de diseño GoF

Uno de los hitos más importantes en el diseño orientado a objetos fue la


publicación del libro “Design Patterns” por Gamma, Helm, Johnson y Vlissides en
1995; llamados comúnmente “Gang of Four”, GoF. En este libro se muestran 23
patrones ampliamente utilizados. En este apartado se tratarán algunos de ellos.

6.4.1 Adaptador

Problema: ¿Cómo resolver interfaces incompatibles, o proporcionar una interfaz


estable para componentes parecidos con diferentes interfaces?

Solución: Convierta la interfaz original de una componente en otra, mediante un


objeto adaptador intermedio.

El propósito de este patrón es convertir la interfaz de una clase en otra interfaz


que es la que esperan los clientes. Este patrón permite que cooperen clases que de otra
forma no podrían colaborar por no tener compatibilidad entre ellas.

Dpto. Electrónica, Automática e Informática Industrial 177


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

Debería usarse el patrón Adaptador cuando:

 Se quiere utilizar una clase existente y su interfaz no concuerda con lo


que se espera.

 Se quiere crear una clase reutilizable que coopere con clases no


relacionadas o que no han sido previstas, es decir, clases que no tienen
por qué tener interfaces compatibles.

Los participantes en este patrón realizan los siguientes roles:

 Objetivo: define los servicios del dominio que usa el cliente. Representa
un interfaz estable con los servicios tal cual espera el cliente.

 Cliente: utiliza los servicios del paquete a través del interfaz Objetivo.

 Adaptable: Implementa los servicios del paquete.

 Adaptador: adapta la interfaz de Adaptable a la interfaz Objetivo.

<<interface>>
Cliente
Objetivo
Implementación
del servicio

Adaptador Adaptable

Los clientes llaman a operaciones a través de la interfase estable. A su vez el


adaptador llama a operaciones de Adaptable que son las que satisfacen las peticiones.

En una implementación en C++ de un adaptador de clases, Adaptador debería


heredar públicamente de objetivo y tener como atributo privado a un objeto de la clase
Adaptable.

Nótese que los nombres de los tipos incluyen el nombre de patrón “Adaptador”.

La aplicación del Adaptador es una especialización de Variaciones Protegidas,


Indirección y Polimorfismo. En GRASP es el polimorfismo, aquí es una especialización
que se llama Adaptador. Se puede analizar muchos patrones más complejos y
especializados en función de la familia GRASP. Existen muchos publicados y es el
alfabeto del DOO.

178 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

Ejemplo 6.14

Las series de Fibonacci se hicieron famosas en la Edad Media por que


planteó el problema de procreación de los conejos de manera formal. Estudió
que si se partía de una pareja de conejos cómo éstos se multiplicaban con el
tiempo. La solución se encuentra en la Serie de Fibonacci. Ésta se construye
con la suma de los dos últimos valores. Los dos primeros términos de la serie
son el 1 y el 1. Los demás se obtienen con la regla mencionada, la suma de los
valores anteriores:

Fn = Fn−1 + Fn− 2

{1 1 1 2 3 5 8 13 ...}

Un generador de la serie de Fibonacci ha sido tomado de Bruce Eckel y


Check Allison5. Adaptarlo para emplear los algoritmos dados por las STL.

// From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison.
// (c) 1995-2004 MindView, Inc. All Rights Reserved.
// See source code use permissions stated in the file 'License.txt',
// distributed with the code package available at www.MindView.net.
#ifndef FIBONACCIGENERATOR_H
#define FIBONACCIGENERATOR_H

class FibonacciGenerator {
int n;
int val[2];
public:
FibonacciGenerator() : n(0) { val[0] = val[1] = 1; }
int operator()() {
int result = n > 2 ? val[0] + val[1] : 1;
++n;
val[0] = val[1];
val[1] = result;
return result;
}
int count() { return n; }
};

#endif // FIBONACCIGENERATOR_H ///:~

Se trata de diseñar un adaptador que permita utilizar algoritmos dados por las
STL como for_each( ) o accumulate(). Estos servicios requieren que la información esté
preparada como un tipo vector de las STL, std::vector<>. Para tal fin, se emplea el
patrón Adaptador:

5
"Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison. (c) 1995-2004
MindView, Inc.

Dpto. Electrónica, Automática e Informática Industrial 179


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

<<interface>>
IAdaptadorFibonacci
<<abstract>> getSerie()
<<static>> factoriaFibonacci()

FibonacciGenerator
AdaptadorFibonacci n : int
tamanyo : unsigned val[2] : int

AdaptadorFibonacci() 1 1 FibonacciGenerator()
<<virtual>> getSerie() operator()()
count()

Las clases de los roles objetivo y adaptador quedarán definidas como:

// De: "Apuntes de Informática Industrial" Carlos Platero.


// (R) 2004 Con licencia GPL.
// Ver permisos en licencia de GPL
//
// Ejemplo de adaptador GoF entre el generador de Fibonacci
// y las librerías STL
#ifndef _ADAPTADOR_FIBO_INC_
#define _ADAPTADOR_FIBO_INC_

#include <vector>
#include "FibonacciGenerator.h"

class IAdaptadorFibonacci
{
public:
virtual std::vector<unsigned> & getSerie() = 0;
static IAdaptadorFibonacci *factoriaFibonacci(unsigned grado);
};

class AdaptadorFibonacci: public IAdaptadorFibonacci


{
FibonacciGenerator f;
unsigned tamanyo;
std::vector<unsigned> elVectorFibon;
friend class IAdaptadorFibonacci;
AdaptadorFibonacci(unsigned num): tamanyo(num)
{
for (unsigned i=0;i<=tamanyo;i++)
elVectorFibon.push_back(f());
}
public:

virtual std::vector<unsigned> & getSerie()


{return elVectorFibon;}
};

180 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

El cliente, ahora, podrá emplear al generador de la serie de Fibonacci utilizando


las funciones algorítmicas de las STL.

// De: "Apuntes de Informática Industrial" Carlos Platero.


// (R) 2005 Con licencia GPL.
// Ver permisos en licencia de GPL
//
//Ejemplo del patrón GoF de adaptador
//Desde un generador de números de Fibonnaci, dado en FibonacciGenerator.h,
//Adaptación para emplear las STL
//Código de prueba (XP)

#include <iostream>
#include "AdaptadorFibonacci.h"
#include <numeric>
#include <algorithm>

IAdaptadorFibonacci* IAdaptadorFibonacci::factoriaFibonacci(unsigned grado)


{
return (new AdaptadorFibonacci(grado));
}
using namespace std;

void imprimir(unsigned);

int main()
{
const unsigned numeroFibo = 20;
IAdaptadorFibonacci *pAdaptadorFibo =
IAdaptadorFibonacci::factoriaFibonacci(numeroFibo);

cout << "Tabla de Fibonacci" <<endl;


cout << "------------------" <<endl;

for_each(pAdaptadorFibo->getSerie().begin(),pAdaptadorFibo->getSerie().end(),
imprimir);
//Ver GRASP "No hable con extraños" y el código maduro

cout << "Valor acumulado total: "


<< accumulate(pAdaptadorFibo->getSerie().begin(),
pAdaptadorFibo->getSerie().end(), 0) << endl;
return 0;

void imprimir(unsigned numFibo)


{
static unsigned indice;
cout << indice++ << ": " << numFibo <<endl;
}

Dpto. Electrónica, Automática e Informática Industrial 181


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

6.4.2 Factoría

Problema: ¿Quién debe ser responsable de la creación de los objetos cuando


existen consideraciones especiales, como una lógica de creación compleja, el deseo de
separar las responsabilidades de la creación para manejar la cohesión, etc.?

Solución: Crear un objeto de Fabricación Pura denominado Factoría que maneje


la creación.

Cuando aparece una nueva variación del tipo de los datos se aplica el patrón
Polimorfismo [GRASP], tal cual se comentó en el apartado 6.3.6. Al principio parece
que sólo es necesario implementarlo en el punto de la aplicación que se introduce la
variación. Sin embargo, mayoritariamente sucede que además se requiere un constructor
para la nueva variación y su repercusión se extiende por todo el código. Para estos casos
se debe aplicar Variaciones Protegidas. Por tanto, se considera la creación de un punto
de variación o punto caliente y se coloca una interfase estable a través del
Polimorfismo, la Indirección y el Adaptador. La construcción de estos objetos deben ser
forzados a ser creados en una única Factoría.

Por ejemplo, cuando al aplicar Variaciones Protegidas y Adaptador sobre la


representación en el diagrama de Bode de la aplicación Respuesta en Frecuencia, ¿quién
crea el adaptador de NTGraph? ¿Y cómo determinar qué clase de adaptador crear?.

Si los creará algún objeto del dominio, estas responsabilidades exceden de la


pura lógica de la aplicación y entra en otras cuestiones relacionadas con la conexión con
componentes de software externos.

Este punto subraya, otro principio de diseño fundamental: mantener siempre una
separación de intereses. La elección de un objeto del dominio para crear los adaptadores
no está de acuerdo con los objetivos de separación de intereses. Además disminuye su
cohesión. La lógica de qué clase Adaptador se instancia es resuelto en la Factoría,
leyendo una fuente externa y después cargando la clase dinámicamente.

Una alternativa típica en este caso es aplicar el patrón Factoría. Los objetos
Factoría tienen varias ventajas:

 Separación de responsabilidades en la creación compleja en objetos de apoyo


cohesivo.

 Ocultan la lógica de creación potencialmente compleja.

 Permite introducir estrategias para mejorar el rendimiento de la gestión de la


memoria, como objetos caché o de reciclaje.

El cliente sólo utilizará las interfases de sus paquetes servidores, dejando que sea
la lógica externa quien decida sobre la implementación y la factoría quien crea los

182 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

objetos que implementan los servicios. A este patrón GoF se le llama Factoría Abstacta.
Se empleará cuando:

 un sistema debe ser independiente de cómo se crean, componen y


representan sus productos.

 un sistema debe ser configurado como una familia de productos entre


varias.

 quiere proporcionar una biblioteca de clases de productos y sólo quiere


revelar sus interfaces, no sus implementaciones.

La estructura del patrón queda reflejada en el siguiente diagrama de clases:

<<interface>>
IProductoA

Cliente
ProductoA1 ProductoA2

<<interface>>
IFactoriaObjetos
<<interface>> MetodoFabricacion() FactoriaConcretaA
IProductoB
MetodoFabricacion()

ProductoB2

ProductoB1
FactoriaConcretaB

Los roles desempeñados son:

 Cliente: sólo usa interfaces declarados por las clases de Fabricación


Abstracta y Productos Abstractos

 Producto Abstracto: declara una interfaz para un tipo de objeto (p.ej.


IClaseA)

 Producto Concreto: define un objeto producto para que sea creado por la
Factoría correspondiente. Implementa la interfaz de Producto Abstracto.

Dpto. Electrónica, Automática e Informática Industrial 183


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

 Factoría Abstracta: declara una interfaz para operaciones que crean


objetos productos abstractos

 Factoría Concreta: implementa las operaciones para crear objetos


producto concretos

Ejemplo 6.15

Se pretende simular el evento de sacar de una bolsa un


tornillo y una tuerca y saber si se pueden ensamblar. La bolsa
puede contener tornillos y tuercas de diferentes métricas.
Hágase para el caso concreto de elementos DIN84 y DIN316.

Se diseñará de manera que el cliente sólo utilice el concepto de


tornillos y tuercas. La responsabilidad de saber si pertenece a la misma
métrica quedará a los objetos concretos creados por las factorías
abstractas. El diseño sería:

IFactoria
ITornillo <<abstract>> fabricacionTornillo()
ITuerca <<abstract>> getMetrica() <<abstract>> fabricacionTuerca()
<<abstract>> getMetrica()

TornilloDIN84 FactoriaTornillos

TornilloDIN84() <<virtual>> fabricacionTornillo()


TuercaDIN84
<<virtual>> getMetrica() <<virtual>> fabricacionTuerca()
TuercaDIN84()
<<virtual>> getMetrica() FactoriaTuercas

TuercaDIN316 <<virtual>> fabricacionTornillo()


<<virtual>> fabricacionTuerca()
TornilloDIN316
<<virtual>> getMetrica()
TornilloDIN316()
<<virtual>> getMetrica()

<<typedef>>
metrica

184 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

typedef enum{DIN84, DIN316} metrica;


class FactoriaTuercas;
class ITuerca
{
public:
virtual metrica getMetrica() = 0;

};
class TuercaDIN84 : public ITuerca
{
metrica laMetrica;
friend class FactoriaTuercas;
TuercaDIN84() {laMetrica = DIN84;}
public:
virtual metrica getMetrica() {return laMetrica;}
};
class TuercaDIN316 : public ITuerca
{
metrica laMetrica;
friend class FactoriaTuercas;
TuercaDIN316() {laMetrica = DIN316;}
public:
virtual metrica getMetrica() {return laMetrica;}

};

class FactoriaTornillos;
class ITornillo
{
public:
virtual metrica getMetrica() = 0;
};
class TornilloDIN84 : public ITornillo
{
metrica laMetrica;
friend class FactoriaTornillos;
TornilloDIN84() {laMetrica = DIN84;}
public:
virtual metrica getMetrica() {return laMetrica;}
};
class TornilloDIN316 : public ITornillo
{
metrica laMetrica;
friend class FactoriaTornillos;
TornilloDIN316() {laMetrica = DIN316;}

public:
virtual metrica getMetrica() {return laMetrica;}

};

Dpto. Electrónica, Automática e Informática Industrial 185


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

class IFactoria
{
public:
virtual ITornillo * fabricacionTornillo(metrica) = 0;
virtual ITuerca * fabricacionTuerca(metrica) = 0;
};
class FactoriaTornillos : public IFactoria
{
public:
virtual ITornillo * fabricacionTornillo(metrica laMetrica)
{
if(laMetrica == DIN84) return new TornilloDIN84;
else if (laMetrica == DIN316) return new TornilloDIN316;
else return 0;
}
virtual ITuerca * fabricacionTuerca(metrica laMetrica)
{return 0;}
};
class FactoriaTuercas: public IFactoria
{
public:
virtual ITornillo * fabricacionTornillo(metrica laMetrica)
{return 0;}

virtual ITuerca * fabricacionTuerca(metrica laMetrica)


{
if(laMetrica == DIN84) return new TuercaDIN84;
else if (laMetrica == DIN316) return new TuercaDIN316;
else return 0;
}
};

#include "IFactoria.h"
#include <iostream>
#include <stdlib.h>

int main()
{
IFactoria *pFactoriaTornillos = new FactoriaTornillos;
IFactoria *pFactoriaTuercas = new FactoriaTuercas;
std::cout<<"Simulacion de sacar tornillo y tuerca de forma aleatoria" <<std::endl;
std::cout<<"La bolsa contiene tornillos y tuercas DIN84 y DIN316"<<std::endl;
std::cout<<"Pulsar c o C para sacar tornillo y tuerca"<<std::endl;
char opcion; std::cin>> opcion;
while(opcion == 'c' || opcion == 'C') {
ITornillo *pTornillo =
pFactoriaTornillos->fabricacionTornillo(rand() % 2 == 1 ? DIN84 : DIN316);
ITuerca *pTuerca =
pFactoriaTuercas->fabricacionTuerca(rand() % 2 == 1 ? DIN84 : DIN316);
if(pTornillo->getMetrica() == pTuerca->getMetrica()){
char *mensaje = pTuerca->getMetrica() == DIN84 ? "DIN84" : "DIN316";
std::cout<<"Ensamblaje correcto: metrica " << mensaje <<std::endl;
}else
std::cout<<"Ensamblaje incorrecto" << std::endl;

std::cout<<"Pulsar c o C para sacar tornillo y tuerca"<<std::endl;


std::cin>> opcion;
delete pTornillo, pTuerca;
}
delete pFactoriaTornillos, pFactoriaTuercas;
return 0;
}

Para la elaboración de las Factorías se emplea el patrón Método de Fabricación


(GoF). Se define una interfaz de factoría para crear los objetos, pero se deja que sean los
métodos de fabricación quien instancia las subclases. Se utiliza cuando:

186 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

 una clase no puede prever la clase de objetos que debe crear.

 una clase quiere que sean sus subclases quienes especifiquen los objetos que ésta
crea.

 las clases delegan la responsabilidad en una de entre varias clases auxiliares y se


desea localizar qué subclase de auxiliar concreta es en la que se delega.

<<interface>>
<<interface>>
IFactoriaObjetos
IProductoA
MetodoFabricacion() : IProductoA

ProductoA1
FactoriaConcretaA
ProductoA2
MetodoFabricacion()

return new ProductoConcreto

Los roles que se desempeñan en este patrón son:

 Producto: Interfaz de los objetos que crea el método de fabricación

 ProductoConcreto: Realización de la interfaz Producto

 Factoría: Declara el servicio de MétodoFabricación que retorna un objeto


de tipo producto abstracto

 FactoríaConcreto: redefine el método de fabricación para devolver una


instancia de un ProductoConcreto

La Factoría se apoya en sus subclases para definir el método de fabricación de


manera que éste devuelva una instancia del producto concreto adecuado.

Hay que tener mucho cuidado con la creación con las Factorías. En C++ es el
programador el que debe posteriormente liberar, con posterioridad, el objeto creado en
la factoría.

A menudo se accede a las factorías con el patrón Singleton.

Dpto. Electrónica, Automática e Informática Industrial 187


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

Ejemplo 6.16

Realizar una factoría de objetos de Nombres. El cliente utilizará la clase


abstracta INombre y las clases concretas serán empleando std::string, CString
y en estilo C (ver ejemplo 4.4).

Primera solución

Se empleará un diseño basado en Factoría Abstracta. El Cliente sólo utilizará los


interfases para recibir los servicios.

<<interface>>
Cliente INombre
(from Logical Vi ew)
<<enum>>
<<abstract>> setNombre() Plataforma
<<abstract>> getNombre()

<<interface>>
IFactoriaNombre STDNombre CNombre MFCNombre
<<abstract>> MetodoFabricacionNombre() elNombre[80] : char (from MetodoFabriacion)

<<virtual>> setNombre()
<<virtual>> getNombre() <<virtual>> setNombre() <<virtual>> setNombre()
STDNombre() <<virtual>> getNombre() <<virtual>> getNombre()
CNombre() MFCNombre()
FactoriaNombre

<<virtual>> MetodoFabricacionNombre()

#ifndef _INOMBRE_INC_ // De: "Apuntes de Informática Industrial"


#define _INOMBRE_INC_ // (R) 2005 Con licencia GPL.
enum Plataforma{ESTANDAR_STL, ESTILO_C, // Ver permisos en licencia de GPL
CADENA_MFC}; #ifndef _INC_STDNOMBRE_
#define _INC_STDNOMBRE_
class INombre {
public: #include <string>
virtual void setNombre (const char *) = 0; #include "INombre.h"
virtual const char * getNombre () = 0; class FactoriaNombre;
}; class STDNombre : public INombre
{
#endif public:
virtual void setNombre(const char *cadena)
{ elNombre = cadena; }
virtual const char * getNombre (void)
{ return (elNombre.c_str());}
#ifndef _INC_MFCNOMBRE_
#define _INC_MFCNOMBRE_ private:
#include <afx.h> std::string elNombre;
#include "INombre.h" STDNombre () {}
class FactoriaNombre; friend class FactoriaNombre;
class MFCNombre : public INombre {
public:
virtual void setNombre(const char *cadena)
};
{ elNombre=cadena; }
virtual const char * getNombre (void) #endif
{ return (elNombre);}
private:
CString elNombre;
MFCNombre () {}
friend class FactoriaNombre;
};
#endif

188 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

// De: "Apuntes de Informática Industrial" // Ver permisos en licencia de GPL


// (R) 2005 Con licencia GPL. #ifndef _IFACTORIA_INC_
// Ver permisos en licencia de GPL #define _IFACTORIA_INC_
#ifndef _INC_CNOMBRE_
#define _INC_CNOMBRE_ #include "STDNombre.h"
#include "CNombre.h"
#include <string> #include "MFCNombre.h"
#include "INombre.h"
class FactoriaNombre; class IFactoriaNombre
class CNombre : public INombre { {
public: public:
virtual void setNombre(const char *cadena) virtual INombre* MetodoFabricacionNombre
{ strcpy (elNombre, cadena); } (enum Plataforma) = 0;

virtual const char * getNombre (void) };


{ return (elNombre);}
private: class FactoriaNombre: public IFactoriaNombre
char elNombre[80]; {
CNombre () {} public:
friend class FactoriaNombre; virtual INombre* MetodoFabricacionNombre
(enum Plataforma tipo)
}; {
if(tipo==ESTANDAR_STL)return new STDNombre;
else if(tipo==ESTILO_C) return new CNombre;
else if(tipo==CADENA_MFC) return new
// De: "Apuntes de Informática Industrial" MFCNombre;
// (R) 2005 Con licencia GPL. else return NULL;
// Ver permisos en licencia de GPL
#include <iostream>
}
#include "../includes/INombre.h" };
#include "../includes/FactoriaNombres.h" #endif

using namespace std;

int main ( void )


{
//Solo utiliza referencias abstractas
IFactoriaNombre *pFactoria = new (FactoriaNombre);
INombre *pNombre1 = pFactoria->MetodoFabricacionNombre (ESTANDAR_STL);
INombre *pNombre2 = pFactoria->MetodoFabricacionNombre (ESTILO_C);
INombre *pNombre3 = pFactoria->MetodoFabricacionNombre (CADENA_MFC);

pNombre1->setNombre("Manolo Gonzalez");
pNombre2->setNombre("Pedro Lopez");
pNombre3->setNombre("Ana Rodriguez");

cout << pNombre1->getNombre() << endl;


cout << pNombre2->getNombre() << endl;
cout << pNombre3->getNombre() << endl;

delete pNombre1, pNombre2, pNombre3;


delete pFactoria;
return 0;
}

Segunda solución:

El método de fabricación se coloca como un servicio estático dentro de la clase


de interfase de Nombre. Para asegurarse de que sólo se utilizará este punto de creación
se ha colocado los constructores privados y se ha declarado a la clase abstracta como
amiga.

Dpto. Electrónica, Automática e Informática Industrial 189


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

<<interface>>
INombre
<<abstract>> setNombre()
<<abstract>> getNombre()

STDNombre CNombre MFCNombre


elNombre[80] : char (from M etodoFabriaci on)

<<virtual>> setNombre()
<<virtual>> getNombre() <<virtual>> setNombre() <<virtual>> setNombre()
STDNombre() <<virtual>> getNombre() <<virtual>> getNombre()
CNombre() MFCNombre()

// De: "Apuntes de Informática Industrial" // De: "Apuntes de Informática Industrial"


// Ver permisos en licencia de GPL // (R) 2005 Con licencia GPL.
// Ver permisos en licencia de GPL
#ifndef _INOMBRE_INC_ #ifndef _INC_STDNOMBRE_
#define _INOMBRE_INC_ #define _INC_STDNOMBRE_

enum Plataforma{ESTANDAR_STL, ESTILO_C, #include <string>


CADENA_MFC}; #include "INombre.h"

class INombre { class STDNombre : public INombre


public: {
virtual void setNombre (const char *) = 0; public:
virtual const char * getNombre () = 0; virtual void setNombre(const char *cadena)
{ elNombre = cadena; }
//Factoria de objetos virtual const char * getNombre (void)
static INombre *MetodoFabricacionNombre(enum { return (elNombre.c_str());}
Plataforma);
}; private:
#endif std::string elNombre;
STDNombre () {}//Desactivar al constructor
friend class INombre; // Sólo se fabrica
// De: "Apuntes de Informática Industrial" //desde el método de fabricación
// (R) 2005 Con licencia GPL. };
// Ver permisos en licencia de GPL #endif
#ifndef _INC_STDNOMBRE_
#define _INC_STDNOMBRE_
#ifndef _INC_MFCNOMBRE_
#include <string> #define _INC_MFCNOMBRE_
#include "INombre1.h"
#include <afx.h>
class CNombre : public INombre #include "INombre.h"
{
public: class MFCNombre : public INombre
{
virtual void setNombre(const char *cadena) public:
{ strcpy (elNombre, cadena); } virtual void setNombre(const char *cadena)
{ elNombre=cadena; }
virtual const char * getNombre (void) virtual const char * getNombre (void)
{ return (elNombre);} { return (elNombre);}
private: private:
char elNombre[80]; CString elNombre;
}; };
#endif
#endif

190 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

// De: "Apuntes de Informática Industrial"


// (R) 2005 Con licencia GPL.
// Ver permisos en licencia de GPL

#include <iostream>
#include "../includes/STDNombre.h"
#include "../includes/CNombre.h"
#include "../includes/MFCNombre.h"
//Método único para producir los objetos nombres
INombre* INombre::MetodoFabricacionNombre(enum Plataforma tipo)
{
if(tipo == ESTANDAR_STL) return new STDNombre;
else if(tipo == ESTILO_C) return new CNombre;
else if(tipo == CADENA_MFC) return new MFCNombre;
else return NULL;
}

using namespace std;

int main ( void )


{
INombre *pNombre1 = INombre::factoriaObjetos(ESTANDAR_STL);
INombre *pNombre2 = INombre::factoriaObjetos(ESTILO_C);
INombre *pNombre3 = INombre::factoriaObjetos(CADENA_MFC);

pNombre1->setNombre("Manolo Gonzalez");
pNombre2->setNombre("Pedro Lopez");
pNombre3->setNombre("Ana Rodriguez");

cout << pNombre1->getNombre() << endl;


cout << pNombre2->getNombre() << endl;
cout << pNombre2->getNombre() << endl;

delete pNombre1, pNombre2, pNombre3;


return 0;
}

6.4.3 Singleton

Problema: ¿Cómo garantizar que una clase sólo tenga una instancia y
proporciona un punto de acceso global a ella? .

Solución: Definir un método estático de la clase que devuelva el singleton.

Del uso del patrón Factoría surge un nuevo problema de diseño ¿quién crea a la
Factoría?. Sólo se necesita una única instancia de Factoría y ésta puede ser llamada
desde distintos lugares del código. Una solución sería ir pasando la instancia de Factoría
en los métodos, pero este proceder no es conveniente, ya que implica acoplamiento por
necesitar visibilidad. La solución está en el patrón Singleton.

Ocasionalmente es conveniente mantener visibilidad global o un único punto de


acceso a una única instancia de una clase. Es importante que algunas clases tengan
exactamente una instancia. Por ejemplo, aunque puede haber muchas impresoras, sólo
debería de haber una única cola de impresión. De la misma manera, en aplicaciones de
control de procesos, sólo debería de existir una única instancia para el driver del
convertidor analógico/digital, aunque pudieran existir varios filtros digitales. Son
ejemplos de este problema patrón.

Dpto. Electrónica, Automática e Informática Industrial 191


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

¿Cómo se puede asegurar que una clase tenga una única instancia y que sea
fácilmente accesible?. Una variable global hace accesible a un objeto, pero no se
previene de crear múltiples instancias de esta clase.

Una solución es hacer que sea la propia clase la responsable de su única


instancia. La clase debe garantizar que no se pueda crear ninguna otra instancia y
proporciona un modo de acceder a su instancia.

La clave del Singleton es evitar que el cliente no tenga el control sobre la vida
del objeto. Para tal fin se declaran todos los constructores como privados y se previene
para que el compilador no produzca constructores por defecto ni sobrecarga de
operadores de asignación. Se construye el servicio de tipo estático para obtener la
referencia Singleton, getInstancia(), y también se colocará como estática la propia
referencia. En la notación se puede emplear el estereotipo <<1>> para indicar que sólo
existe una única instancia de esta clase

<<1>>
Singleton
<<static>> unicaInstancia : Singleton
datosDelSingleton retorna unicaInstancia

Singleton()
operator=()
<<static>> getInstancia()
operacionesDatosSingleton()

#include <iostream>
using namespace std;

class Singleton {
int i; //Dato por ejemplo
Singleton(int x) : i(x) { }
void operator=(Singleton&); // Para desactivar
Singleton(const Singleton&); // Para desactivar
public:
static Singleton& getInstancia() {
static Singleton unicaInstancia(47); //P.ej valor 47
return unicaInstancia;
}
int getValor() { return i; }
void setValor(int x) { i = x; }
};
int main() {
Singleton& s = Singleton::getInstancia();
cout << s.getValor() << endl;
Singleton& s2 = Singleton::getInstancia();
s2.setValor(9);
cout << s.getValor() << endl;
return 0;
}

192 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

Ejemplo 6.17

Realizar una única factoría de objetos Nombre que sea accesible en


cualquier parte de la aplicación (ver problema 6.12).

Cliente1

<<interface>>
INombre
Cliente2 <<abstract>> setNombre()
<<abstract>> getNombre()

MFCNombre CNombre

<<1>> <<virtual>> setNombre()


<<virtual>> setNombre()
FactoriaUnicaNombres <<virtual>> getNombre()
<<virtual>> getNombre()

FactoriaUnicaNombres()
operator=()
FactoriaUnicaNombres()
<<static>> getInstancia() MFCNombre
MetodoFabricacionNombre()
<<enum>>
Plataforma <<virtual>> setNombre()
<<virtual>> getNombre()

// De: "Apuntes de Informática Industrial" Carlos Platero.


// Ver permisos en licencia de GPL
#ifndef _IFACTORIA_INC_
#define _IFACTORIA_INC_

#include "STDNombre.h"
#include "CNombre.h"
#include "MFCNombre.h"

class FactoriaUnicaNombres
{
FactoriaUnicaNombres(){};
void operator=(FactoriaUnicaNombres&); // Para desactivar
FactoriaUnicaNombres(const FactoriaUnicaNombres&); // Para desactivar
public:
static FactoriaUnicaNombres& getInstancia()
{
static FactoriaUnicaNombres unicaInstancia;
return unicaInstancia;
}

INombre* MetodoFabricacionNombre (enum Plataforma tipo)


{
if(tipo == ESTANDAR_STL) return new STDNombre;
else if(tipo == ESTILO_C) return new CNombre;
else if(tipo == CADENA_MFC) return new MFCNombre;
else return NULL;
}
};

#endif

Dpto. Electrónica, Automática e Informática Industrial 193


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

// De: "Apuntes de Informática Industrial" Carlos Platero.


// (R) 2005 Con licencia GPL.
// Ver permisos en licencia de GPL
#include <iostream>
#include "../includes/INombre.h"
#include "../includes/FactoriaUnicaNombres.h"
using namespace std;
int main ( void )
{
//Solo utiliza referencias abstractas
void otrosNombres (void);
FactoriaUnicaNombres &laFactoria = FactoriaUnicaNombres::getInstancia();
INombre *pNombre1 = laFactoria.MetodoFabricacionNombre (ESTANDAR_STL);
INombre *pNombre2 = laFactoria.MetodoFabricacionNombre (ESTILO_C);

pNombre1->setNombre("Manolo Gonzalez");
pNombre2->setNombre("Pedro Lopez");

cout << pNombre1->getNombre() << endl;


cout << pNombre2->getNombre() << endl;

delete pNombre1, pNombre2;


otrosNombres();
return 0;
}

void otrosNombres ( void )


{
//Solo utiliza referencias abstractas
FactoriaUnicaNombres &laMismaFactoria = FactoriaUnicaNombres::getInstancia();
INombre *pNombre3 = laMismaFactoria.MetodoFabricacionNombre (CADENA_MFC);

pNombre3->setNombre("Ana Blanco");
cout << pNombre3->getNombre() << endl;

delete pNombre3;
}

6.4.4 Estrategia

Problema: ¿Cómo diseñar diversos algoritmos o políticas que están


relacionadas? ¿Cómo diseñar que estos algoritmos o políticas se puedan variar
dinámicamente?

Solución: Defina cada algoritmo/política/estrategia en una clase independiente,


con una interfaz común.

Este patrón define una familia de algoritmos relacionados, los encapsula y los
hace intercambiables. La consecuencia de esta estructura es la variación dinámica de los
algoritmos sin que los clientes se vean afectados. Los roles de este patrón son:

 Estrategia: declara una interfaz común a todos los algoritmos permitidos.

 Estrategia concreta: implementa el algoritmo concreto usando la interfaz


Estrategia.

194 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

 Contexto: este objeto usa la interfaz Estrategia para llamar al algoritmo concreto
definido por una Estrategia Concreta. Tiene como atributo a un objeto de
EstrategiaConcreta, a través de su interfaz.

<<interface>>
Contexto
IEstrategia

EstrategiaConcretaB
EstrategiaConcretaA

Un objeto estrategia se agrega a un objeto de contexto, esto es, el objeto


contexto necesita tener visibilidad de atributo de su estrategia. Además es habitual que
el objeto contexto pase una referencia de él mismo al objeto estrategia, de manera que la
estrategia tenga visibilidad de parámetro de objeto contexto, para futuras
colaboraciones.

Al existir diferentes algoritmos que cambian con el tiempo, la pregunta sería:


¿quién debería de crear la estrategia?. Un enfoque directo es aplicar, de nuevo, el patrón
Factoría. Una Factoría de Estrategias debe ser responsable de crear todas las estrategias
(algoritmos o políticas conectables o cambiantes). Con este diseño, uno puede cambiar
dinámicamente, mientras se está ejecutando la aplicación, la política de actuación.

Como con la mayoría de las factorías, será construido empleando un Singleton y


se accederá mediante el patrón Singleton.

Con los patrones Estrategia y Factoría se ha conseguido Variaciones Protegidas


con respecto a las políticas que varían dinámicamente. La Estrategia se fundamenta en
el Polimorfismo y en las intefaces para permitir algoritmos conectables en un diseño de
objetos.

Ejemplo 6.18

Siguiendo el Proceso
Unificado, diseñe una
aplicación que sea un
conversor de monedas. En
una primera versión inicial,
considere sólo euros, dólares y libras esterlinas. A la aplicación se le facilitará
los valores de conversión entre las monedas y la cantidad de una moneda
concreta a convertir en el resto de monedas.

Dpto. Electrónica, Automática e Informática Industrial 195


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

void CConversorMonedasDlg::OnCalcular()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
ConversorMoneda elConversor(this->m_factorDolar,this->m_factorLibra,
this->m_elTipo,this->m_Cantidad);
this->m_Euros = elConversor.getEuros();
this->m_Dolares = elConversor.getDolar();
this->m_Libras = elConversor.getLibras();
UpdateData(FALSE);

Solución

ConversorMoneda
<<interface>>
convertirDolarAEuro() IMoneda
<<abstract>> getEuros() <<typedef>>
convertirLibraAEuro()
<<abstract>> getDolar() tipoMoneda
ConversorMoneda()
1 1
getEuros() <<abstract>> getLibras()
getDolar() <<static>> MetodoFabricacion()
getLibras()

MonedaLibra
cantidad : double

MonedaLibra()
<<virtual>> getEuros()
MonedaEuro MonedaDolar <<virtual>> getDolar()
cantidad : double <<virtual>> getLibras()
cantidad : double

MonedaEuro() MonedaDolar()
<<virtual>> getEuros() <<virtual>> getEuros()
<<virtual>> getDolar() <<virtual>> getDolar()
<<virtual>> getLibras() <<virtual>> getLibras()

196 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

typedef enum {EURO,DOLAR,LIBRA} tipoMoneda;


class ConversorMoneda;
class IMoneda
{
public:
virtual double getEuros() = 0;
virtual double getDolar() = 0;
virtual double getLibras() = 0;
static IMoneda* MetodoFabricacion(tipoMoneda,double,ConversorMoneda *);
};
class ConversorMoneda
{
IMoneda *pMoneda; double coefDolar; double coefLibra;
public:
double convertirDolarAEuro() {return 1/coefDolar;}
double convertirLibraAEuro() {return 1/coefLibra;}
ConversorMoneda(double dolar, double libra, tipoMoneda elTipo, double cant):
coefDolar(dolar), coefLibra(libra)
{
pMoneda = IMoneda::MetodoFabricacion(elTipo, cant, this);
}
double getEuros() {return pMoneda->getEuros(); }
double getDolar() {return pMoneda->getDolar(); }
double getLibras(){return pMoneda->getLibras(); }
~ConversorMoneda()
{ delete pMoneda; }
};

class MonedaEuro: public IMoneda


{
double cantidad; ConversorMoneda *pConversor;
MonedaEuro(double valor, ConversorMoneda *pConver):
cantidad(valor), pConversor(pConver) {}
friend class IMoneda;
public:
virtual double getEuros() {return cantidad;}
virtual double getDolar()
{return cantidad/this->pConversor->convertirDolarAEuro();}
virtual double getLibras()
{return cantidad/this->pConversor->convertirLibraAEuro();}
};

class MonedaDolar: public IMoneda


{
double cantidad; ConversorMoneda *pConversor;
MonedaDolar(double valor, ConversorMoneda *pConver):
cantidad(valor), pConversor(pConver) {}
friend class IMoneda;
public:
virtual double getEuros()
{return cantidad*this->pConversor->convertirDolarAEuro();}
virtual double getDolar() {return cantidad;}
virtual double getLibras()
{return cantidad*this->pConversor->convertirDolarAEuro()/
this->pConversor->convertirLibraAEuro();}
};
class MonedaLibra: public IMoneda
{
double cantidad; ConversorMoneda *pConversor;;
MonedaLibra(double valor,ConversorMoneda *pConver):
cantidad(valor), pConversor(pConver) {}
friend class IMoneda;
public:
virtual double getEuros()
{return cantidad*this->pConversor->convertirLibraAEuro();}
virtual double getDolar()
{return cantidad*this->pConversor->convertirLibraAEuro()/
this->pConversor->convertirDolarAEuro();}
virtual double getLibras() {return cantidad;}
};

#endif

Dpto. Electrónica, Automática e Informática Industrial 197


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

6.4.5 Observador

Problema: Diferentes tipos de objetos receptores subscritores están interesados


en el cambio de estado o eventos de un objeto emisor y quieren reaccionar cada uno a su
manera cuando el emisor genere un evento. Además, el emisor quiere mantener bajo
acoplamiento con los subscriptores ¿Qué hacer?.

Solución: Defina una interfaz “subscriptor” u “oyente”. Los subscriptores


implementan esta interfaz. El emisor dinámicamente puede registrar subscriptores que
están interesados en un evento y notificarles cuando ocurre un evento.

Este patrón define una dependencia de uno-a-muchos entre objetos, de forma


que cuando un objeto cambie de estado se notifique y se actualicen automáticamente a
todos los objetos que dependen de él. También es conocido como Dependents
(Dependientes), Publish-Subscribe (Publicar-Suscribir).

Una consecuencia habitual de dividir un sistema en una colección de clases


cooperantes es la necesidad de mantener una consistencia entre los objetos relacionados.
No se desea alcanzar esa consistencia haciendo a las clases fuertemente acopladas, ya
que reduciría su reutilización.

Por ejemplo, muchos interfaces gráficas de usuario separan los aspectos de


presentación de la interfaz de usuario del dominio del problema. Las clases que definen
los datos de las aplicaciones y las representaciones pueden utilizarse de forma
independiente. Así en un objeto hoja de cálculo y un gráfico de barras pueden
representar la información contenida en el mismo objeto de datos del dominio. La hoja
de cálculo y el gráfico de barras no se conocen entre sí, permitiéndose de esta manera
reutilizarse, pero gracias a este patrón se comportan como si se conocieran. Cuando el
usuario cambia la información de la hoja de cálculo, la barra de gráfica refleja los
cambios inmediatamente y viceversa.

¿Por qué no se puede mandar un mensaje desde el dominio a la vista?. Esta


solución supone que los objetos del dominio deben saber el interfaz de la vista, por
tanto, se rompería el principio de Bajo Acoplamiento.

La separación Modelo-Vista mantiene Variaciones Protegidas con respecto a los


cambios en la interfaz de usuario.

El patrón Observador describe cómo establecer estas relaciones. Los principales


objetos de este patrón son el Observable y el Observador. Un Observable puede tener
cualquier número de observadores dependientes de él. Cada vez que el Observable
cambia de estado se notifica a sus observadores. En respuesta, cada observador
consultará al Observable para sincronizar su estado con el estado de éste.

198 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

Este tipo de interacción también se conoce como publicar-suscribir. El


Observable es quien publica las notificaciones. Envía estas notificaciones sin tener que
conocer quiénes son sus observadores. Pueden suscribirse un número indeterminado de
observadores para recibir notificaciones.

Los roles que se desempeñan en este patrón son:

 Observable
o un Observable puede ser observado por cualquier número de objetos
Observador.
o proporciona una interfaz para asignar y quitar objetos Observador.
 Observador
o define una interfaz para actualizar los objetos que deben ser notificados
ante cambios en un sujeto.
 ObservableConcreto
o almacena el estado de interés para los objetos ObservadorConcreto.
o envía una notificación a sus observadores cuando cambia su estado.
 ObservadorConcreto
o mantiene una referencia a un objeto ObservableConcreto.
o guarda un estado que debería ser consistente con el del sujeto.
o Implementa la interfaz de actualización del Observador para mantener su
estado consistente con el del sujeto.
Así, el ObservableConcreto notifica a sus observadores, cada vez que se produce
un cambio. Después de ser informado de un cambio en el ObservableConcreto, un
objeto ObservadorConcreto puede pedirle al observable más información.
ObservadorConcreto usa esta información para sincronizar su estado con el del
observable.

Observable

<<virtual>> ~Observable()
<<parameter>>
<<interface>> <<virtual>> notifyObservers()
Observer <<virtual>> hasChanged()
<<virtual>> countObservers()
<<virtual>> ~Observer()
<<virtual>> deleteObservers()
<<abstract>> update()
<<virtual>> deleteObserver()
<<virtual>> addObserver()
<<virtual>> clearChanged()
<<virtual>> setChanged()
ObservadorConcreto

SujetoConcreto

Dpto. Electrónica, Automática e Informática Industrial 199


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

El siguiente diagrama de secuencias muestra los métodos entre el emisor y


receptor.

: SujetoConcreto : ObservadorConcreto

addObserver(Observer&)

notifyObservers(Argument*)

update(Observable*, Argument*)

El patrón Observador permite modificar los observables y los observadores de


forma independiente. Es posible reutilizar los observables sin conocer a sus
observadores y viceversa. Esto permite añadir observadores sin modificar al observable
u otros observadores.

Otras ventajas e inconvenientes del patrón Observador son los siguientes:

1. Acoplamiento abstracto entre Observable y Observador. Todo lo que un


Observable sabe es que tiene una lista de observadores, cada uno de los cuales se ajusta
a la interfaz simple de la clase abstracta al Observador. El Observable no conoce la
clase concreta de ningún observador. Por tanto el acoplamiento entre sujetos y
observador es mínimo (patrón GRASP de Bajo Acoplamiento) .

Gracias a que Observable y Observador no están fuertemente acoplados, pueden


pertenecer a diferentes capas de abstracción de un sistema. Un sujeto de bajo nivel
puede comunicarse e informar a un observador de más alto nivel, manteniendo de este
modo intacto la estructura de capas del sistema (patrón Capas de POSA).

2. Capacidad de comunicación mediante difusión. A diferencia de una petición


ordinaria, la notificación enviada por un Observable no necesita especificar su receptor
(patrón GRASP de Alta Cohesión). La notificación se envía automáticamente a todos
los objetos interesados que se hayan suscrito a ella. Al Observable no le importa cuántos
objetos interesados haya; su única responsabilidad es notificar a los observadores. Esto
da la libertad de añadir y quitar observadores en cualquier momento. Se deja al
observador manejar u obviar una notificación.

3. Actualizaciones inesperadas. El mayor inconveniente de este patrón es que los


observadores no saben de la presencia de los otros observadores y no pueden saber el
coste último de cambiar el estado. Una operación aparentemente inofensiva sobre el
Observable puede dar lugar a una serie de actualizaciones en cascada de los

200 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

observadores y sus objetos dependientes. Más aún, los criterios de dependencia que no
están bien definidos o mantenidos suelen provocar falsas actualizaciones que pueden ser
muy difíciles de localizar.

6.4.5.1 Implementación
Dos tipos de objetos se utilizan para poner el patrón del observador. La clase
Observable que se encarga de que el estado del Observable concreto sea escuchado por
sus observadores y la clase Observer que está a la escucha. El patrón del Observer
permite que se modifique desde fuera el código y luego se le indica al patrón del
Observer, en ejecución, que haga las correspondientes actualizaciones. La clase
Observable llama a la función miembro notifyObservers() para cada observador en la
lista de observadores a la escucha.
Primero, se presenta el Observer:
// From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison.
// (c) 1995-2004 MindView, Inc. All Rights Reserved.
//
//: C10:Observer.h
// The Observer interface.
#ifndef OBSERVER_H
#define OBSERVER_H
class Observable;
class Argument {};
class Observer {
public:
// Called by the observed object, whenever
// the observed object is changed:
virtual void update(Observable* o, Argument* arg) = 0;
virtual ~Observer() {}
};
#endif // OBSERVER_H ///:~

Puesto que el Observer obra recíprocamente con Observable, el Observable se


debe declarar primero. Además, la clase de la discusión es virtual y es una superclase
empleada para cualquier tipo de atributos que se desean pasar durante una actualización.
La clase Argument, declarada en este ejemplo como vacía, será la que defina los
atributos que van a estar a la escucha. El servicio update() es llamada por el objeto que
está en la escucha cuando se produce una notificación del emisor.

La clase Observable mantiene la lista de objetos que desean estar a la escucha de


las modificaciones que produce el emisor.
// From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison.
// (c) 1995-2004 MindView, Inc. All Rights Reserved.
//
//: C10:Observable.h
// The Observable class.
#ifndef OBSERVABLE_H
#define OBSERVABLE_H
#include <set>
#include "Observer.h"

class Observable {
bool changed;
std::set<Observer*> observers;
protected:
virtual void setChanged() { changed = true; }
virtual void clearChanged() { changed = false; }
public:
virtual void addObserver(Observer& o) {
observers.insert(&o);

Dpto. Electrónica, Automática e Informática Industrial 201


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

}
virtual void deleteObserver(Observer& o) {
observers.erase(&o);
}
virtual void deleteObservers() {
observers.clear();
}
virtual int countObservers() {
return observers.size();
}
virtual bool hasChanged() { return changed; }
// If this object has changed, notify all
// of its observers:
virtual void notifyObservers(Argument* arg = 0) {
if(!hasChanged()) return;
clearChanged(); // Not "changed" anymore
std::set<Observer*>::iterator it;
for(it = observers.begin();it != observers.end(); it++)
(*it)->update(this, arg);
}
virtual ~Observable() {}
};
#endif // OBSERVABLE_H ///:~

El objeto Observable tiene un flag para indicar si ha cambiado el estado de los


atributos a escuchar. En un diseño más simple, no habría indicador; si sucedió algo,
cada uno sería notificado.

La colección de objetos del observador se mantiene en un lista STL


set<Observer*> para prevenir los duplicados; los servicios de insert(), erase(), clear(),
y size() se exponen para permitir que se agreguen y se quiten a los observadores en
cualquier momento, proporcionando flexibilidad en tiempo de ejecución. La mayoría
del trabajo se hace en notifyObservers(). Mientras no se produzcan cambios, las
llamadas repetidas al notifyObservers() no hacen nada y no pierden el tiempo. Cuando
hay un cambio en los atributos del emisor se produce la llamada al update() de todos los
observadores que estén en la lista.

6.4.5.2 Las clases internas

Las clases que están a la escucha requieren de una interfaz que las permitan
tener la propiedad de ser observables. Para este fin se requiere de algo que tiene Java y
no C++, las clases internas. Se trata de jerarquizar las clases de forma que se puedan
tener acceso a los datos no estático de la clase que las contiene implícitamente, esto es,
una instancia de la clase interna tenga acceso a datos de la clase que la ha creado. Se
presenta un ejemplo del idioma interno:
// From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison.
// (c) 1995-2004 MindView, Inc. All Rights Reserved.
//
//: C10:InnerClassIdiom.cpp
// Example of the "inner class" idiom.
#include <iostream>
#include <string>
using namespace std;

class Poingable {
public:
virtual void poing() = 0;
};
void callPoing(Poingable& p) {
p.poing();
}
class Bingable {
public:
virtual void bing() = 0;
};
void callBing(Bingable& b) {

202 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

b.bing();
}
class Outer {
string name;
// Define one inner class:
class Inner1;
friend class Outer::Inner1;
class Inner1 : public Poingable {
Outer* parent;
public:
Inner1(Outer* p) : parent(p) {}
void poing() {
cout << "poing called for "
<< parent->name << endl;
// Accesses data in the outer class object
}
} inner1;
// Define a second inner class:
class Inner2;
friend class Outer::Inner2;
class Inner2 : public Bingable {
Outer* parent;
public:
Inner2(Outer* p) : parent(p) {}
void bing() {
cout << "bing called for "
<< parent->name << endl;
}
} inner2;
public:
Outer(const string& nm)
: name(nm), inner1(this), inner2(this) {}
// Return reference to interfaces
// implemented by the inner classes:
operator Poingable&() { return inner1; }
operator Bingable&() { return inner2; }
};

int main() {
Outer x("Ping Pong");
// Like upcasting to multiple base types!:
callPoing(x);
callBing(x);
} ///:~

El ejemplo, previsto para demostrar la sintaxis más simple para el idioma


interno, comienza con los interfaces de Poingable y de Bingable, cada uno contiene
una sola función del miembro. Los servicios de callPoing() y de callBing() requieren
que el objeto entregue los interfaces de Poingable y de Bingable, respectivamente. La
clase Outer contiene ciertos datos confidenciales (nombre), y desea proporcionar un
interfaz de Poingable y un interfaz de Bingable, así que puede ser utilizado con el
servicio callPoing() y con callBing(). Para proporcionar un objeto de Poingable sin
derivar de Poingable, se utiliza el idioma interno de la clase. Primero, la clase interna se
declara y luego se la declara friend de la clase que la contiene, generándose la jerarquía
de clases. Note que la clase interna guarda un indicador de quien la creó, y este
indicador se debe inicializar en el constructor. El cual servirá para tener acceso a los
atributos de la clase “padre”.

6.4.5.3 Entre flores, abejas y colibríes


Armado con el observador, los archivos de observables y el idioma interno de la
clase, ahora podemos mirar un ejemplo del patrón del observador:

Dpto. Electrónica, Automática e Informática Industrial 203


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

// From "Thinking in C++, Volume 2", by Bruce Eckel & Chuck Allison.
// (c) 1995-2004 MindView, Inc. All Rights Reserved.
//
//: C10:ObservedFlower.cpp
// Demonstration of "observer" pattern.
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
#include "Observable.h"
using namespace std;

class Flower {
bool isOpen;
public:
Flower() : isOpen(false),
openNotifier(this), closeNotifier(this) {}
void open() { // Opens its petals
isOpen = true;
openNotifier.notifyObservers();
closeNotifier.open();
}
void close() { // Closes its petals
isOpen = false;
closeNotifier.notifyObservers();
openNotifier.close();
}
// Using the "inner class" idiom:
class OpenNotifier;
friend class Flower::OpenNotifier;
class OpenNotifier : public Observable {
Flower* parent;
bool alreadyOpen;
public:
OpenNotifier(Flower* f) : parent(f),
alreadyOpen(false) {}
void notifyObservers(Argument* arg = 0) {
if(parent->isOpen && !alreadyOpen) {
setChanged();
Observable::notifyObservers();
alreadyOpen = true;
}
}
void close() { alreadyOpen = false; }
} openNotifier;
class CloseNotifier;
friend class Flower::CloseNotifier;
class CloseNotifier : public Observable {
Flower* parent;
bool alreadyClosed;
public:
CloseNotifier(Flower* f) : parent(f),
alreadyClosed(false) {}
void notifyObservers(Argument* arg = 0) {
if(!parent->isOpen && !alreadyClosed) {
setChanged();
Observable::notifyObservers();
alreadyClosed = true;
}
}
void open() { alreadyClosed = false; }
} closeNotifier;
};
class Bee {
string name;
// An "inner class" for observing openings:
class OpenObserver;
friend class Bee::OpenObserver;
class OpenObserver : public Observer {
Bee* parent;
public:
OpenObserver(Bee* b) : parent(b) {}
void update(Observable*, Argument *) {
cout << "Bee " << parent->name
<< "'s breakfast time!” << endl;
}
} openObsrv;
// Another "inner class" for closings:
class CloseObserver;
friend class Bee::CloseObserver;
class CloseObserver : public Observer {
Bee* parent;
public:
CloseObserver(Bee* b) : parent(b) {}
void update(Observable*, Argument *) {
cout << "Bee " << parent->name

204 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

<< "'s bed time!” << endl;


}
} closeObsrv;
public:
Bee(string nm) : name(nm),
openObsrv(this), closeObsrv(this) {}
Observer& openObserver() { return openObsrv; }
Observer& closeObserver() { return closeObsrv;}
};
class Hummingbird {
string name;
class OpenObserver;
friend class Hummingbird::OpenObserver;
class OpenObserver : public Observer {
Hummingbird* parent;
public:
OpenObserver(Hummingbird* h) : parent(h) {}
void update(Observable*, Argument *) {
cout << "Hummingbird " << parent->name
<< "'s breakfast time!” << endl;
}
} openObsrv;
class CloseObserver;
friend class Hummingbird::CloseObserver;
class CloseObserver : public Observer {
Hummingbird* parent;
public:
CloseObserver(Hummingbird* h) : parent(h) {}
void update(Observable*, Argument *) {
cout << "Hummingbird " << parent->name
<< "'s bed time!” << endl;
}
} closeObsrv;
public:
Hummingbird(string nm) : name(nm),
openObsrv(this), closeObsrv(this) {}
Observer& openObserver() { return openObsrv; }
Observer& closeObserver() { return closeObsrv;}
};
int main() {
Flower f;
Bee ba("A"), bb("B");
Hummingbird ha("A"), hb("B");
f.openNotifier.addObserver(ha.openObserver());
f.openNotifier.addObserver(hb.openObserver());
f.openNotifier.addObserver(ba.openObserver());
f.openNotifier.addObserver(bb.openObserver());
f.closeNotifier.addObserver(ha.closeObserver());
f.closeNotifier.addObserver(hb.closeObserver());
f.closeNotifier.addObserver(ba.closeObserver());
f.closeNotifier.addObserver(bb.closeObserver());
// Hummingbird B decides to sleep in:
f.openNotifier.deleteObserver(hb.openObserver());
// Something changes that interests observers:
f.open();
f.open(); // It's already open, no change.
// Bee A doesn't want to go to bed:
f.closeNotifier.deleteObserver(
ba.closeObserver());
f.close();
f.close(); // It's already closed; no change
f.openNotifier.deleteObservers();
f.open();
f.close();
} ///:~

Propósito Patrones de diseño GoF Tema que trata

Dpto. Electrónica, Automática e Informática Industrial 205


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

De creación Factoría Abstracta Uso de familias de referencias abstractas,


ocultado las implementaciones concretas.
Método de Fabricación
Creación de las clases concretas.
Singleton
la única instancia de una clase

Estructurales Adaptador la interfaz de un objeto

Fachada la interfaz de un subsistema

De comportamiento Observador cómo se mantiene actualizado el objeto


dependiente
Estrategia
cómo se varía dinámicamente un algoritmo

6.5 Ejercicios

1. Habilidades necesarias para el diseño orientado a objetos.

2. Tipos de visibilidad en la programación orientada a objetos.

3. ¿Qué es la Programación Extrema, XP?.

4. ¿Qué se entiende por patrón en el diseño orientado a objetos?.

5. Describir el par problema/solución de los patrones GRASP y GoF


estudiados.

Problema 1

Realizar el primer diseño para


el simulador del sistema de control
de elevación de una aeronave (ver
este problema en los ejercicios de
los capítulos 2 y 3).

Un modelo simplificado del


control de elevación en transformadas
en Z está dado por la siguiente función
de transferencia:

0.0895 z − 0.085
M (z ) = 2
z − 1.883 z + 0.888

206 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

El periodo de muestreo es de 25ms. Por tanto, si la señal de mando se la denota


por δk y el ángulo de elevación de la aeronave por αk. El algoritmo de control está
definido por la siguiente ecuación en diferencias:

α k = 0.0895δ k − 0.085δ k −1 + 1.883α k −1 − 0.888α k − 2

Como prueba de concepto se puede suponer una entrada en escalón unitario. La


dinámica de elevación de la aeronave seguirá la siguiente trayectoria:

k δk δk-1 αk αk-1 αk-2


0 1 0 0.0895 0 0
1 1 1 0.173 0.0895 0
2 1 1 0.25 0.173 0.0895
3 1 1 0.323 0.25 0.173
Empleando Matlab, la solución quedaría como:

>> g1=tf([.0895 -.085],[1 -1.883 .888],.025)

>>step(g1)

Una vez realizado las pruebas de conceptos se pasa al


modelo del dominio y al DSS:

Simulador_LTI
tiene definido el sistema mediante un tiempoFinal
ModeloSistema
periodoMuestreo
valoresSalida

esta definido por

estimula al sistem a con


FDT
grado : unsigned
Senyal_Entrada
elTipoEntrada

getValorInstantek()

Dpto. Electrónica, Automática e Informática Industrial 207


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

: Sistema
Ingeniero de Control
: <Actor Name>
Se le pasará los
coeficientes de
la FDT del
introducirModelo() sistema

especificarExcitacion()

Se le dirá cuál es
mostrarSalida() el tipo de señal,
el tiempo final y
el intervalo...

La aplicación dará
los resultados en un
gráfico del tipo y=y(t)

Aplicando los patrones de Alta Cohesión, Bajo Acoplamiento y Modularidad-Capas, la


vista de gestión de la aplicación estaría formada por los siguientes paquetes:

VistaSimulador

DominioSimulador

Patrón Alta
GraficoPlot Cohesion, Bajo
Acoplamiento,
Capa

El diagrama de interacción y el DCD del paquete del dominio estará constituido


por:

208 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

Patrón
Fachada
: Vista : CoordinadorSimulador
Patrón
Creador
introducirModelo( )

create() Patrón Experto


: ModeloSistema
en Información

especificarExcitacion( )

create()
: Senyal_Entrada

create()
: Simulador_LTI

Algoritmia para
calcularSalida( )
calcular la señal
de salida getCoefNum( )

getCoefDen( )

getValorInstantek( )

mostrarSalida( )

getSenyaSalida( )

ModeloSistema
(f rom Analy sis Model)
FDT
(f rom Analy sis Model)
CoordinadorSimulador grado : unsigned
(f rom Design Model) getCoefNum()
getCoefDen()
1
introducirModelo()
especificarExcitacion()
mostrarSalida()
notificarResultados()
Senyal_Entrada
(f rom Analy sis Model) 2
elTipoEntrada Polinomio
(f rom Analy sis Model)
getValorInstantek() coeficientes : std::vector<double>

Simulador_LTI
(f rom Analy sis Model)

tiempoFinal
periodoMuestreo
valoresSalida

calcularSalida()
getSenyaSalida()

Problema 2

Dpto. Electrónica, Automática e Informática Industrial 209


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

Para el código adjuntado se pide:

a. Ingeniería inversa: Diagrama de clases.

b. Ingeniería inversa: Diagrama de secuencia.

c. Resultado de su ejecución en la consola.

d. Indicar los patrones GRASP empleados en este patrón.

e. Diseñar e implementar el servicio rotar( ), tal que

 x2   cos (θ ) − sin (θ )   x1 
 =  
 y2   sin (θ ) cos (θ )   y1 

Empléese sobre el punto p2.

#include <iostream>
#include <string>
#include <cmath>
using namespace std;

class Punto {
public:
double x, y;
Punto(double xi, double yi) : x(xi), y(yi) {}
Punto(const Punto& p) : x(p.x), y(p.y) {}
Punto& operator=(const Punto& rhs) {
x = rhs.x;
y = rhs.y;
return *this;
}
friend ostream&
operator<<(ostream& os, const Punto& p) {
return os << "x=" << p.x << " y=" << p.y;
}
};

class Vector {
public:
double magnitud, direccion;
Vector(double m, double d) : magnitud(m), direccion(d) {}
};

class Espacio {
public:
static Punto trasladar(Punto p, Vector v) {
p.x += (v.magnitud * cos(v.direccion));
p.y += (v.magnitud * sin(v.direccion));
return p;
}
};

int main() {
Punto p1(1, 2);
Punto p2 = Espacio::trasladar(p1, Vector(3, 3.1416/3));
cout << "p1: " << p1 << " p2: " << p2 << endl;

return 0;
}

a) Diagrama de clases de la ingeniería inversa

210 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

Espacio

<<static>> trasladar(p : Punto, v : Vector) : Punto

Punto
x
y Vector
magnitud
Punto(xi : double, yi : double)
direccion
Punto(p : const Punto&)
operator=(rhs : const Punto&) : Punto&
Vector(m : double, d : double)
<<friend>> operator<<(os : ostream&, p : const Punto&) : ostream&

b) Diagrama de secuencia de la ingeniería inversa

: main : Espacio

Punto(double, double) p1 : Punto

Vector(double, double) : Vector

trasladar(Punto, Vector)

Punto(const Punto&) p2 : Punto

c) p1: x=1 y=2 p2: x=2.5 y=4.6

d) Se ha aplicado Experto de Información en la clase Punto y Vector. Para evitar


el acoplamiento entre ambas clases se ha aplicado el patrón Indirección y por tanto una
Fabricación Pura con la clase Espacio.

e)

Dpto. Electrónica, Automática e Informática Industrial 211


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

class Espacio {
public:
static Punto trasladar(Punto p, Vector v) {
p.x += (v.magnitud * cos(v.direccion));
p.y += (v.magnitud * sin(v.direccion));
return p;
}
static Punto rotar(Punto p, double theta) {
Punto res(0,0);
res.x = (p.x * cos(theta)) - (p.y *sin(theta));
res.y = (p.x * sin(theta)) + (p.y *cos(theta));
return res;
}
};

int main() {
Punto p1(1, 2);
Punto p2 = Espacio::trasladar(p1, Vector(3, 3.1416/3));
Punto p3 = Espacio::rotar(p2,3.1416/6);

cout << "p1: " << p1 << " p2: " << p2 << " p3: " << p3 <<endl;

return 0;
}

212 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

Problema 3

En el cuadro se
entrega el código sobre un
generador de números
primos. Se trata de diseñar
un componente tal que el
cliente le entregue el
número límite de números
primos, n, y el servidor
retorne con un vector que
contenga los n primeros
números primos. En la
figura se presenta el
resultado del cliente. Se

#ifndef NUMEROSPRIMOS_H
#define NUMEROSPRIMOS_H

class NumerosPrimos {
unsigned elUltimoPrimo;
unsigned elNumero;
public:
NumerosPrimos() : elUltimoPrimo(1) { elNumero = elUltimoPrimo+1;}
unsigned getSiguientePrimo()
{
do{
for(unsigned divisor = 2;elNumero % divisor != 0; divisor++) ;
if (divisor == elNumero)
elUltimoPrimo = elNumero;
else
elNumero++;
} while ( elUltimoPrimo != elNumero );
elNumero++;
return (elUltimoPrimo);
}

};
#endif

pide:
1. Realizar ingeniería inversa sobre el generador de números primos.
2. Obtener el diagrama de clase de diseño, DCD, así como el diagrama de
secuencia del componente servidor.
3. Implementación del cliente en C++.
4. Implementación de las clases en C++ del componente servidor.

NumerosPrimos
elUltimoPrimo : unsigned
elNumero : unsigned

NumerosPrimos()
getSiguientePrimo() : unsigned

Dpto. Electrónica, Automática e Informática Industrial 213


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

<<interface>>
Cliente IAdaptadorNumerosPrimos
<<abstract>> getNumerosPrimos()
<<static>> metodoFabricacion()

NumerosPrim os
AdaptadorNumerosPrimos
elUltimoPrimo : unsigned
elNumero : unsigned
AdaptadorNumerosPrimos()
<<virtual>> getNumerosPrimos()
NumerosPrimos()
AdaptadorNumerosPrimos()
getSiguientePrimo() : unsigned

: Cliente

metodoFabricacion(unsigned)
: IAdaptadorNumerosPrim os

AdaptadorNumerosPrimos( )
: AdaptadorNumerosPrimos : NumerosPrimos

getNumerosPrimos( )

getNumerosPrimos( )

getSiguientePrimo( )

214 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

#include <iostream>
#include "AdaptadorNumerosPrimos.h"
#include <numeric>
#include <algorithm>

IAdaptadorNumerosPrimos* IAdaptadorNumerosPrimos::metodoFabricacion(unsigned limite)


{
return (new AdaptadorNumerosPrimos(limite));

}
using namespace std;
void imprimir(unsigned);

int main()
{
unsigned elLimiteNumerosPrimos;
cout<<"Cuantos numeros primos desea que aparezcan : ";
cin >> elLimiteNumerosPrimos;

IAdaptadorNumerosPrimos *pAdaptadorNumPrimos =
IAdaptadorNumerosPrimos::metodoFabricacion(elLimiteNumerosPrimos);

cout << endl <<"Tabla de Numeros Primos" <<endl;


cout << "-----------------------" <<endl;

for_each(pAdaptadorNumPrimos->getNumerosPrimos().begin(),
pAdaptadorNumPrimos->getNumerosPrimos().end(),imprimir);
delete pAdaptadorNumPrimos;
return 0;
}

void imprimir(unsigned numPrimo)


{
static unsigned indice;
cout.width(5);
cout << numPrimo;
if(++indice % 10 == 0)
cout << endl;
}

#ifndef _ADAPTADOR_NUMEROSPRIMOS
#define _ADAPTADOR_NUMEROSPRIMOS

#include <vector>
#include "NumerosPrimos.h"

class IAdaptadorNumerosPrimos
{
public:
virtual std::vector<unsigned> & getNumerosPrimos() = 0;
static IAdaptadorNumerosPrimos *metodoFabricacion(unsigned);
};

class AdaptadorNumerosPrimos: public IAdaptadorNumerosPrimos


{
NumerosPrimos elGenerador;
unsigned elLimite;
std::vector<unsigned> elVectorNumerosPrimos;
friend class IAdaptadorNumerosPrimos;
AdaptadorNumerosPrimos(unsigned num): elLimite(num)
{
for (unsigned i=1;i<=elLimite;i++)
elVectorNumerosPrimos.push_back
(elGenerador.getSiguientePrimo());
}
public:
virtual std::vector<unsigned> & getNumerosPrimos()
{return elVectorNumerosPrimos;}
};

#endif

Dpto. Electrónica, Automática e Informática Industrial 215


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

Problema 4

Para la aplicación informática de una sala de cine, se desea que un


taquillero introduzca el número de localidades que desea un comprador y se le
aplica el mejor descuento para el espectador. En esta primera versión se
considera que las monedas pueden ser EUROS, DOLARES y LIBRAS. Por
defecto, se considerará que el dinero se expresa en EUROS y el precio de
butaca es de 5€. Las políticas de ventas a introducir, en esta primera iteración,
son:
a) Si el día de la semana es miércoles se le aplicará un descuento del
50%.
b) En cualquier día de la semana, la compra de tres entradas será al
precio de dos. Esta política se aplica para número de localidades que sea
múltiplo de tres.
El arquitecto del programa ha realizado un primer borrador del diagrama
de clase de diseño. Obviamente no está toda la información para la
codificación.

: Vista

VentaLocalidades(Dinero, float) :
VentaLocalidades

getPrecio(int)

metodoFabricacionPrecios(Fecha&, float) : IPrecioLocalidades

getPrecioTotal(int, Dinero)

216 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

VentaLocalidades
<<interface>>
Vista elDescuento : float IPrecioLocalidades
<<abstract>> getPrecioTotal()
VentaLocalidades()
<<static>> metodoFabricacionPrecios()
getPrecio()

DiaDelEspectador
DosXTres
DiaDelEspectador()
<<virtual>> getPrecioTotal() DosXTres()
Fecha <<virtual>> getPrecioTotal()

isMiercoles()

Dinero <<typedef>>
cantidad : float TipoDinero
elTipoMoneda : TipoDinero

Dinero()
Dinero()
Dinero()
operator=()
setCantidad()
getCantidad()
setTipoDinero()
getTipoDinero()

1. Implementar la clase Dinero (por defecto se considera que el tipo de


moneda es el EURO).

Dinero
cantidad : float
elTipoMoneda : TipoDinero

Dinero()
Dinero(valor : float, elTipo : TipoDinero)
Dinero(elValor : Dinero&)
operator=(elValor : Dinero&) : Dinero&
setCantidad(laCantidad : float) : void
getCantidad( : void) : float
setTipoDinero(elTipo : TipoDinero) : void
getTipoDinero( : void) : TipoDinero

2. Para las estrategias de los precios se ha utilizado un interfaz, de


manera que las políticas de ventas se puedan variar en el futuro. Realizar su
implementación.

Dpto. Electrónica, Automática e Informática Industrial 217


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

<<interface>>
IPrecioLocalidades
<<abstract>> getPrecioTotal(numeroLocalidades : int, elPrecioUnitario : Dinero) : Dinero
<<static>> metodoFabricacionPrecios(hoy : Fecha&, descuento : float) : IPrecioLocalidades*

DiaDelEspectador
elDescuento : float

DiaDelEspectador(descuento : float)
<<virtual>> getPrecioTotal(numeroLocalidades : int, elPrecioUnitario : Dinero) : Dinero

DosXTres

DosXTres()
<<virtual>> getPrecioTotal(numeroLocalidades : int, elPrecioUnitario : Dinero) : Dinero

3. Implementar la clase de Venta de Localidades

VentaLocalidades
elDescuento : float
elDiaSemana : Fecha
elPrecioUnitario : Dinero
pPrecioLocalidades : IPrecioLocalidades *

VentaLocalidades(elPrecio : Dinero, descuento : float)


getPrecio(numeroLocalidades : int) : Dinero

218 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

#ifndef _DINERO_INC_
#define _DINERO_INC_

typedef enum {EURO, DOLAR, LIBRA} TipoDinero;

class Dinero
{
TipoDinero elTipoMoneda;
float cantidad;
public:
Dinero(): elTipoMoneda(EURO), cantidad(0) {}
Dinero(float valor, TipoDinero elTipo): elTipoMoneda(elTipo),cantidad(valor) {}

Dinero(Dinero &elValor)
{
elTipoMoneda = elValor.elTipoMoneda;
cantidad = elValor.cantidad;
}

Dinero& operator= (Dinero &elValor)


{
elTipoMoneda = elValor.elTipoMoneda;
cantidad = elValor.cantidad;
return(*this);
}

void setCantidad(float laCantidad){cantidad=laCantidad;}


float getCantidad(void){return cantidad;}
void setTipoDinero(TipoDinero elTipo){elTipoMoneda=elTipo;}
TipoDinero getTipoDinero(void){return elTipoMoneda;}
};

#endif

#ifndef _VENTA_LOCALIDADES_INC_
#define _VENTA_LOCALIDADES_INC_

#include "Fecha.h"
#include "Dinero.h"
#include "OfertasTaquillas.h"

class VentaLocalidades
{
Fecha elDiaSemana;
Dinero elPrecioUnitario;
float elDescuento;
IPrecioLocalidades *pPrecioLocalidades;

public:
VentaLocalidades(Dinero elPrecio, float descuento) :
elPrecioUnitario(elPrecio),elDescuento(descuento) {}
Dinero getPrecio(int numeroLocalidades)
{
pPrecioLocalidades =
IPrecioLocalidades::metodoFabricacionPrecios(elDiaSemana,elDescuento);
Dinero elDinero =
pPrecioLocalidades->getPrecioTotal(numeroLocalidades,elPrecioUnitario) ;
delete pPrecioLocalidades;
return(elDinero);
}
};

#endif

Dpto. Electrónica, Automática e Informática Industrial 219


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

#ifndef _OFERTAS_TAQUILLA_INC_
#define _OFERTAS_TAQUILLA_INC_
#include "Dinero.h"
#include "Fecha.h"

class IPrecioLocalidades
{
public:
virtual Dinero getPrecioTotal(int numeroLocalidades, Dinero elPrecioUnitario) = 0;
static IPrecioLocalidades *metodoFabricacionPrecios(Fecha &, float);
};

class DiaDelEspectador : public IPrecioLocalidades


{
float elDescuento;
friend class IPrecioLocalidades;
DiaDelEspectador(float descuento): elDescuento(descuento) {}
public:
virtual Dinero getPrecioTotal(int numeroLocalidades, Dinero elPrecioUnitario)
{
Dinero elValorTotal;
elValorTotal.setCantidad(elPrecioUnitario.getCantidad()*numeroLocalidades*elDescuento);
elValorTotal.setTipoDinero(elPrecioUnitario.getTipoDinero());
return (elValorTotal);
}
};

class DosXTres : public IPrecioLocalidades


{
friend class IPrecioLocalidades;
DosXTres() {}
public:
virtual Dinero getPrecioTotal(int numeroLocalidades, Dinero elPrecioUnitario)
{
Dinero elValorTotal;
int multiplosDeTres = 0;
for(int i=1;i<=numeroLocalidades;i++)
if( i%3 == 0)
multiplosDeTres++;
int localidadesSueltas = numeroLocalidades - (multiplosDeTres*3);
elValorTotal.setCantidad(elPrecioUnitario.getCantidad()*
(localidadesSueltas+(multiplosDeTres*2)));
elValorTotal.setTipoDinero(elPrecioUnitario.getTipoDinero());
return (elValorTotal);
}
};

#endif

#include "includes/VentaLocalidades.h"

IPrecioLocalidades * IPrecioLocalidades::metodoFabricacionPrecios
(Fecha &hoy, float descuento = 0)
{
if ((hoy.isMiercoles() == true ) && (descuento > 100/3) )
return new DiaDelEspectador(descuento);
else return new DosXTres;
}

220 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

Problema 5

Desarrollar una aplicación que


convierta las magnitudes de longitud de
un sistema a otro, sabiendo que:
1 milla = 1609 m
1 pulgada = 25.4 mm
1 pie = 30.48 cm
Se pide:
1. AOO: Modelo del dominio.
2. DOO: Diagrama de clases de diseño. Indicar los patrones que se están
aplicando.
3. Implementación en C++.

Convierte de
un tipo de
longitud a otro Longitudes
(from ConversorLongitudes)

es un tipo de medida de
ConversorLongitud
(from ConversorLongitudes)
Pies
es un tipo de medida de (from ConversorLongitudes)

es un tipo de medida de
es un tipo de medida de
Pulgadas
(from ConversorLongitudes)

Metros
(from ConversorLongitudes)
Millas
(from ConversorLongitudes)

ConversorLongitud <<interface>>
millaMetro : float ILongitudes
pulgadaMetro : float <<abstract>> getMetros()
pieMetro : float <<abstract>> getMillas()
<<abstract>> getPulgadas()
ConversorLongitud() 1 <<abstract>> getPies()
<<virtual>> ~ConversorLongitud() 1 <<static>> metodoFabricacion()
getMetros()
getMillas()
getPulgadas()
getPies()
Pies
factorMillaMetro()
Metros Millas Pulgadas cantidad : float
factorPulgadaMetro()
factorPieMetro() cantidad : float cantidad : float cantidad : float
Pies()
Millas() Pulgadas() getMetros()
Metros()
getMetros() getMetros() getMillas()
getMetros()
getMillas() getMillas() getPulgadas()
getMillas()
getPulgadas() getPulgadas() getPies()
getPulgadas()
getPies() getPies() getPies()

Dpto. Electrónica, Automática e Informática Industrial 221


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

Los patrones utilizados son: Estrategia(GoF) que incluye Variaciones Protegidas


(GRASP), Método de Fabricación (GoF) y Polimorfismo (GRASP)

El código de test sería:

void CConversorLongitudesDlg::OnCalcular()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);

ConversorLongitud elConversor(this->m_tipoLongitud,this->m_longitud);
this->m_Metros = elConversor.getMetros();
this->m_Millas = elConversor.getMillas();
this->m_Pulgadas = elConversor.getPulgadas();
this->m_Pies = elConversor.getPies();

UpdateData(FALSE);
}

Mientras que el interfaz, el conversor y las subclases de longitudes serían:


#if !defined(AFX_LONGITUDES_H)
#define AFX_LONGITUDES_H

typedef enum {METROS,MILLAS,PULGADAS,PIES} tipoLongitudes;

class ConversorLongitud;
class ILongitudes
{
public:
virtual float getMetros() = 0;
virtual float getMillas() = 0;
virtual float getPulgadas() = 0;
virtual float getPies() = 0;
static ILongitudes * metodoFabricacion(tipoLongitudes,float,
ConversorLongitud *);
};
#endif
--------------------------------------------------------------------------------------------------------------------------------------------------------
#if !defined(AFX_CONVERSORLONGUITUDES_H)
#define AFX_CONVERSORLONGUITUDES_H

#include "Longitudes.h"

class ConversorLongitud
{
ILongitudes *plongitudes;
float millaMetro;
float pulgadaMetro;
float pieMetro;

public:
ConversorLongitud(tipoLongitudes,float);
virtual ~ConversorLongitud();

float getMetros() { return plongitudes->getMetros(); }


float getMillas() { return plongitudes->getMillas(); }
float getPulgadas() { return plongitudes->getPulgadas(); }
float getPies() { return plongitudes->getPies(); }

float factorMillaMetro() {return millaMetro;}


float factorPulgadaMetro() {return pulgadaMetro;}
float factorPieMetro() {return pieMetro;}

};

222 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

#if !defined(AFX_LONGUITUDES_SIS_H)
#define AFX_LONGUITUDES_SIS_H

#include "ConversorLongitud.h"

class Metros : public ILongitudes


{
float cantidad; ConversorLongitud *pConversor;
Metros(float valor,ConversorLongitud *pC):
cantidad(valor), pConversor(pC) {}
friend class ILongitudes;
public:
float getMetros() { return cantidad;}
float getMillas() {return cantidad/pConversor->factorMillaMetro();}
float getPulgadas() {return cantidad/pConversor->factorPulgadaMetro();}
float getPies() {return cantidad/pConversor->factorPieMetro();}
};

class Millas : public ILongitudes


{
float cantidad; ConversorLongitud *pConversor;
Millas(float valor,ConversorLongitud *pC):
cantidad(valor), pConversor(pC) {}
friend class ILongitudes;
public:
float getMetros() { return cantidad*pConversor->factorMillaMetro();}
float getMillas() {return cantidad;}
float getPulgadas() {return cantidad*pConversor->factorMillaMetro()
/pConversor->factorPulgadaMetro();}
float getPies() {return cantidad*pConversor->factorMillaMetro()
/pConversor->factorPieMetro();}
};

class Pulgadas : public ILongitudes


{
float cantidad; ConversorLongitud *pConversor;
Pulgadas(float valor,ConversorLongitud *pC):
cantidad(valor), pConversor(pC) {}
friend class ILongitudes;
public:
float getMetros() { return cantidad*pConversor->factorPulgadaMetro();}
float getMillas() {return cantidad*pConversor->factorPulgadaMetro()
/pConversor->factorMillaMetro();}
float getPulgadas() {return cantidad;}
float getPies() {return cantidad*pConversor->factorPulgadaMetro()
/pConversor->factorPieMetro();}
};

class Pies : public ILongitudes


{
float cantidad; ConversorLongitud *pConversor;
Pies(float valor,ConversorLongitud *pC):
cantidad(valor), pConversor(pC) {}
friend class ILongitudes;
public:
float getMetros() { return cantidad*pConversor->factorPieMetro();}
float getMillas() {return cantidad*pConversor->factorPieMetro()
/pConversor->factorMillaMetro();}
float getPulgadas() {return cantidad*pConversor->factorPieMetro()
/pConversor->factorPulgadaMetro();}
float getPies() {return cantidad;}

};
#endif

Dpto. Electrónica, Automática e Informática Industrial 223


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

Problema 6

Cálculo de una integral sobre una función monovariable (en este caso,
sólo polinómica), pudiendo elegir la estrategia de integración: a) Lineal o b)
Bilineal.
a) Caso de uso
b) Modelo del dominio
c) DSS
d) Vista de gestión
e) Diagramas de interacción
f) DCD
g) Implementación

Calculo de Integrales
(from <Use Case Name>)

<Actor Name>
<<include>>
(f rom Actors)

definir la funcion

El cálculo de la integral requiere saber cual es la función a quien se le va aplicar


el operador. Un modelo del dominio podría quedar reflejado como:

Polinomio
(f rom DominioIntegral)

OperadorIntegral
es el nucleo del IntervaloInferior
FunciónMatemática
IntervaloSuperior
1 1 diferencial

En esta aplicación habrá que definir la función y los parámetros de la


integración. Se propone el siguiente DSS:

224 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

: Sistema
Usuari o Matematico :
<Actor Name>

definirLaFuncion()

calcularLaIntegral()

retornar_valor_integral()

En esta fase se dividirá el modelo en dos paquetes:

VistaIntegral DominioIntegral

Desgranado el DSS y aplicando los patrones se proponen los diagramas de


interacción y de clases de diseño:

Vista : Valores de la Creación con


CoordinadorIntegrales función Factoría de tipo
Singleton
definirLaFuncion( )
create()
:
IFuncionMonoVar

Parámetros
de la funcion

calcularLaIntegral( )

create()
:
ContextoIntegral

Pasar valores de
los intervalos, de
la diferencial y
de la estrate...
Se le pasa la función
y el tipo de
estrategia en el
cálculo de la integral

Dpto. Electrónica, Automática e Informática Industrial 225


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

IntegradorLineal IntegradorBilineal

VistaIntegrales <<virtual>> calcularIntegral() <<virtual>> calcularIntegral()

definirLaFuncion()
calcularLaIntegral()
<<1>>
IIntegrador FactoriaServicios
<<local>>
<<abstract>> calcularIntegral() <<static>> getInstancia()
CoordinadorIntegrales MetodoFabricacionFuncion()
MetodoFabricacionEstrategias()
CoordinadorIntegrales()
<<local>>
definirLaFuncion() ContextoIntegrador Estrategia Factoria(GoF) y
calcularLaIntegral() (GoF) Singleton (GoF)
~CoordinadorIntegrales() ContextoIntegrador()
calcularIntegral()
Polinomio
IFuncionMonoVar
Controlador getCoeficiente()
(GRASP) Polinomio()
<<abstract>> calcularValor()
Polinomio()
<<abstract>> setValoresFuncion()
<<virtual>> calcularValor()
<<virtual>> setValoresFuncion()

<<typedef>> <<typedef>> Variaciones


TipoEstrategia TipoFuncion Protegias
(GRASP)

226 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

// De: "Apuntes de Informática Industrial" Carlos Platero.


// Ver permisos en licencia de GPL
#ifndef _INTEGRADOR_INC_
#define _INTEGRADOR_INC_
#include "../Funcion/FuncionMonoVar.h"
typedef enum tipoEstr{LINEAL,BILINEAL} TipoEstrategia;
class IIntegrador
{
public:
virtual double calcularIntegral(IFuncionMonoVar *, float intInf,
float intSup, float diferencial) = 0;
};
class IntegradorLineal: public IIntegrador
{
public:
virtual double calcularIntegral(IFuncionMonoVar * pLaFuncion, float intInf,
float intSup, float diferencial){
double resultado = 0;
for(float i= intInf;i<=intSup;i+=diferencial)
resultado += pLaFuncion->calcularValor(i)*diferencial;
return (resultado);
}
};
class IntegradorBilineal: public IIntegrador
{
public:
virtual double calcularIntegral(IFuncionMonoVar * pLaFuncion, float intInf,
float intSup, float diferencial){
double resultado = 0;
for(float i= intInf;i<=intSup;i+=diferencial)
resultado +=
(pLaFuncion->calcularValor(i)+pLaFuncion->calcularValor
(i-diferencial))/2 *diferencial;
return (resultado);
}
};
class ContextoIntegrador
{
IIntegrador* pEstrategia;
public:
ContextoIntegrador
(IIntegrador* pTipodeIntegrador) : pEstrategia(pTipodeIntegrador) {}
double calcularIntegral(IFuncionMonoVar* pLaFuncion, float intInf,
float intSup, float diferencial)
{return(pEstrategia->calcularIntegral(pLaFuncion,inInf,intSup,diferencial));}
};

#endif

Dpto. Electrónica, Automática e Informática Industrial 227


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

#ifndef _IFUNCION_MONO_INC_
#define _IFUNCION_MONO_INC_
#include <vector>
#include <math.h>
typedef enum tipoFun{POLINOMIO,TRIGONOMETRICA} TipoFuncion;
class IFuncionMonoVar
{
public:
virtual double calcularValor(float) = 0;
virtual void setValoresFuncion( unsigned , double *) = 0;

};
class Polinomio : public IFuncionMonoVar
{
std::vector<double> coeficientes;
std::vector<double>::iterator iteratorCoeficientes;

double getCoeficiente(unsigned n)
{return(*(iteratorCoeficientes+n));}

public:
Polinomio(){}
Polinomio(unsigned grado, double *pCoef){
for (unsigned i=0;i<=grado;coeficientes.push_back(*(pCoef+i)),i++);
iteratorCoeficientes = coeficientes.begin();
}
virtual double calcularValor(float valorInd){
double resultado = 0;
for(int i=0;i<= this->coeficientes.size() ;i++)
resultado += getCoeficiente(i)*pow(valorInd,i);
return resultado;
}
virtual void setValoresFuncion( unsigned grado, double *pCoef){
for (unsigned i=0;i<=grado;coeficientes.push_back(*(pCoef+i)),i++);
iteratorCoeficientes = coeficientes.begin();
}
};
#endif

#ifndef _FACTORIA_INC_
#define _FACTORIA_INC_
#include "../Funcion/FuncionMonoVar.h"
#include "../Integrador/Integrador.h"
class FactoriaServicios
{
FactoriaServicios(){};
void operator=(FactoriaServicios&); // Para desactivar
FactoriaServicios(const FactoriaServicios&); // Para desactivar
public:
static FactoriaServicios& getInstancia(){
static FactoriaServicios unicaInstancia;
return unicaInstancia;
}
//Factoria de funciones matematicas
IFuncionMonoVar* FactoriaServicios::
MetodoFabricacionFuncion(TipoFuncion elTipoFuncion){
if (elTipoFuncion == POLINOMIO) return new Polinomio();
else return NULL;
}
//Factoria de estrategias
IIntegrador* FactoriaServicios::
MetodoFabricacionEstrategia(TipoEstrategia elTipoEstr){
if (elTipoEstr == LINEAL) return new IntegradorLineal;
else if (elTipoEstr == BILINEAL) return new IntegradorBilineal;
else return NULL;
}
};

#endif

228 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

// De: "Apuntes de Informática Industrial" Carlos Platero.


// (R) 2005 Con licencia GPL.
// Ver permisos en licencia de GPL
#include "../../include/Coordinador/CoordinadorIntegrales.h"
int CoordinadorIntegrales::definirLaFuncion(TipoFuncion elTipoFun,unsigned grado,
double *pCoef)
{
FactoriaServicios &laFactoria = FactoriaServicios::getInstancia();
this->pFuncionMono = laFactoria.MetodoFabricacionFuncion (elTipoFun);
if (this->pFuncionMono == NULL)
return (-1);
this->pFuncionMono->setValoresFuncion(grado,pCoef);
return 0;
}
double CoordinadorIntegrales::calcularLaIntegral(float intInf,
float intSup, float diferencial, TipoEstrategia elTipoIntegral)
{
if (this->pFuncionMono == NULL)
return (0);
FactoriaServicios &laFactoria = FactoriaServicios::getInstancia();
IIntegrador *pEstrategia = laFactoria.MetodoFabricacionEstrategia(elTipoIntegral);
if (pEstrategia == NULL)
return (0);

ContextoIntegrador elIntegrador(pEstrategia);
double resultado = elIntegrador.calcularIntegral( pFuncionMono,
intInf, intSup, diferencial );
delete pEstrategia;
return(resultado);

Problema 7

Realizar un juego de batalla los hombres contra los dragones. Los


hombres lanzan cuchillos y los dragones bolas de fuego. Los dragones se
mueven en el área izquierda de la pantalla y los hombres en el lado derecho.
En mitad de la batalla aparecen paredes móviles que se desplazan en el centro
de la pantalla. El número de luchadores puede ser variable y dinámico. Se pide:

1. Jerarquía a dos niveles de las características principales.

2. Modelo del dominio.

3. Diagrama de clases de diseño.

4. Implementación en C++ de los ficheros de cabecera de las clases.


1. Video juego de los hombres que luchan contra los dragones
1.1 Los hombres se mueven en un área restringida de la derecha.
1.2 Los dragones se mueven en un área restringida de la izquierda.
1.3 Los hombres lanzan cuchillos que se clavan en la pared o que matan
al dragón o que pasan sin hacer daño.
1.4 Los dragones lanzan bolas de fuego que no pueden atravesar las
paredes y que si tocan a un hombre lo mata.
1.5 Los dragones desaparecen de la pantalla al morir.
1.6 Los hombres desaparecen de la pantalla al morir.

Dpto. Electrónica, Automática e Informática Industrial 229


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

Luchador Arm a
ParedMovil

1
mata detiene 1
1 detiene
Dragón
lanza 1
1..n
1 Cuchillo
0..n BolaFuego 1 1
lucha contra

mata 1 0..n
1..n
lanza
1
Hombre
1

<<1>>
FactoriaObjetos ClienteVideoJuego

FactoriaObjetos()
getInstance()
metodoFabricacionLuchadores()
metodoFabricacionArmas()
metodoFabricacionObstaculos()

<<Interface>>
<<Interface>> <<Interface>>
IArma
ILuchador IObstaculo
moverseTrayectoria()
lanzarArma() moverseAleatorio()
haChocadoObjeto()
moverse() pintar()
pintar()
haRecibidoDisparo()
pintar()

Hombre BolasDeFuego Cuchillo


(f rom Analy sis Model) Dragon (f rom Analy sis Model) (f rom Analy sis Model)
(f rom Analy sis Model)

ParedMovil
(f rom Analy sis Model)

#ifndef _LUCHADORES_INC_ #ifndef _ARMAS_INC_


#define _LUCHADORES_INC_ #define _ARMAS_INC_

class FactoriaObjetos; class FactoriaObjetos;

typedef enum{HOMBRE,DRAGON} typedef enum {BOLAFUEGO,CUCHILLO}


TipoLuchadores; TipoArmas;

class ILuchador class IArmas


{ {
public: public:
virtual void lanzarArma() = 0; virtual void moverseTrayectoria() = 0;
virtual void moverse() = 0; virtual bool haChocadoObjeto() = 0;
virtual bool haRecibidoDisparo() = 0; virtual void pintar() = 0;
virtual void pintar() = 0; };
};
class BolaFuego : public IArmas
class Hombre : public ILuchador {
{ friend class FactoriaObjetos;
friend class FactoriaObjetos; BolaFuego();
Hombre(); public:
public: virtual void moverseTrayectoria();
virtual void lanzarArma(); virtual bool haChocadoObjeto();
virtual void moverse(); virtual void pintar();
virtual bool haRecibidoDisparo(); };
virtual void pintar();
}; class Cuchillo : public IArmas
{
class Dragon : public ILuchador friend class FactoriaObjetos;
{ Cuchillo();
friend class FactoriaObjetos; public:
Dragon(); virtual void moverseTrayectoria();
public: virtual bool haChocadoObjeto();
virtual void lanzarArma(); virtual void pintar();
virtual void moverse(); };
virtual bool haRecibidoDisparo();
virtual void pintar();
}; #endif

#endif

230 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

#ifndef _OBSTACULOS_INC_ #ifndef _FACTORIA_INC_


#define _OBSTACULOS_INC_ #define _FACTORIA_INC_

class FactoriaObjetos; #include "Luchadores.h"


#include "Armas.h"
typedef enum {PAREDMOVIL} TipoObstaculos; #include "Obstaculos.h"
Derecho de Autor © 2007 Carlos Platero Dueñas.
class IObstaculo class FactoriaObjetos {
{ FactoriaObjetos(){}; // Para desactivar
Permiso para copiar, distribuir y/o modificar
public: este documento {};bajo los términos
void operator=(FactoriaObjetos&)
devoid
virtual la Licencia de Documentación
moverseAleatoriamente() = 0; Libre GNU, Versión
FactoriaObjetos(const 1.1 o cualquier
FactoriaObjetos&) {}; otra
virtual void pintar() = 0; public:
}; versión posterior publicada por la Free Software Foundation; sin secciones
static FactoriaObjetos& getInstance() {
invariantes, sin texto de la Cubierta Frontal, así como
static FactoriaObjetos el texto de la Cubierta
unicaInstancia;
class ParedMovil : public IObstaculo return unicaInstancia;
{ Posterior. Una copia de la licencia es incluida
}
en la sección titulada "Licencia de
friendDocumentación
class FactoriaObjetos;
Libre GNU". IArma* metodoFabricacionArmas (TipoArmas tipo) {
ParedMovil(); if(tipo == BOLAFUEGO) return new BolaFuego;
public: else if(tipo == CUCHILLO) return new Cuchillo;
virtual void moverseAleatoriamente() = 0; else return NULL;}
virtual void pintar() = 0; ILuchador* metodoFabricacionLuchadores (TipoLuchador tipo)
}; {
if(tipo == HOMBRE) return new Hombre;
La Licencia de documentación libre GNUelse(GNU if(tipo ==Free
DRAG ON)Documentation
return new Dragon; License)
#endif
es una licencia con copyleft para contenidos abiertos. Todos los contenidos de
else return NULL;}
IObstaculo * metodoFabricacionObstaculos (TipoObstaculos
estos apuntes están cubiertos por esta tipo){
licencia. La version 1.1 se encuentra en
http://www.gnu.org/copyleft/fdl.html. La traducción (noVIL)oficial)
if(tipo == P AREDMO return new al castellano de
ParedMovil;
else return NULL;}
la versión 1.1 se encuentra en http://www.es.gnu.org/Licencias/fdles.html
};

Problema 8

Se tienen las siguientes clases pertenecientes a unas librerías C++,


desarrolladas por los fabricantes de impresoras, y que sirven para imprimir un
archivo cualquiera en una impresora de un determinado fabricante.

Por tanto, las dos clases anteriores no se pueden modificar, tocar o


cambiar. Se desea hacer un programa que permita al usuario teclear el nombre
de un archivo, el nombre de la impresora de destino, y que el programa utilice
automáticamente la clase de la librería correspondiente. La función main de
ese programa (incompleta) seria. Se pide:
1. Diagrama UML de las clases existentes .
2. Diagrama de Clases de Diseño (DCD) de la solución.
3. Explicación (breve, con notas en el anterior diagrama) de los patrones
usados.
4. Implementación C++ de la solución propuesta (no olvidar completar el
main).

Dpto. Electrónica, Automática e Informática Industrial 231


Capítulo 6: Diseño orientado a objetos Apuntes de Informática Industrial

class EpsonPrinterDriver
{
public:
bool Print(char filename[]);
};
class HPControladorImpresora
{
public:
//este metodo devuelve 1 en caso de exito y -1 en caso de error
int ImprimeFichero(char* nombre_fichero);
};

int main()
{
char fichero[255],nombre_impresora[255];

cout<<"Introduzca en nombre de fichero: ";


cin>>fichero;
cout<<"Introduzca nombre impresora: HP o EPSON: ";
cin>>nombre_impresora;

Impresora* impresora=NULL;

//AQUÍ COMPLETAR CODIGO DE CREACION DE LA IMPRESORA ADECUADA


// en funcion de "nombre_impresora"

if(impresora==NULL)
{
cout<<"Impresora no existe"<<endl;
return -1;
}
if(impresora->Imprime(fichero))
cout<<"Impresion correcta"<<endl;
else
cout<<"Impresion fallida"<<endl;
return 1;
}

232 Dpto. Electrónica, Automática e Informática Industrial


Apuntes de Informática Industrial Carlos Platero

Derecho de Autor © 2009 Carlos Platero Dueñas.


Permiso para copiar, distribuir y/o modificar este documento bajo los términos
de la Licencia de Documentación Libre GNU, Versión 1.1 o cualquier otra
versión posterior publicada por la Free Software Foundation; sin secciones
invariantes, sin texto de la Cubierta Frontal, así como el texto de la Cubierta
Posterior. Una copia de la licencia es incluida en la sección titulada "Licencia de
Documentación Libre GNU".

La Licencia de documentación libre GNU (GNU Free Documentation License) es una


licencia con copyleft para contenidos abiertos. Todos los contenidos de estos apuntes
están cubiertos por esta licencia. La version 1.1 se encuentra en
http://www.gnu.org/copyleft/fdl.html. La traducción (no oficial) al castellano de la
versión 1.1 se encuentra en http://www.es.gnu.org/Licencias/fdles.html

Dpto. Electrónica, Automática e Informática Industrial 233

También podría gustarte