Programar em C++/Encapsulamento

Origem: Wikilivros, livros abertos por um mundo aberto.

Conceito[editar | editar código-fonte]

Encapsulamento, em linguagens orientadas a objeto, é a capacidade de ocultação de detalhes de implementação por parte de entidades de manipulação de dados. Esta característica visa prover um meio de manter cada classe responsável por operações a elas atribuídas sem interferências externas. A vantagem dessa característica é de manter os indivíduos de cada classe com funções bem delimitadas e criar meios de criar módulos onde cada classe faça bem aquilo de que está encarregada, tendo total controle sobre tais operações.

Atributos de restrição[editar | editar código-fonte]

Classes podem proteger sua estrutura de acessos de outras entidades de dados que não pertencem a seu corpo. Para isto, em C++ temos atributos de acesso para blocos de dados ou funções membros de classes. Em algumas linguagens os atributos são definidos para cada membro individualmente.

Os atributos de restrição de acesso em C++ são três: private, public e protected. Cada atributo oferece um nível de ocultação para membros de classes. Eles são usados para assegurar que apenas grupos identificados de código tenham acesso a partes presselecionadas da classe.

Os níveis de proteção estão ligados ao parentesco do código que pretende acesso com a classe em que os mesmos estão definidos. Mais especificamente, classes que não são filhas da que pretendem acessar só poderão ter acesso a membros públicos, classes filhas terão acesso a membros protegidos (protected) ou públicos (public) e finalmente, nenhum código que não pertença a própria classe poderá acessar membros privados (private).

Classes derivadas (pequena introdução)[editar | editar código-fonte]

Precisamos dos conceitos básicos de herança para entender alguns conceitos de encapsulamento. Para não criarmos dependências circulares entre os tópicos, ou seja, para não dependermos de conceitos de herança que também precisa de tópicos de encapsulamento, faremos uma pequena introdução dos conceitos de classes derivadas antes de prosseguirmos com o nosso estudo de encapsulamento.

Uma classe pode ser estendida a partir de outra, ou seja, podemos reaproveitar um código já existente em uma determinada classe que já temos e criar uma nova classe com tudo que já existia na primeira, mais o que definirmos para a nova.

Vejamos um exemplo básico:

class veículo
{ 
  string cor;
  string combustivel;
  ...
  ...
};

class carro : public veiculo
{
 int nrodas;
 ...
 ...
 int mover( int nkilometros );

};

A segunda classe declarada possui a extensão ": public veiculo" a mais, esta parte refere-se a uma declaração de parentesco. De fato, ao declarar a classe desta forma estamos informando ao compilador que a classe veiculo é mãe da classe carro. Semanticamente, isto significa que a classe carro possui toda a estrutura da classe veiculo além de seus próprios membros.

Definindo acessos[editar | editar código-fonte]

Considerando o exemplo anterior, podemos observar que os atributos "cor", "combustivel", "nrodas" poderiam ser alterados em qualquer ponto do programa se tivéssemos usado a palavra struct, porém usando a palavra class algo de diferente ocorre, pois não podemos ter acesso a estes atributos, a menos que estejamos acessando-os através de funções definidas dentro das classes.

Em classes, o atributo private é definido por padrão, ou seja, os membros que não tenham definidos os seus atributos de acesso explicitamente, serão definidos como privados. Este comportamento revela a necessidade de resguardar os membros de uma classe através de atributos de restrições. Em C++, ao definir membros em uma classe antes da definição de qualquer atributo de restrição estamos definindo-os como privados (private).

Ainda levando em consideração o exemplo anterior, podemos definir atributos de restrições para grupos de membros e modificar o comportamento padrão da classe. Se definirmos public, os dados estarão acessíveis a qualquer parte do programa, o que é equivalente a coloca-los em uma estrutura com a palavra struct ao invés de dentro de uma classe. Se definirmos protected temos uma situação peculiar, apenas funções membro da classe ou de suas "filhas" poderão acessar dados da classe mãe.

Vejamos o exemplo anterior com algumas alterações:

class veiculo
{ 
  string cor;

  protected:
    string combustivel;
  ...
  ...
 public:
  bool altCor( string c )
       { if ( c == "vermelho" )
           { cor = c;
             return true;
           } 

         if ( c == "azul" )
           { cor = c;
             return true;
           } 

         if ( c == "prata" )
           { cor = c;
             return true;
           }

        return false; 
       }
};

class carro : public veiculo
{
 int nrodas;
 ...
 ...
 int mover( int nkilometros );

};

Neste exemplo definimos que o atributo cor não pode ser modificado, a não ser pela função altCor(), onde restringimos as cores a um conjunto que desejamos. Observe que ao tentar atribuir qualquer cor diferente de "vermelho", "azul" e "prata", receberemos um retorno "false". Assim, temos a possibilidade de controlar o comportamento do objeto criado através da restrição imposta.

O atributo "combustível" será manipulado livremente pela classe "veículo" e pela classe "carro", visto que o atributo protected dá visibilidade às classes derivadas de "veiculo". O mesmo mecanismo de controle visto no parágrafo anterior poderá ser implementado para acessos fora do escopo das duas classes, ou seja, para funções no escopo global ou em outras classes.

Escopos globais[editar | editar código-fonte]

Até aqui vimos os atributos de restrição sendo usados em classes para encapsular partes internas a elas, porém, existe outro mecanismo de encapsulamento muito útil em C++, os namespaces. Estes "espaços de nomes" são meios de delimitar áreas onde símbolos são usados, o que permite evitar que erros ocorram por coincidência de nomes.

A sintaxe para criação de um namespace é bem simples. Vejamos um exemplo de código, para observarmos os detalhes:

 namespace MeuEspaco
 { void print()
   { cout << "Função de imprimir no meu espaco" << endl;
   }
 }

 namespace EspacoAlheio
 {
  void print()
   { cout << "Função de imprimir no Espaco alheio" << endl;
   }
 }