Uma das principais características das linguagens orientadas a objetos é prover mecanismos para a implementação de códigos seguros, ou seja, é garantir que as variáveis (campos) de uma classe recebam exatamente os valores que se espera que elas recebam.
No C#, podemos implementar o encapsulamento de duas formas: através de métodos que acessam e atualizam os valores dos campos e através de propriedades (que é a forma recomendada). Neste artigo veremos as duas abordagens.
Implementando o encapsulamento através de Métodos
A primeira forma que veremos neste artigo para se realizar a implementação do encapsulamento na linguagem C# é através dos métodos. Para que possamos entender este conceito, imagine que precisa-se posicionar determinado componente na tela. Este controle de posição é realizado através de coordenadas x e y. Imaginando que o intervalo válido para x é aquele que possui valores de 0 a 1280 e que o intervalo para y é aquele que possui os valores de 0 a 1024, analisemos o código apresentado na Listagem 1:
struct PosicaoDeTela
{
public PosicaoDeTela(int x, int y)
{
this.X = verificaIntervaloX(x);
this.Y = verificaIntervaloY(y);
}
public int X;
public int Y;
private static int verificaIntervaloX(int x)
{
if(x < 0 || x > 1280)
{
throw new ArgumentOutOfRangeException("x");
}
return x;
}
private static int verificaIntervaloY(int y)
{
if(y < 0 || x > 1024)
{
throw new ArgumentOutOfRangeException("y");
}
return y;
}
}
Listagem 1: Exemplo de estrutura para solução do problema proposto
Muito embora estrutura apresentada na Listagem 1 funcione, ela possui um grave problema: os campos X e Y são públicos. Note, quando os valores são passados para estas variáveis através dos métodos, os valores que estão sendo passados sofrem uma verificação de validade, ou seja, os métodos garantem que os valores que estão sendo passados são válidos. Entretanto, quando definimos X e Y como públicos, damos poder para que estes campos sejam acessados de forma direta, como apresenta a Listagem 2.
// código anterior
//
//
PosicaoDeTela origemPlanoCartesiano = new PosicaoDeTela(0,0);
//
// continuação
//
int posicaoX = origemPlanoCartesiano.X;
origemPlanoCartesiano.Y = -43;
//
//
// código posterior
Listagem 2: Exemplo de utilização da estrutura e erro na atribuição
Ao analisarmos a Listagem 2 fica evidente o problema com este tipo de abordagem, basta observarmos a linha 9. Ao acessar diretamente o campo da estrutura um valor inválido foi passado e, como não há um filtro, um erro ocorrerá em tempo de execução.
Para solucionar este problema bastaria fazer a alteração apresentada na Listagem 3.
struct PosicaoDeTela
{
private int X;
private int Y;
public PosicaoDeTela(int x, int y)
{
this.X = verificaIntervaloX(x);
this.Y = verificaIntervaloY(y);
}
private static int verificaIntervaloX(int x) { ... }
private static int verificaIntervaloY(int y) { ... }
public int retornaX()
{
return this.X;
}
public int atribuiParaX(int novoX)
{
this.X = verificaIntervaloX(noxoX);
}
public int retornaY()
{
return this.Y;
}
public int atribuiParaY(int novoY)
{
this.Y = verificaIntervaloY(noxoY);
}
}
Listagem 3: Implementando métodos para acessar os campos
Mas o que foi feito na Listagem 3? Uma mudança simples. Os campos X e Y da estrutura que na Listagem um apresentavam-se públicos agora tornaram-se privados (portanto só podem ser acessados por elementos internos à estrutura) e, para que estes elementos possam ter seus valores “setados” e retornados, novos métodos foram criados para este fim específico (retornaX, atribuiParaX, retornaY e atribuiParaY). Com esta estrutura, garantimos que valores incorretos não serão passados aos campos.
Muito embora a estrutura apresentada na Listagem 3 garanta a validade dos dados e implemente a segurança do código, duas características indesejávis se apresentam: a estruta fica descaracterizada em termos de acesso a campos e, além disso, pagamos um preço em termos de performance em função das chamadas constantes a métodos para efetuar operações simples. Por exemplo, uma operação simples de incremento, implica em primeiro efetuar a leitura (através do método) e depois a atualização (através de outro método). Este processo é apresentado na Listagem 4:
// código anterior
//
//
int posicaoX = origemPlanoCartesiano.retornaX();
origemPlanoCartesiano.atribuiParaX(posicaoX + 10);
//
//
// código posterior
Listagem 4: Acessando e atualizando os campos através dos métodos
Você deve estar se perguntando neste ponto: Se campos públicos quebram o encapsulamento e acesso aos campos com métodos são caros computacionalmente e descaracterizam a classe ou estrutura, como devo proceder? Resposta: Utilize “Propriedades”.
O que são Propriedades?
Uma propriedade nada mais é do que um recurso da linguagem C# que permite combinar algumas características de Campos e outras de Métodos. Na verdade, uma própriedade é um campo que atua como um método para o compilador. A Listagem 5 apresenta um exemplo de declaração de propriedade no C#:
ModificadorDeAcesso Tipo NomePropriedaede { get { //código de leitura da propriedade } set { //código de atribuição da propriedade } }
Listagem 5: Modelo de declaração de uma propriedade no C#
Como você pôde perceber, uma propriedade possui dois blocos: o bloco get e o bloco set. O bloco get é onde o desenvolvedor pode implementar ações quando o valor da propriedade é lido. Já o bloco set é onde podemos implementar as ações quando o valor da propriedade será gravado. É importante lembrar que, assim como em um campo simples, uma propriedade também possui modificador de acesso (public, private, etc.), um tipo (o tipo da propriedade especifica o tipo de dado lido e gravado). Vale observar também que uma propriedade é acessada como se fosse um campo comum e o compilador fará a conversão necessária para que ele trabalhe como um método.
A Listagem 6 apresenta a implementação do exemplo apresentado anteriormente utilizando propriedades.
struct PosicaoDeTela
{
public PosicaoDeTela(int X, int Y)
{
this.x = verificaIntervaloX(X);
this.y = verificaIntervaloY(Y);
}
public int X
{
get { return this.x; }
set { this.x = verificaIntervaloX(value); }
}
public int Y
{
get { return this.y; }
set { this.x = verificaIntervaloY(value); }
}
private static int verificaIntervaloX(int x) { ... }
private static int verificaIntervaloY(int y) { ... }
private int x, y;
}
Listagem 6: Exemplo utilizando propriedades
Olhando o código da Listagem 6 não é difícil entender-mos o porque é importante utilizarmos propriedades para manipular campos. As propriedades implementam todas as ações dentro de sí próprias, fato este que garante a segurança da mesma forma como dito anteriormente e em uma única estrutura.
Para utilizarmos as propriedades basta fazermos como apresentado na Listagem 7:
//código anterior
//
//
PosicaoDeTela origemPlanoCartesiano = new PosicaoDeTela(0,0);
int posicaoX = origemPlanoCartesiano.X; //chama o método get da propriedade
int posicaoY = origemPlanoCartesiano.Y; //chama o método get da propriedade
//
//
origemPlanoCartesiano.X = 40; //chama o método set da propriedade
origemPlanoCartesiano.Y = 10; //chama o método set da propriedade
//
//
//código posterior
Listagem 7: Utilizando as propriedades criadas
Como é possível observar na Listagem 7, é extremamente simples acessar e gravar valores para os campos através das propriedades. Com isso, não descaracterizamos a classe ou estrutura e não efetuamos chamadas manuais sucessivas a métodos, ficando esta responsabilidade a cargo do compilador.
O exemplo utilizado neste artigo foi retirado do livro Visual C# 2008 – Passo a Passo.
Bom pessoal, é isso. Espero que este artigo possa tê-lo ajudado a compreender melhor a importância de utilizarmos as propriedades para implementarmos o encapsulamento nas aplicações .NET. 🙂 . Por favor, deixe seu feedback através dos comentários beleza?
Grande abraço a todos!
Facebook
Twitter
Instagram
LinkedIn
RSS