sexta-feira, 23 de novembro de 2007

Dica C++: Minimizando a dependência de arquivos - Parte 1

Algumas vezes nos deparamos com situações em que as declarações que construímos precisam fazer referência umas às outras, de forma a implementar um relacionamento entre ambas as partes. Quando esse relacionamento é modelado, geralmente desejamos colocar cada parte declarada num arquivo diferente, de forma que fiquem logicamente e fisicamente separados.

Se por exemplo criamos duas classes, A e B, criamos dois arquivos de declaração respectivos, A.h e B.h. Só que nossa classe A precisa usar um objeto da classe B, que por sua vez também precisa usar um objeto da classe A. Nesse caso, se A.h incluir B.h e B.h incluir A.h, claramente está sendo criado uma dependência circular, o que não é permitido (A.h irá incluir a declaração de B.h, que irá incluir a declaração de A.h, que írá incluir...).

Na maioria dos casos, os programadores são levados a declarar as classes num mesmo arquivo, digamos AB.h. Veja o exemplo abaixo:

Por questões de simplificação, as classes foram criadas ignorando uso de constantes, operadores, construtores, destrutores, etc.

Arquivo AB.h_______________________________________________

#ifndef AB_H
#define AB_H
        class B; // pre-declaracao
        class A
{
public:
B* GetB();
void SetB(B *b);
private:
B *_b;
};
        class B
{
public:
A* GetA();
void SetA(A *a);
private:
A *_a;
};
#endif

Arquivo AB.cpp_____________________________________________

#include "AB.h"
// class A
B* A::GetB()
{
return ( _b );
}
void A::SetB(B *b)
{
_b = b;
}
// class B
A* B::GetA()
{
return ( _a );
}
void B::SetA(A *a)
{
_a = a;
}

__________________________________________________________


Isso termina com o problema da dependência circular, mas também traz alguns efeitos indesejados:



  • No arquivo de declaração é perdida a abstração de A separado de B e vice-versa;
  • Não há como reutilizar A sem B, e vice-versa (o principal);

Todavia, há outras formas de resolver ou minimizar o problema e conseqüentemente seus efeitos.


Solução 1: Limitar à dependência da declaração antecipada


Se A não tem conhecimento dos detalhes (métodos, atributos e subtipos) de B e vice-versa, eles só precisam saber que deve haver a respectiva declaração de quem eles precisam em algum lugar. Onde ? Não importa. Só precisam saber que existe. É aí que está a solução.


Isto quer dizer que A não precisa conhecer a declaração de B especificamente, mas apenas saber que B existe.


Arquivo A.h________________________________________________

#ifndef A_H
#define
A_H
        class B; // pre-declaracao
        class A
{
public:
B* GetB();
void SetB(B *b);
private:
B *_b;
}; 
#endif
__________________________________________________
(O arquivo A.cpp conterá a implentação de A mostrada no arquivo AB.cpp) 
 

Arquivo B.h________________________________________________

#ifndef B_H
#define B_H
        class A; // pre-declaracao
        class B
{
public:
A* GetA();
void SetA(A *a);
private:
A *_a;
}; 
#endif
__________________________________________________

(O arquivo B.cpp conterá a implentação de B mostrada no arquivo AB.cpp)


Em nenhum dos arquivos (de cabeçalho ou de implementação) é necessário especificar a declaração do tipo pré-declarado usado.


Assim, A e B tem um conhecimento praticamente abstrato um do outro. Cada um sabe que o outro existe, mas não sabe como ele é.


Com isso é aberta a possibilidade de reutilização de um mesmo arquivo em outros projetos, já que qualquer uma das partes pode variar que a outra continua não tendo conhecimento.


Vale lembrar que esta solução é válida pois as partes não se conhecem realmente (só se conhecem de nome). Quando precisarem se conhecer serão adotadas outras soluções, as quais veremos nas próximas partes deste modesto artigo. ;)

Nenhum comentário: