Vocês já ouviram falar do SRP (Single Responsability Principle)? Trata-se de um dos princÃpios incentivados pelo SOLID, que prega a responsabilidade única de classes e métodos. 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!
Introdução
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 classes é 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.
Um breve cenário
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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. O que acha?
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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? Em uma analogia, é o mesmo que enviar um e-mail para 30 pessoas, porém, o conteúdo só interessar a 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 pensamento no nosso código? A analogia está certa, o que está errado é a nossa implementação! 🙂
A Divisão de Classes na prática
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 TCliente
, 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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!
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
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!
Óla Prof, escrevo a partir de angola, gostaria que publicasses no seu blog como usar o modelo mvc, estou há ter mutos problemas .
Obrigado pela atenção
Olá, Woza! Saudações do Brasil! 🙂
Woza, já tenho um artigo sobre MVC com Delphi, mas está muito desatualizado. Foi publicado há 7 anos.
Por conta disso, pretendo refazer o artigo utilizando técnicas mais avançadas, como RTTI, Generics, e Design Patterns.
O blog está meio desatualizado devido à pandemia, mas pretendo retomar em breve.
Abraço!
Muito bom dia André!
Tenho olhado alguns artigos seus – e Brother – você está de parabéns – sou um aprendiz na área da programação, mas com seus artigos a coisa está ficando mais clara. Vi artigos de outras pessoas e na verdade são muito complexos. Você escreve e que deve ser feito de forma simples e objetiva – continue assim – Parabéns novamente!
Olá, Franklin, boa tarde!
Muito obrigado pelo feedback! A minha intenção é justamente essa: “descomplicar” a programação.
Pretendo continuar o trabalho em breve! Abração!