0% encontró este documento útil (0 votos)
21 vistas43 páginas

Tema 2 - Programación Modular

Cargado por

pau.frangi
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
21 vistas43 páginas

Tema 2 - Programación Modular

Cargado por

pau.frangi
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 43

Programación modular

Alberto Verdejo
Facultad de Informática (UCM)

Fundamentos de la programación II
Programación modular

‣ Es una metodología de diseño de software que organiza el código en


unidades autónomas llamadas módulos.

‣ Estos módulos son bloques de construcción independientes pero que pueden


colaborar entre sí para resolver un problema.

‣ En programación modular se separa la interfaz (cómo usar el módulo) de la


implementación (cómo funciona internamente).
Separación de interfaz e implementación

‣ Facilita el mantenimiento del código: Al separar la interfaz de la


implementación, los cambios en la implementación no afectan a quienes
usan la interfaz, simplificando las actualizaciones y correcciones.
• Cuando un módulo A incluye a otro módulo B, cualquier cambio en la
implementación de B no debería afectar a la implementación de A.

‣ Oculta detalles de implementación: Esto permite a otros programadores


interactuar con un módulo sin necesidad de saber cómo está construido
internamente, fomentando la abstracción y la simplificación del diseño.
Separación de interfaz e implementación

‣ Favorece la reutilización de código: Al proporcionar una interfaz clara y bien


definida, los módulos se pueden reutilizar en diferentes contextos,
promoviendo la eficiencia y evitando la duplicación de código.

‣ Mejora la legibilidad y claridad del código: La separación de interfaz e


implementación facilita la comprensión del código al dividirlo en partes más
manejables y comprensibles. Cada unidad o módulo tiene un objetivo claro y
sencillo.
¿Qué son la interfaz y la implementación?

‣ Interfaz: la interfaz de un módulo es la forma en que se interactúa con él


desde el exterior sin revelar los detalles internos de su implementación. Se
define mediante las declaraciones de funciones y tipos de datos que son
accesibles desde fuera del módulo. Todo lo que el usuario del módulo
necesita saber para poder utilizarlo y nada más.

‣ Implementación: La implementación comprende los detalles concretos de


cómo un módulo realiza sus funciones. Incluye la lógica, algoritmos y
variables internas que no es necesario conocer por quienes utilizan el
módulo.
Programación modular en C++

‣ Separación en ficheros .h y .cpp: Una práctica común en C++ es dividir la


interfaz y la implementación de una clase en dos archivos diferentes.
‣ Los archivos de cabecera (.h) contienen las declaraciones de tipos y
funciones del módulo. Esto proporciona la “vista externa” del módulo. Lo que
necesita saber quien lo va a usar. Es decir, la interfaz del módulo.
‣ Los archivos de implementación (.cpp) contienen la implementación real
(definiciones) de las funciones declaradas en el archivo de cabecera. Aquí es
donde se detallan los algoritmos necesarios para implementar las
funcionalidades declaradas en la interfaz de la clase.
¿A qué hora pasa el próximo tren?
Problema: ¿A qué hora pasa el próximo tren?

Todo el que ha estado alguna vez esperando que llegue el tren


se ha planteado lo que podría haber hecho si hubiese sabido a
qué hora pasaría. Es entonces cuando envidiamos a los usuarios
que lo utilizan con frecuencia y son capaces de llegar a la esta-
ción con solo un minuto de antelación. ¿Por qué siempre confia-
mos en la suerte y no comprobamos a qué hora pasará antes de
vernos ya en el andén?

Hoy, al llegar a la estación y ver que de nuevo tendrás que esperar, has decidido hacer una
aplicación que te permita saber en cualquier momento a qué hora pasará el próximo tren.
Para empezar, en el tiempo que has estado esperando ya has conseguido el horario de los
trenes de las estaciones que utilizas a menudo. Ahora, ya será muy fácil calcular cuándo
pasará el próximo tren.

Requisitos de implementación
Problema: ¿A qué hora pasa el próximo tren?
Tipo de datos Hora horas.h

#include <iostream>

struct Hora {
int hh;
int mm;
int ss;
};

bool operator<(Hora const& a, Hora const& b);

std::istream & operator>>(std::istream & entrada, Hora &/*sal*/ h);

std::ostream & operator<<(std::ostream & salida, Hora const& h);


Tipo de datos Hora horas.cpp

#include <iomanip>
using namespace std;
#include "horas.h"

// operador de comparación
bool operator<(Hora const& a, Hora const& b) {
if (a.hh < b.hh) return true;
else if (a.hh > b.hh) return false;
else if (a.mm < b.mm) return true;
else if (a.mm > b.mm) return false;
else return a.ss < b.ss;
}
Tipo de datos Hora horas.cpp

// operador de entrada
istream & operator>>(istream & entrada, Hora &/*sal*/ hora) {
char aux;
entrada >> hora.hh >> aux >> hora.mm >> aux >> hora.ss;
return entrada;
}

// operador de salida
ostream & operator<<(ostream & salida, Hora const& hora) {
salida << setfill('0') << setw(2) << hora.hh << ':'
<< setfill('0') << setw(2) << hora.mm << ':'
<< setfill('0') << setw(2) << hora.ss;
return salida;
}
Tipo de datos HorarioTrenes horario.h

#include "horas.h"

const int MAX_TRENES = 100; // ¿nos quedaremos cortos?

struct HorarioTrenes {
Hora horario[MAX_TRENES];
int num_trenes;
};
Tipo de datos HorarioTrenes horario.cpp

#include "horario.h"
Programa principal main.cpp

#include "horas.h"
#include "horario.h"
using namespace std;

bool busca(HorarioTrenes const& trenes, Hora const& h,


Hora &/*sal*/ sig) {
int i = 0;
while (i < trenes.num_trenes && trenes.horario[i] < h)
++i;
if (i < trenes.num_trenes) {
sig = trenes.horario[i];
return true;
} else return false;
}
Programa principal main.cpp

int main() {
int nTrenes, nConsultas; cin >> nTrenes >> nConsultas;
HorarioTrenes trenes;
trenes.num_trenes = nTrenes;
for (int i = 0; i < nTrenes; ++i) {
cin >> trenes.horario[i];
}
// consulta las horas
for (int i = 0; i < nConsultas; ++i) {
Hora h; cin >> h;
Hora sig;
if (busca(trenes, h, sig)) cout << sig << '\n';
else cout << "NO\n"; // ya no hay trenes posteriores
}
return 0;
}
¿Mejoras?

‣ El implementador del tipo HorarioTrenes ha aprendido a usar la clase


std::vector y cree que conviene utilizarla ahora para evitar la desventaja
del límite máximo del número de trenes.

horario.h
#include <vector>

using HorarioTrenes = std::vector<Hora>;


¿Mejoras?

main.cpp

¡Falta de cohesión!
¿Mejoras?

main.cpp

¡Demasiado acoplamiento!
Objetivos deseables en la programación modular

‣ El acoplamiento se refiere al grado en que dos módulos están


interconectados o dependen entre sí. Un acoplamiento fuerte indica una
relación intensa y estrecha entre los módulos, lo cual puede llevar a
problemas como la falta de flexibilidad, la dificultad para realizar cambios y la
reducción de la modularidad.
‣ La cohesión se refiere a la medida en que los componentes de un módulo
están relacionados entre sí, es decir, cuánto sentido tiene agruparlos juntos.
‣ Una alta cohesión y un bajo acoplamiento son objetivos deseables para
mejorar la legibilidad, mantenibilidad y reutilización del código.
Tipo de datos Hora horas.h

#include <iostream>

struct Hora { Este módulo no cambia.


int hh; ¡Ya lo habíamos hecho bien!
int mm;
int ss;
};

bool operator<(Hora const& a, Hora const& b);

std::istream & operator>>(std::istream & entrada, Hora &/*sal*/ h);

std::ostream & operator<<(std::ostream & salida, Hora const& h);


Tipo de datos Hora horas.cpp

#include <iomanip>
using namespace std;
#include "horas.h"

// operador de comparación
bool operator<(Hora const& a, Hora const& b) {
if (a.hh < b.hh) return true;
else if (a.hh > b.hh) return false;
else if (a.mm < b.mm) return true;
else if (a.mm > b.mm) return false;
else return a.ss < b.ss;
}
Tipo de datos Hora horas.cpp

// operador de entrada
istream & operator>>(istream & entrada, Hora &/*sal*/ hora) {
char aux;
entrada >> hora.hh >> aux >> hora.mm >> aux >> hora.ss;
return entrada;
}

// operador de salida
ostream & operator<<(ostream & salida, Hora const& hora) {
salida << setfill('0') << setw(2) << hora.hh << ':'
<< setfill('0') << setw(2) << hora.mm << ':'
<< setfill('0') << setw(2) << hora.ss;
return salida;
}
Tipo de datos HorarioTrenes horario.h

#include "horas.h"

const int MAX_TRENES = 100; // ¿nos quedaremos cortos?


struct HorarioTrenes {
Hora horario[MAX_TRENES];
int num_trenes;
};

void inicia(HorarioTrenes &/*sal*/ trenes);

void inserta(HorarioTrenes &/*ent/sal*/ trenes, Hora const& h);

bool busca(HorarioTrenes const& trenes, Hora const& h,


Hora &/*sal*/ sig);
Tipo de datos HorarioTrenes horario.cpp

#include "horario.h"

void inicia(HorarioTrenes &/*sal*/ trenes) {


trenes.num_trenes = 0;
}

void inserta(HorarioTrenes &/*ent/sal*/ trenes, Hora const& h) {


trenes.horario[trenes.num_trenes] = h;
++trenes.num_trenes;
}
Tipo de datos HorarioTrenes horario.cpp

bool busca(HorarioTrenes const& trenes, Hora const& h,


Hora &/*sal*/ sig) {
int i = 0;
while (i < trenes.num_trenes && trenes.horario[i] < h)
++i;
if (i < trenes.num_trenes) {
sig = trenes.horario[i];
return true;
}
else
return false;
}
Programa principal main.cpp
int main() {
int nTrenes, nConsultas; cin >> nTrenes >> nConsultas;
HorarioTrenes trenes;
inicia(trenes);
for (int i = 0; i < nTrenes; ++i) {
Hora h; cin >> h;
inserta(trenes, h);
}
// consulta las horas
for (int i = 0; i < nConsultas; ++i) {
Hora h; cin >> h;
Hora sig;
if (busca(trenes, h, sig)) cout << sig << '\n';
else cout << "NO\n"; // ya no hay trenes posteriores
}
return 0;
}
¿Mejoras? ¿Ahora sí podemos?

‣ El implementador del tipo HorarioTrenes ha aprendido a usar la clase


std::vector y cree que conviene utilizarla ahora para evitar la desventaja
del límite máximo del número de trenes.
horario.h
#include <vector>

using HorarioTrenes = std::vector<Hora>;

‣ Solamente se producen errores en horario.cpp. ¡Lógico!


‣ No es necesario cambiar nada en main.cpp.
‣ ¡Pero hay que ser muy disciplinado! ¿Nos puede ayudar C++?
Programación orientada a objetos: clases

‣ Los tipos de datos son la herramienta para representar en el código


conceptos del problema a resolver.
‣ Los tipos constan de valores y operaciones que pueden utilizarse para
construir dichos valores, consultar sus propiedades o modificarlos.
‣ Las clases son tipos de datos definidos por el usuario. Se construyen a partir
de tipos básicos, otras clases y operaciones. Tienen dos tipos de
componentes:
• Atributos: definen la representación de los objetos (valores) de la clase.
• Métodos: proporcionan las operaciones sobre esos objetos.
Clases: Interfaz e implementación

‣ Las clases tienen una interfaz (la parte a la que acceden directamente los
usuarios de la clase) y una implementación (a la que se accede solo
indirectamente a través de la interfaz).
class MiClase { // nombre de la clase
public:
// miembros públicos: la interfaz accesible por los usuarios
interfaz // - funciones
// - tipos
private:
// miembros privados: detalles de la implementación
// (solamente utilizables desde los miembros de la clase)
implementación // - funciones
// - tipos
// - atributos (variables)
};
Tipo de datos Hora horas.h

#include <iostream>

class Hora {
public:
Hora(int h = 0, int m = 0, int s = 0) : hh(h), mm(m), ss(s) {}
int hora() const { return hh; }
int minuto() const { return mm; }
int segundo() const { return ss; }
bool operator<(Hora const& otro) const;
void read(std::istream & input = std::cin);
void print(std::ostream & output = std::cout) const;
private:
int hh, mm, ss;
};

std::ostream & operator<<(std::ostream & salida, Hora const& h);


std::istream & operator>>(std::istream & entrada, Hora &/*sal*/ h);
Tipo de datos Hora horas.cpp

#include <iomanip>
#include "horas.h"
using namespace std;

// operador de comparación
bool Hora::operator<(Hora const& otro) const {
if (hh < otro.hh) return true;
else if (hh > otro.hh) return false;
else if (mm < otro.mm) return true;
else if (mm > otro.mm) return false;
else return ss < otro.ss;
}
Tipo de datos Hora horas.cpp

void Hora::read(istream & input) {


char aux;
input >> hh >> aux >> mm >> aux >> ss;
}

istream & operator>>(istream & entrada, Hora &/*sal*/ h) {


h.read(entrada);
return entrada;
}
Tipo de datos Hora horas.cpp

void Hora::print(ostream & output) const {


output << setfill('0') << setw(2) << hh << ':'
<< setfill('0') << setw(2) << mm << ':'
<< setfill('0') << setw(2) << ss;
}

std::ostream & operator<<(ostream & salida, Hora const& h) {


h.print(salida);
return salida;
}
Tipo de datos HorarioTrenes horario.h

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

class HorarioTrenes {
public:
HorarioTrenes() {}
void inserta_tren(Hora const& hora);
bool siguiente_tren(Hora const& hora, Hora &/*sal*/ sig) const;

private:
std::vector<Hora> trenes;
};
Tipo de datos HorarioTrenes horario.cpp
#include "horario.h"

void HorarioTrenes::inserta_tren(Hora const& hora) {


trenes.push_back(hora);
}

bool HorarioTrenes::siguiente_tren(Hora const& hora,


Hora &/*sal*/ sig) const {
bool encontrado = false;
for (int i = 0; i < trenes.size() && !encontrado; ++i) {
if (!(trenes[i] < hora)) {
encontrado = true; sig = trenes[i];
}
}
return encontrado;
}
Programa principal main.cpp
int main() {
int nTrenes, nConsultas; cin >> nTrenes >> nConsultas;

HorarioTrenes trenes;
for (int i = 0; i < nTrenes; ++i) {
Hora h; cin >> h;
trenes.inserta_tren(h);
}

// consulta las horas


for (int i = 0; i < nConsultas; ++i) {
Hora h; cin >> h;
Hora sig;
bool existe = trenes.siguiente_tren(h, sig);
if (!existe) cout << "NO\n"; // ya no hay trenes posteriores
else cout << sig << '\n';
}
return 0;
}
Proyecto multiarchivo
Compilación separada
‣ Cada módulo se compila a código objeto de forma independiente.
horas.cpp
horas.obj
#include "horas.h"
#include <iomanip>
001011101010110010101
001010100101010111110
// operador de comparación 0101001010101010100101010101
bool Hora::operator<(Hora const& otro) const
{ 0101100101010101010101010101
if (hh < otro.hh) return true;
else if (hh > otro.hh) return false; 0010101010101010000010101010
else if (mm < otro.mm) return true;
else if (mm > otro.mm) return false; 1101010010101010101010000101
else return ss < otro.ss;
} 0101111001010101010111100110
0101010110101010101001001010
void Hora::read(std::istream& i) {
char aux; 1001111001010101010010101001

main.cpp
i >> hh >> aux >> mm >> aux >> ss;
} 0101001010100101010100101000

std::istream & operator>>(std::istream &


entrada, Hora& h) {
0100111101001010101100101010
1001010100101010101010100101
main.obj
h.read(entrada);
return entrada; 0100101010101000010101011100
}
1010100100101010111111101010
#include <iostream>
void Hora::print(std::ostream& o) const {
1100110101011100001001010100 using namespace std; 001011101010110010101
o << std::setfill('0') << std::setw(2) << #include "horas.h"
hh << ':'
101010101010110101010010100 #include "horario.h"
001010100101010111110
<< std::setfill('0') << std::setw(2) <<
0101001010101010100101010101
int main() { 1001111001010101010010101001
int nTrenes, nConsultas; 0101001010100101010100101000
cin >> nTrenes >> nConsultas;

HorarioTrenes trenes;
0100111101001010101100101010
1001010100101010101010100101
for (int i = 0; i < nTrenes; ++i) {
Hora h; cin >> h; 0100101010101000010101011100
trenes.inserta_tren(h);
} 1010100100101010111111101010
// consulta las horas 1100110101011100001001010100
for (int i = 0; i < nConsultas; ++i) {
Hora h; cin >> h; 0101100101010101010101010101
Hora sig;
bool existe = trenes.siguiente_tren(h, 0010101010101010000010101010

horario.cpp
sig);
if (!existe) cout << "NO\n"; // ya no 1101010010101010101010000101

horario.obj
hay trenes posteriores
else cout << sig << '\n'; 0101111001010101010111100110
}
return 0; 0101010110101010101001001010
}
101010101010110101010010100

#include "horario.h"
001011101010110010101
void HorarioTrenes::inserta_tren(Hora const&
001010100101010111110
hora) {
trenes.push_back(hora);
0101001010101010100101010101
} 1001111001010101010010101001
bool HorarioTrenes::siguiente_tren(Hora
0101001010100101010100101000
const& hora, Hora &/*sal*/ sig) const { 0100111101001010101100101010
bool encontrado = false;
for (int i = 0; i < trenes.size() && 1001010100101010101010100101
!encontrado; ++i) {
if (!(trenes[i] < hora)) { 0100101010101000010101011100
encontrado = true;
sig = trenes[i]; 1010100100101010111111101010
}
} 1100110101011100001001010100
return encontrado;
} 0101100101010101010101010101
0010101010101010000010101010
1101010010101010101010000101
0101111001010101010111100110
0101010110101010101001001010
101010101010110101010010100
Compilación separada
‣ Y después se unen para formar el ejecutable.

‣ Si un módulo cambia solamente se regenera el .obj de ese módulo.


Preprocesamiento de #include
horas.h
#include <iostream>
horario.h
class Hora {
#include "horas.h" public:
#include <vector> Hora(int h, int m, int) {}
horario.cpp int hora() const;
class HorarioTrenes { ...
#include "horario.h"
public:
void HorarioTrenes::inserta_tren(Hora const& hora) HorarioTrenes() {}
{ void inserta_tren(Hora ...
trenes.push_back(hora);
...
}

bool HorarioTrenes::siguiente_tren

main.cpp
#include <iostream>
using namespace std;
#include "horas.h"
#include "horario.h"

int main() {
int nTrenes, nConsultas;
cin >> nTrenes >> nConsultas;
Preprocesamiento de #include
horas.h
#ifndef HORAS
#define HORAS
horario.h
#ifndef HORARIO
#define HORARIO
horario.cpp
#endif

#endif

main.cpp

#include "horas.h"
#include "horario.h"
Preprocesamiento de #include: #pragma once
horas.h
#pragma once
¡No es estándar! horario.h
#pragma once

horario.cpp

main.cpp

#include "horas.h"
#include "horario.h"
Espacios de nombres

También podría gustarte