Outro preceito importante é o de encapsulamento de dados ou information hiding. É até anterior à OO, importante também em outros paradigmas de programação. Trata-se de não expor detalhes de como uma entidade é implementada; expor apenas a interface da entidade. Em OO, isto significa, ter poucos (idealmente, nenhum) dados públicos (no sentido semântico de públicos). Um excelente livro sobre programação que eu li, e agora me escapa qual, dizia a este respeito que devemos seguir o conselho ouvido na infância: "Don't show your private parts."
Perco as contas de quantas vezes vejo isto tudo ser deixado de lado. Por exemplo quando fazem algo assim:
class Horario
{
unsigned hora;
unsigned minuto;
public: unsigned getHora();
unsigned getMinuto();
void setHora(unsigned hora); void setMinuto(unsigned minuto);
};
main()
{
Horario t0, t1, t2, t3;
t0.setHora = 10;
t0.setMinuto = 10;
// um pouco mais de código, inicializando tn e fazendo sabe-se mais o que....
// então imprime alguns t:
cout << t2.getHora() << ':' << t2.getMinuto() << endl;
cout << t3.getHora() << ':' << t3.getMinuto() << endl;
}
estão jogando no lixo os preceitos mencionados acima.
O information hiding foi pro vinagre, pois embora os membros não sejam declarados como públicos, semanticamente eles são totalmente públicos: usuários da classe podem fazer o que bem entenderem com eles... Os getXXX e setXXX geram o problema.
Além disso, o preceito básico da OO foi pro espaço também. Notem que as operações (inicialização e "impressão") sobre os objetos estão sendo feitas FORA do objeto (fora da classe).
Melhor seria algo como:
class Horario
{
unsigned hora;
unsigned minuto;
public:
Horario(unsigned h, unsigned m): hora(h), minuto(m) {};
string paraString()
{/*converte para string no formato hh:mm, possivelmente usando ostringstream*/};
//Eventualmente outras operações com Horarios, mas nada de getXXX ou setXXX
};
main()
{
Horario t0(10,10);
Horario t2(5,11);
Horario t3(23,55);
// um pouco mais de código, inicializando tn e fazendo sabe-se mais o que....
// então imprime alguns t:
cout << t2.paraString() << endl;
cout << t3.paraString() << endl;
}
Ou, para este caso de "impressão", usando um operador de inserção em stream:
class Horario
{
friend ostream& operator<<(ostream& s, const Horario& h);
unsigned hora;
unsigned minuto;
public:
Horario(unsigned h, unsigned m): hora(h), minuto(m) {};
//Eventualmente outras operações com Horarios, mas nada de getXXX ou setXXX
};
ostream& operator<<(ostream& s, const Horario& h)
{
s << h.hora << ':' << h.minuto;
return s;
}
main()
{
Horario t0(10,10);
Horario t2(5,11);
Horario t3(23,55);
// um pouco mais de código, inicializando tn e fazendo sabe-se mais o que....
// então imprime alguns t:
cout << t2 << endl;
cout << t3 << endl;
}
IMPORTANTE!!!!!! Nesta implementação acima, a classe e o operator<< devem estar no mesmo módulo. Ou seja, as declarações de ambas no mesmo .h (horario.h, por exemplo) e as implementações de ambas no mesmo .cpp (horario.cpp, por exemplo). Notem que quebramos um pouco do information hiding nesta implementação, já que o operator<< precisa acessar os dados privados da classe. Quer dizer, a classe não mostra as suas "partes privadas" para todo mundo, mas mostra para os "amiguinhos". Não é o ideal e não me agrada, mas até pode ser aceitável em algumas situações, desde que classe e operador tenham uma "união estável" bem forte e a classe tenha poucos "amiguinhos" (entidades friend).
Eu prefiro uma implementação que inclui o encapsulamento da paraString() e a conveniência do operador<<: Implemento o operator<< chamando o paraString(). Neste caso, a declaração de friend fica desnecessária.