Abordando o encapsulamento

Abordando o encapsulamentoA Programação Orientada a Objetos disponibiliza um conjunto amplo de recursos para desenvolver um software, centralizado em 4 pilares. Um deles, o encapsulamento, é extensivamente utilizado durante a modelagem das classes. Embora alguns desenvolvedores associem o encapsulamento somente aos conceitos de getters e setters, ele representa mais do que isso!

 

Enquanto criamos as classes do nosso software, a nossa intenção é ocultar os detalhes da implementação, principalmente para evitar comportamentos inesperados. Por exemplo, se a minha classe expuser e permitir a alteração de variáveis ou métodos internos, o seu funcionamento pode ser comprometido. É o mesmo conceito de um disco rígido: para evitar o mau funcionamento, os componentes do disco rígido são “encapsulados” em uma caixa de metal, evitando que sejam manuseados internamente. Nesse mesmo sentido, para proteger as propriedades internas da nossa classe, utilizamos o encapsulamento.

Bom, quando nos referimos ao encapsulamento, as primeiras palavras que nos vem à mente são getters e setters, não é? Estes dois métodos, também conhecidos como seletores e modificadores, evitam que as variáveis de uma classe sejam acessadas diretamente de outras classes. Em outras palavras, somente é possível obter ou atribuir um valor a uma variável de uma classe através dos getters e setters.
No entanto, eu faço questão de levantar uma crítica. Considere que uma classe tenha um atributo chamado “Porcentagem” que deve ser encapsulado. Muitos desenvolvedores declarariam a variável como “FPorcentagem” e os métodos como “GetPorcentagem” e “SetPorcentagem”, concorda? Porém, do que adianta criar estes métodos se o próprio nome da variável está explícita? Basta olhar na declaração dos métodos para descobrir que há uma variável na classe relacionada à porcentagem. Conceitualmente, é como se a variável fosse pública.
Claro, na implementação do getter ou setter, o desenvolvedor pode implicitamente trabalhar os dados obtidos ou atribuídos, mas isso não poupa a classe da exposição de sua estrutura.

 

Mas isso não é encapsular os dados?
Encapsulamento não significa simplesmente adicionar uma camada de acesso para as variáveis. Em vez disso, deve-se trabalhar em conjunto com a abstração de dados. O objetivo, na realidade, é permitir que outras classes trabalhem com o conteúdo dos dados, sem necessariamente conhecer como eles são tratados dentro da classe.

Com base no mesmo exemplo anterior sobre a porcentagem, considere o código:

private
  FPorcentagem: real;
  FCodVendedor: integer;
public
  function GetPorcentagem: real;
  procedure SetPorcentagem(const Value: real);
  function GetCodVendedor: integer;
  procedure SetCodVendedor(const Value: integer);
end;

Observe que a implementação acima, além de expor as variáveis, exige que elas sejam manipuladas separadamente. Para calcular a comissão de vendas de um vendedor, por exemplo, é necessário preencher os dois atributos da classe, caso contrário, o cálculo será inválido. Porém, não há nenhuma condição que obrigue o desenvolvedor a preenchê-los antes de calcular a comissão.

 

E o que você sugere?
Se aplicarmos o encapsulamento apropriadamente, este seria o código:

private
  FPorcentagem: real;
  FCodVendedor: integer;
public
  function CalcularComissaoVendedor(Porcentagem: real; CodVendedor: integer): real;
end;

Bem melhor, não acha?
Posso apontar três vantagens nessa abordagem:

  1. As variáveis da classe não são expostas;
  2. O método obriga a passagem da porcentagem e o código do vendedor simultaneamente;
  3. Facilita a manutenção da classe, já que as variáveis são acessadas apenas internamente.

 

Focando um pouco mais na praticidade, o desenvolvedor pode mesclar métodos públicos que compartilhem do mesmo assunto, resultando em um encapsulamento mais evidente. Como exemplo, o desenvolvedor pode substituir estes métodos:

function ObterTotalPedidosPendentes: integer;
function CalcularEstimativaLucro(TotalPedidosPendentes: integer): real;

Por este:

function CalcularEstimativaDosPedidosPendentes: real;

Dessa forma, o método já obteria o total de pedidos pendentes, realizaria os cálculos necessários e calcularia a estimativa de lucro de uma só vez. Tudo estaria em um alto nível de encapsulamento, ou seja, não saberíamos quais e quantas variáveis estão sendo manipuladas dentro do método! Claro, na implementação interna da classe, este método pode (e deve) ser desmembrado em vários estágios para não afetar a legibilidade.

Se um mesmo método encapsulado deve ser implementado em várias classes diferentes, o desenvolvedor pode ainda utilizar Interfaces para padronizar as implementações e favorecer futuras manutenções no código.

Antes de fechar o artigo, gostaria de frisar que não sou contra a utilização de getters e setters. Pelo contrário, eles são extremamente úteis e eficientes, desde que utilizados com moderação. Entretanto, nós, desenvolvedores, devemos ter consciência ao representar a estrutura de dados de nossas classes externamente. Ao invés de meros métodos de acesso, talvez o ideal seja contemplar a abstração de dados e ocultar devidamente nossas implementações.

 

Leitor, espero que este artigo tenha contribuído para o seu conhecimento!
Abraço e até a próxima!


 

Compartilhe!
Share on FacebookTweet about this on TwitterShare on LinkedInShare on Google+Pin on PinterestEmail this to someone

8 comentários

  1. Olá André, quero parabenizá-lo pela ótima legibilidade de seus artigos.

    Adorei suas analogias, principalmente a deste artigo, relacionando o disco rígido à uma classe para explicar o encapsulamento, isso ajuda e muito no entendimento do assunto.

    Estou aprendendo muito com os posts do seu blog.

    Obrigado por compartilhar seu conhecimento, mas não apenas por isso, por passar de uma maneira simples e de fácil compreensão, sua escrita é bem agradável.

    Parabéns!

    1. Olá, Lucas!

      Agradeço fortemente pelo feedback e pela motivação!
      Fico contente em saber que está acompanhando o blog.
      A intenção é exatamente essa: compartilhar o conhecimento de uma forma descomplicada, sempre que possível.

      Grade abraço e um bom feriado!

  2. Olá André, estou gostando muito de suas dicas, você consegue explicar de uma forma simples onde não precisamos voltar e ler várias vezes para entender. Parabéns pelo conteúdo!

    Utilizo muitas funções ou métodos com parâmetros justamente para determinar que ali sou obrigado a informar algo. Lendo o seu artigo “Orientações sobre parâmetros de um método”, foi dito que quando temos muitos parâmetros seria melhor criar um objeto e passar apenas ele. Achei legal essa dica, porém temos como obrigar que um valor do objeto seja obrigatório?

    Talvez minha pergunta ficou meio confusa, mas vou tentar explicar melhor com um exemplo prático.
    Hoje tenho um módulo de NFE que é utilizado em vários sistemas (para padaria, metalúrgica, comércio, etc). Esse módulo é populado com dados enviados através de procedures como:

    procedure PreencherDadosEmitente(…..)
    procedure PreencherDadosDestinatario(…..)

    procedure AdicionarItemNFE(pCodProduto, pNomeProduto, pNCM, pCodBarras, pInfAdProd: String, pQtde, pValorUnitario, pValorTotal, pValorICMS: Double…….)

    Quando ocorre alguma mudança na legislação que obriga a informação de um novo campo, apenas adiciono o novo parâmetro e depois informo em cada sistema que utiliza esse módulo. Se eu não informar terei um erro na compilação. Como pode ver, a quantidade de parâmetros é enorme. Sinto que eu poderia de alguma forma melhorar isso. Você poderia dar uma sugestão?

    Obrigado e até mais!

    1. Olá, Cassiano, como vai?
      Muito obrigado pelo feedback sobre o conteúdo do blog! 🙂
      Entendi a sua pergunta, sim! Atualmente, utilizar da quantidade de parâmetros é muito comum nos projetos de software, porém, a quantidade excessiva pode prejudicar a legibilidade e manutenção do código. Por isso que recomendo a empregabilidade de objetos como parâmetros.

      No seu código, por exemplo, poderíamos criar a seguinte classe:

      TItemNFE = class
      private
        FCodProduto: integer;
        FNomeProduto: string;
        FNCM: string;
        FCodBarras: string;
        FInfAdProd: string;
        FQtde: integer;
        FValorUnitario: double;
        FValorTotal: double;
        FValorICMS: double;
      public
        property CodProduto: integer read FCodProduto write FCodProduto;
        property NomeProduto: string read FNomeProduto write FNomeProduto;
        property FNCM: string read FNCM write FNCM;
        property CodBarras: string read FCodBarras write FCodBarras;
        property InfAdProd: string read FInfAdProd write FInfAdProd;
        property Qtde: integer read FQtde write FQtde;
        property ValorUnitario: double read FValorUnitario write FValorUnitario;
        property ValorTotal: double read FValorTotal write FValorTotal;
        property ValorICMS: double read FValorICMS write FValorICMS;
      end;

      Veja como o código ficaria a passagem de parâmetro utilizando um objeto dessa classe.

      var
        ItemNFE: TItemNFE;
      begin
        ItemNFE := ITemNFE.Create;
        try
          ItemNFE.CodProduto := 1;
          ItemNFE.NomeProduto := 'Descricao';
          ItemNFE.NCM := 'NCM';
          ItemNFE.CodBarras := '123456789';
          ItemNFE.InfAdProd := 'InfAdProd';
          ItemNFE.Qtde := 10;
          ItemNFE.ValorUnitario := 50.00;
          ItemNFE.ValorTotal := 500.00;
          ItemNFE.ValorTotal := 10.00;
      
          AdicionarItemNFE(ItemNFE);
        finally
          FreeAndNil(ItemNFE);
        end;
      end;

      O código ficou ligeiramente mais extenso, mas, por outro lado, tornou-se mais expressivo.
      Vale ressaltar que a classe que criei como exemplo (TItemNFE) pode receber vários “aprimoramentos”, como Getters, Setters (que inclusive abordo no artigo), validações e tratamentos de dados.

      Espero ter ajudado, Cassiano!
      Abraço!

  3. Olá André, a respeito da dúvida do Cassiano, também fiquei curioso e decidi aprofundar.

    Creio que ele queria saber como validar se o objeto passado como parâmetro está vazio, creio que podemos usar:
    if Assigned(objeto) then …
    para saber se foi instanciado, mas para validar se as propriedades foram preenchidas, teríamos que percorrê-las e verificar seu conteúdo ? Alguma dica ?
    Obrigado

    1. Boa noite, Jean, tudo bem?
      Desculpe-me pela demora. Houve alguns problemas no servidor de e-mail e estou recebendo as mensagens hoje.
      Bom, Jean, para verificar se todas as propriedades estão preenchidas, é necessário, sim, validar o conteúdo de cada uma delas. No entanto, para evitar a inserção de várias condições If-Else, uma solução seria declarar as propriedades como Published. Dessa forma, pode-se utilizar o recurso de RTTI do Delphi para percorrer todas as propriedades utilizando somente uma condição For.

      Na verdade, você me deu uma boa ideia. Vou elaborar um artigo exemplificando o uso de RTTI para essa finalidade e publico no máximo até semana que vem!

      Fique no aguardo! Abraço!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Preencha o campo abaixo * Time limit is exhausted. Please reload CAPTCHA.