programação ii módulos, encapsulamento e tadsbfeijo/prog2/progii_tad.pdf · • módulo é um...

17
Programação II Módulos, Encapsulamento e TADs Bruno Feijó Dept. de Informática, PUC-Rio

Upload: tranhuong

Post on 07-Dec-2018

218 views

Category:

Documents


0 download

TRANSCRIPT

Programação II

Módulos, Encapsulamento e

TADs

Bruno Feijó

Dept. de Informática, PUC-Rio

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);

}