Divisão de classes

Divisão de classesVocês se lembram do artigo sobre Regra Decrescente? No artigo, fiz uma breve menção ao SRP (Single Responsability Principle), um dos princípios incentivados pelo SOLID. Apesar de já conhecermos a importância deste princípio, o artigo de hoje visa realçar a relevância da divisão de classes na arquitetura de um projeto, prezando pela alta coesão. Acompanhe!

 

Criar classes com responsabilidades bem definidas é um dos pontos fortes do Clean Code e o principal propósito do SRP (Princípio da Responsabilidade Única).
Muitas vezes, ao desenvolver um software, algumas classes podem apresentar uma sobrecarga de responsabilidades, ou seja, fazer mais do que inicialmente foi projetada para exercer. Embora o motivo possa ser uma falha de abstração, essa situação também pode surgir através de constantes modificações no projeto ou refatorações arbitrárias.
A divisão de classe é uma técnica que consiste em identificar pontos no código que podem ser “desacoplados” de uma classe, buscando reduzir ou evitar responsabilidades excessivas e, evidentemente, trazendo coesão para a arquitetura de um projeto.

Bom, chega de enrolação e vamos partir para um exemplo prático!
O código abaixo faz uma operação bem básica: obtém os valores que estão em cada campo de texto e os atribuem em diferentes variáveis. Por fim, estes valores passam por uma validação. Se estiverem válidos, os dados são gravados, caso contrário, uma mensagem de aviso é exibida.

var
  Codigo: integer;
  Nome, Endereco, Cidade, CPF: string;
  DataNasc: TDateTime;
begin
  Codigo := StrToInt(EditCodigo.Text);
  Nome := EditNome.Text;
  Endereco := EditEndereco.Text;
  Cidade := EditCidade.Text;
  CPF := EditCPF.Text;
  DataNasc := StrToDate(EditDataNasc.Text);
 
  if not ValidarDados(Codigo, Nome, Endereco, Cidade, CPF, DataNasc) then
  begin
    MessageDlg('Preencha os dados corretamente!', mtWarning, [mbOK], 0);
    Exit;
  end;
 
  GravarDados(Codigo, Nome, Endereco, Cidade, CPF, DataNasc);
end;

No entanto, observe que 6 parâmetros são passados para a função ValidarDados e também para o método GravarDados. Essa quantidade de parâmetros, além de comprometer a objetividade do código, também dificulta a interpretação do método como um todo. Claro, o método acima é apenas uma demonstração, mas, imagine que, em um caso real, existam 20 variáveis referentes aos dados do cliente, implicando que teríamos que passar 20 parâmetros na função. Isso realmente seria correto?
Seguindo a mesma diretriz, suponha que um desenvolvedor irá utilizar a função ValidarDados em outra parte do código. Ao se deparar com a declaração da função, provavelmente a impressão inicial do desenvolvedor será: “Nossa, tenho que declarar 20 variáveis para passar como parâmetros para este método?”. Se você fosse este desenvolvedor, ficaria perplexo, não?

 

Oras, basta criar variáveis de classe!
Boa ideia! Se criarmos variáveis de classe, não será preciso declará-las dentro do método e, portanto, não será necessário passar parâmetros, já que essas variáveis estarão visíveis no escopo global da classe.
Bom, então vamos implementar essa ideia!

private
  FCodigo: integer;
  FNome: string;
  FEndereco: string;
  FCidade: string;
  FCPF: string;
  FDataNasc: TDateTime;
  ...
 
begin
  FCodigo := StrToInt(EditCodigo.Text);
  FNome := EditNome.Text;
  FEndereco := EditEndereco.Text;
  FCidade := EditCidade.Text;
  FCPF := EditCPF.Text;
  FDataNasc := StrToDate(EditDataNasc.Text);
 
  if not ValidarDados then
  begin
    MessageDlg('Preencha os dados corretamente!', mtWarning, [mbOK], 0);
    Exit;
  end;
 
  GravarDados;
end;

 

Mas espere aí… ainda não me convenci da solução. Criamos 6 variáveis que estarão visíveis para todos os métodos da classe, mas apenas 2 destes métodos irão utilizá-las! Isso indica um certo “egoísmo” destes 2 métodos, não acha? Em uma analogia, é o mesmo que enviar um e-mail para 30 pessoas, porém, o conteúdo só interessar 2 delas.
Alguém provavelmente irá contestar: “Sua analogia está errada! No caso do e-mail, basta eu selecionar as 2 pessoas que se interessam pelo conteúdo e enviar a mensagem somente para elas.”. Certo, e por que não podemos aplicar este mesmo comportamento no nosso projeto? A analogia está certa, o que está errado é a nossa implementação! 🙂

Finalmente chegamos no âmago do artigo! Se existem apenas 2 métodos que utilizam as variáveis de classe que criamos, a recomendação é extrair estes métodos para uma classe separada, junto com as variáveis que eles utilizam. Essa é a divisão de classes citada no título deste artigo.
Ao realizarmos essa extração, definiremos adequadamente a responsabilidade de cada classe, reduzindo a baixa coesão. Além disso, as variáveis (que tanto nos preocupa) serão removidas do escopo global, como se estivéssemos removendo aquele conteúdo indesejado enviado para os outros 28 destinatários do e-mail.
Uma sugestão é criar uma nova classe (chamada “Cliente”, por exemplo) e transformar as variáveis em propriedades dessa classe. Os dois métodos, ValidarDados e GravarDados serão públicos e, então, poderão ser chamados a partir de outras classes:

type
  TCliente = class
  private
    FCodigo: integer;
    FNome: string;
    FEndereco: string;
    FCidade: string;
    FCPF: string;
    FDataNasc: TDateTime;
  public
    property Codigo: integer read FCodigo write FCodigo;
    property Nome: string read FNome write FNome;
    property Endereco: string read FEndereco write FEndereco;
    property Cidade: string read FCidade write FCidade;
    property CPF: string read FCPF write FCPF;
    property DataNasc: TDateTime read FDataNasc write FDataNasc;
 
    function ValidarDados: boolean;
    procedure GravarDados;
  end;

 

Finalmente, veremos como ficou a solução:

var
  Cliente: TCliente;
begin
  Cliente := TCliente.Create;
  try
    Cliente.Codigo := StrToInt(EditCodigo.Text);
    Cliente.Nome := EditNome.Text;
    Cliente.Endereco := EditEndereco.Text;
    Cliente.Cidade := EditCidade.Text;
    Cliente.CPF := EditCPF.Text;
    Cliente.DataNasc := StrToDate(EditDataNasc.Text);
 
    if not Cliente.ValidarDados then
    begin
      MessageDlg('Preencha os dados corretamente!', mtWarning, [mbOK], 0);
      Exit;
    end;
 
    Cliente.GravarDados;
  finally
    FreeAndNil(Cliente);
  end;
end;

 

Bem melhor, não é? Separamos as responsabilidades, aumentamos a coesão e melhoramos a arquitetura do programa.
O exemplo deste artigo é mais uma prova das vantagens que a Orientação a Objetos, Clean Code e SOLID podem nos proporcionar no desenvolvimento de um software. Assim que possível, pretendo elaborar mais artigos como este!

 

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

2 comentários

  1. var
    Cliente: TCliente; //<<— Aqui você acoplou a sua classe do dominia na sua camada de apresentação
    begin
    Cliente := TCliente.Create; //<<— Delegou ao metoda da apresentação mais uma resposabilidade …CRIAR
    try

    1. Olá, Lindemberg.

      Para a utilização de classes, o desenvolvedor não precisa necessariamente trabalhar com padrões de arquitetura, que dividem a classe a modelagem da apresentação, como é o caso do MVC, MVP, MVVM ou MOVE.
      O que poderia ser feito, nesse contexto, é criar um Factory para retornar a instância do cliente. Abordarei esse detalhe nos futuros artigos sobre Design Patterns e Injeção de Dependência.

      Abraço.

      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.