Programação II Módulos, Encapsulamento e TADsbfeijo/prog2/ProgII_TAD.pdf · • Módulo é um...
Transcript of Programação II Módulos, Encapsulamento e TADsbfeijo/prog2/ProgII_TAD.pdf · • Módulo é um...
Módulos
• Programação modular é uma técnica de design de software na qual
particionamos o programa em diversos módulos
• Módulo é um artefato de programação que pode ser desenvolvido e
compilado independentemente dos demais artefatos que compõe um
programa. Os módulos são, portanto, compilados em separado e,
posteriormente, integrados para formar o programa.
– Por exemplo: os vários source files .c
• Módulos são incorporados nos programas através de interfaces.
• A interface é a especificação do módulo, que provê informação aos
clientes sobre a funcionalidade do módulo. Os elementos definidos na
interface são detectados pelos outros módulos.
– Em C, as interfaces são definidas pelos header files .h
• Dois objetivos importantes da programação modular:
– ENCAPSULAMENTO (encapsulation ou Information hiding): proteger dados e
funções internas de um módulo e tornar invisível para programadores de
outros módulos as características da implementação de um módulo
– REÚSO
int tamanhoStr(char * s) {
int n;
for (n=0; s[n]; n++)
;
return n;
}
void copiaStr(char * s, char * t){
int i;
for (i=0; s[i] = t[i]; i++)
;
}
Módulo, Interface e Encapsulamento
int tamanhoStr(char * s) {
int n = 0;
while (*s++)
n++;
return n;
}
void copiaStr(char * dest, char * orig){
while (*dest++ = *orig++)
;
}
#include "cadeias.h"
cadeias.ccadeias.h
prog1.c
#include <stdio.h>
int main (void)
{
char s[10];
char * t = puc;
printf("Tamanho= %d\n“,tamanhoStr(t));
copiaStr(s,t);
printf("Tamanho= %d\n“,tamanhoStr(s));
return 0;
}
/* Funcoes modulo cadeias.c */
// Retorna tamanho do string s
int tamanhoStr(char * s);
// Copia string orig para string dest
void copiaStr(char * dest, char * orig);
#include "cadeias.h"
a interface:
A parte encapsulada pode
mudar sem afetar o
programa que usa o Módulo
cadeia.obj + prog1.obj
prog.exe
compile: link
compile:
outra
versão:
Bibliotecas são similares
• Você já encontrou situações similares ao exemplo anterior quando usou
as bibliotecas de matemática e de strings, cujas interfaces são math.h e
string.h (obviamente bem mais complexas que o .h do exemplo anterior).
Vantagens e Problemas da Prog Modular
• Vantagens
– Vencer barreiras de complexidade
– Distribuir trabalho
– Reutilizar módulos (diminuindo volume de trabalho e aumentando qualidade)
– Tornar gerenciável o processo de desenvolvimento
– Permitir desenvolvimento incremental
– Permitir deixar o aprimoramento do desempenho para uma época mais
oportuna
– Reduz tempo de compilação
– Podemos predizer o comportamento de um programa examinando as
interfaces dos módulos que o compõem
• Problemas
– Como particionar um programa e como especificar módulos ?
– Como garantir que as interfaces entre módulos sejam bem conhecidas e
respeitadas pelos programadores ?
– Como assegurar a qualidade de um módulo (controle de qualidade) ?
– Como coordenar e acompanhar o trabalho da equipe ?
– Como manter a coerência do conjunto de módulos em contínua evolução ?
Programação Modular, Arndt von Staa, Campus/Elsevier, 2000.
Tipo Abstrato de Dados (TAD)
• TAD é um conjunto de funções operando sobre uma nova estrutura de dados (i.e.
um novo tipo de dados), de maneira que a organização física da estrutura e a
implementação das funções são encapsuladas (i.e. não são visíveis para o
programador usuário do tipo abstrato).
• Usamos o novo tipo de dados através destas funções (chamadas de operadores) –
i.e. usamos este novo tipo de maneira indireta: acesso à representação é sempre
através das funções. Trata-se de um tipo abstrato no sentido de que não
conhecemos a sua organização física concreta e o usamos através de sua
funcionalidade (não dependemos de saber a sua implementação).
• TADs podem ser implementados como módulos.
• A interface de um TAD contém apenas:
1. o nome do novo tipo de dados (via typedef)
• Atenção: é apenas o typedef !! o struct fica escondido no módulo .c
2. os protótipos das funções que manipulam o novo tipo de dados
• Os nomes das funções devem ser prefixadas pelo nome do tipo (para evitar conflitos
quando tipos distintos são usados em conjunto). Por exemplo: ptoCria ou pt_cria para
uma função que cria um tipo Ponto.
• Para independermos totalmente da organização “física” do novo tipo, é essencial que
haja funções que criam este tipo e que destroem este tipo abstrato.
Módulos Implementando TADs
struct ponto { float x; float y; };
Ponto * ptoCria(float x, float y)
{
Ponto * p = (Ponto *)malloc(sizeof(Ponto));
p->x = x;
p->y = y;
return p;
}
void ptoLibera(Ponto * p) { free(p); }
void ptoPrint(Ponto * p)
{
printf("%f %f\n", p->x, p->y);
}
#include ...
#include "ponto.h"
ponto.cponto.h
prog.c
A parte encapsulada pode mudar
(inclusive as estruturas) sem afetar o
programa que usa o Módulo TAD
typedef struct ponto Ponto;
Ponto * ptoCria(float x, float y);
void ptoLibera(Ponto * p);
void ptoPrint(Ponto * p);
int main(void)
{
Ponto * p1 = ptoCria(2.0,1.0);
ptoPrint(p1);
ptoLibera(p1);
return 0;
}
#include ...
#include “ponto.h"
estruturas e funções
privadas
(implementação
encapsulada, escondida)
interface de TAD
Identificando e definindo TADs
• Note que podemos ter módulos e interfaces sem que isto caracterize um
TAD. TAD diz respeito a um novo tipo e é abstrato.
• Se a interface tem o struct de um tipo, então não se trata de um tipo
abstrato. Neste caso você tem um tipo concreto. Não há encapsulamento.
Tipos concretos de dados são implementações diretas e públicas de
algum conceito relativamente simples.
• Um TAD é um modelo matemático para uma específica classe de
estruturas de dados cujas instâncias têm comportamento similar, ou seja:
que têm um significado (i.e. uma semântica) similar.
• TADs oferecem uma visão (e um uso) de alto nível de um conceito
independente de sua implementação.
• Um TAD diz respeito a um tipo e não a mais de um tipo.
– Por exemplo, você não deve misturar funções que manipulam um vetor de
pontos 2D (tipo Ponto * v[];), e.g. Ponto * somaVetores(Ponto ** v, Ponto ** w);
com um TAD Ponto. Um TAD representa um tipo e suas
características/funcionalidades próprias. Neste exemplo de vetor de pontos,
crie um módulo separado: vetorPontos.c. Você até pode decidir implementar
vetores de Pontos como um TAD, mas nunca misturá-los.
TAD - Ponto
• Interface de Ponto
– define o nome do tipo e os nomes das funções exportadas
– a composição da estrutura Ponto não faz parte da interface:
• não é exportada pelo módulo
• não faz parte da interface do módulo
• não é visível para outros módulos
– os módulos que utilizarem o TAD Ponto:
• não poderão acessar diretamente os campos da estrutura Ponto
• só terão acesso aos dados obtidos através das funções exportadas
TAD Ponto – Interface – ponto.h
/* TAD: Ponto 2D */
/* Tipo exportado */
typedef struct ponto Ponto;
/* Funções exportadas */
/* Construtores e Destruidores (construtors & Destructors)*/
/* Função cria - Aloca e retorna um ponto com coordenadas (x,y). Retorna
NULL se não há espaço suficiente de memória */
Ponto * ptoCria(float x, float y);
/* Função libera - Libera a memória de um ponto previamente criado */
void ptoLibera(Ponto * p);
/* Que acessa e atribui (accessors & mutators)(get & set) */
/* Função acessa - Retorna os valores das coordenadas de um ponto */
void ptoAcessa(Ponto * p, float * x, float * y);
/* Função atribui - Atribui novos valores às coordenadas de um ponto */
void ptoAtribui(Ponto * p, float x, float y);
/* Funções e operações */
/* Função distancia - Retorna a distância entre dois pontos */
float ptoDist(Ponto * p1, Ponto * p2);
ponto.h: arquivo com a interface de Ponto
(não há dependência de outros módulos)
TAD Ponto – Módulo – ponto.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "ponto.h“
struct ponto
{
float x;
float y;
};
Ponto * ptoCria (float x, float y)
{
Ponto* p = (Ponto *) malloc
(sizeof(Ponto));
if (p == NULL)
return NULL;
p->x = x;
p->y = y;
return p;
}
void ptoLibera(Ponto * p){
free(p);
// nao adianta fazer p = NULL;
}
void ptoAcessa(Ponto * p, float * x,
float * y)
{
*x = p->x;
*y = p->y;
}
void ptoAtribui(Ponto* p, float x, float y)
{
p->x = x;
p->y = y;
}
float ptoDist(Ponto * p1, Ponto * p2){
float dx = p2->x - p1->x;
float dy = p2->y - p1->y;
return sqrt(dx*dx + dy*dy);
}
Exemplo de Programa que usa o TAD Ponto
#include <stdio.h>
#include "ponto.h"
int main(void)
{
float x, y;
Pointo * p1 = ptoCria(2.0,1.0);
Pointo * p2 = ptoCria(3.4,2.1);
if (p1==NULL || p2==NULL) {printf("sem memoria\n");exit(1);}
float d = ptoDist(p1,p2);
printf("Distancia entre pontos: %f\n",d);
ptoAcessa(p1,&x,&y);
printf("Coordenadas ponto 1: %f e %f\n",x,y);
ptoLibera(p1);
ptoLibera(p2);
return 0;
}
Algumas Variantes
• O tipo exportado pode ser definido como ponteiro, p.ex:
typedef struct ponto * Ponto;
• Neste caso, as funções exportadas precisam ser reescritas, por ex.:
• Accessors podem ser um para cada componente:
float ptoAcessa_x(Ponto p);
float ptoAcessa_y(Ponto p);
• Implemente estas variantes!
Ponto ptoCria (float x, float y)
{
Ponto p = (Ponto)malloc(sizeof(struct ponto));
if (p == NULL)
return NULL;
p->x = x;
p->y = y;
return p;
}
Arquivos Header Idempotente(1) – GUARDS
Sempre pode acontecer que arquivos header sejam incluídos várias vezes fazendo com que variáveis e
estruturas sejam redefinidas várias vezes. Isto pode resultar em erros de compilação. Felizmente o
preprocessador da linguagem C provê uma maneira fácil de assegurar que um arquivo header seja incluído apenas uma vez. Através da diretiva #ifndef (que significa if not defined), podemos incluir um bloco
de texto somente se uma particular expressão está indefinida. Neste caso, incluimos a definição da expressão para garantir que o código no #ifndef seja incluído apenas na primeira vez que o arquivo é carregado. Na expressão, usamos nomes com letras maiúsculas e precedidas de _. Também é usual colocar
_H no final. Por exemplo:
#ifndef _PONTO_H
#define _PONTO_H
/* código */
#endif // #ifndef _PONTO_H
Note que não é necessário dar um valor para a expressão _PONTO_H. É suficiente incluir a linha
#define _PONTO_H para tornar a expressão definida. Também note que é útil comentar qual comando
condicional um particular #ifndef termina, pois diretivas de preprocessador são raramente indentados (o que
pode dificultar seguir o fluxo da execução). Situação similar pode ser usada para definir constantes, por ex.:#ifndef NULL
#define NULL (void *)0
#endif // #ifndef NULL
(1) Uma operação é IDEMPOTENTE se aplicada mais de uma vez a qualquer valor ela dá o mesmo valor
que se fosse aplicada uma única vez. Por ex.: abs(abs(x)) = abs(x)
Estas 3 diretivas são chamadas de HEADER GUARDS (porque
protegem). Header Guards tornam o arquivo Idempotente.
Mais recentemente, este tipo de Header Guards foi substituído
pela diretiva #pragma once
Porém nem todos os compiladores reconhecem #pragma
Exemplo Header Idempotente
/* TAD: Ponto 2D */
#ifndef _PONTO_H
#define _PONTO_H
typedef struct ponto Ponto;
/* Função cria - Aloca e retorna um ponto com coordenadas (x,y). Retorna
NULL se não há espaço suficiente de memória */
Ponto * ptoCria(float x, float y);
/* Função libera - Libera a memória de um ponto previamente criado */
void ptoLibera(Ponto * p);
/* Função acessa - Retorna os valores das coordenadas de um ponto */
void ptoAcessa(Ponto * p, float * x, float * y);
/* Função atribui - Atribui novos valores às coordenadas de um ponto */
void ptoAtribui(Ponto * p, float x, float y);
/* Função distancia - Retorna a distância entre dois pontos */
float ptoDist(Ponto * p1, Ponto * p2);
#endif // #ifndef _PONTO_H
TAD Circulo – Interface – circulo.h
/* TAD: Círculo */
/* Dependência de módulos */
#include "ponto.h"
/* Tipo exportado */
typedef struct circulo Circulo;
/* Funções exportadas */
// Constructors & Destructors
/* Função cria - Aloca e retorna um círculo com centro (x,y) e raio r */
Circulo * circCria (float x, float y, float r);
/* Função libera - Libera a memória de um círculo previamente criado */
void circLibera (Circulo * c);
// Get & Set
Float circGetRaio(Circulo * c); // acessa raio
Ponto * circGetCentro(Circulo * c); // acessa centro
// Funcoes e Operacoes Gerais
/* Função area - Retorna o valor da área do círculo */
float circArea (Circulo * c);
/* Função interior - Verifica se um dado ponto p está dentro do círculo */
int circInterior (Circulo * c, Ponto * p);
interface ponto.h incluída na interface pois uma
das funções (circInterior) faz uso do tipo Ponto
Refaça este exemplo com circCria tendo dois
argumentos (um Ponto e e um raio). Refaça também
com typedef struct circulo * Circulo;
Funções tipo Set não são
necessárias porque você já deu
valores na ocasião da criação.
Seriam necessárias se fosse
Circulo * circCria(void);
TAD Circulo – Módulo – circulo.c (incompleto)
#include <stdlib.h>
#include "circulo.h"
#define PI 3.14159
struct circulo { Ponto * p; float r; };
Circulo * circCria (float x, float y, float r) {
Circulo * c = (Circulo *)malloc(sizeof(Circulo));
if (c!=NULL) { c->p = ptoCria(x,y);
c->r = r; if (c->p==NULL) return NULL;}
return c;
}
void circLibera (Circulo * c) {
ptoLibera(c->p);
free(c);
}
float circArea (Circulo * c){
return PI*c->r*c->r;
}
int circInterior (Circulo * c, Ponto * p){
float d = ptoDist(c->p,p);
return (d<c->r);
}