Os princípios SOLID

Um conjunto de boas práticas na programação orientada à objetos.

Muito tem se falado sobre SOLID nos últimos anos. Em entrevistas de empregos, ofertas de trabalho esse assunto sempre vem a tona. Quando se pede um código para análise do potencial do candidato, e o código fere um ou mais desses princípios, certamente é mal visto.

SOLID é um acrônimo, que remete à princípios no desenvolvimento orientado à objeto. Criado por Robert C. Martin, o “Uncle Bob” nos anos 2000. O acrônimo, entretando, foi criado por Michael Feathers, em cima dos princípios descritos por Robert.

Single responsability principle (SRP) - Princípio da responsabilidade única
Open/closed principle (OCP) - Princípio aberto fechado
Liskov substitution principle (LSP) - Princípio da substituição de Liskov
Interface segregation principle (ISP) - Princípio da segregação de interface
Dependency inversion principle (DIP) - Princípio da inversão de dependência

Neste primeiro artigo iremos abordar o Single responsability principle.

Este princípio pode ser considerado o principal princípio, pois os demais princípios acabam dependendo dele. Caso este seja violado, praticamente inviabiliza os demais.

Mas vamos ao que interessa. Este princípio diz que uma classe deveria ter apenas um motivo para mudar (a class should have only one reason to change).

Alguns autores expandem esse conceito para módulos e até métodos. O importante é entender que a responsabilidade deve ser única, ou seja, quando criar uma classe, entenda o propósito dela, e siga até o fim. Se uma classe serve para cadastrar uma pessoa, esta classe não deve também enviar o e-mail confirmando o cadastro. Se uma classe tem a função de gerar um relatório do “ponto” de um funcionário, esta mesma classe não deveria estar calculando os débitos ou créditos deste relatório.

Entendido a teoria, vamos à prática, afinal “talk is cheap, show me the code” (Linus Torvalds)

Para explicar este processo vou apresentar exemplos de violação do princípio e exemplos onde foi aplicado corretamente.

A classe a seguir mostra um esboço de uma impressão de relatório do ponto de um funcionário. O funcionário acessa uma funcionalidade via sistema web, coloca seus dados e manda imprimir. O sistema irá exibir na tela todos os dias do período, horários de entrada e saída e o crédito ou débito em horas e minutos de cada dia que o funcionário trabalhou. Ao final irá exibir o saldo de banco de horas desse funcionário, para mais ou para menos.

<?php
class RelatorioPonto
{
public function imprimir(FichaPonto $ficha, $dtInicio, $dtFim)
{
echo "<h1>Relatorio</h1>";
$pontos = $ficha->getPontos($dataInicio, $dataFim);
foreach($pontos as $ponto) {
echo $ponto->data . "...";
}

$bancoHoras = $this->calculaBancoHoras($ficha);
echo "Saldo do banco de horas: " . $bancoHoras;
}

private function calculaBancoHoras($ficha)
{
//calcula o banco de horas do funcionario e retorna valor
}
}

Na classe acima, temos um método imprimir que recebe uma instância de FichaPonto , classe responsável pelo tratamento do ponto do funcionário, e um período para consulta. A partir daí, pega os pontos do período e exibe. Ao término, exibe também o banco de horas daquela ficha.

Neste temos uma violação do princípio da responsabilidade única. A classe RelatorioPontodeve ter apenas um objetivo, e consequentemente apenas um motivo para mudar.

Por exemplo, caso o relatório deva ser gerado em formatação diferente, mais ou menos dados, outro layout HTML, em PDF, etc, o método imprimir sofreria mudança. Isso seria um motivo para essa classe ser alterada. Sua responsabilidade é pegar dados calculados e apenas exibir para o usuário. Até aqui, tudo conforme esperado.

Por outro lado, imagine que nesta empresa onde o sistema roda, as horas só poderão ir para o banco de horas caso o gestor autorize. Neste caso, esta classe estaria mudando também por esse motivo, o que viola o princípio. A alternativa aqui é jogar a responsabilidade deste cálculo para outra classe.

<?php
class RelatorioPonto
{
public function imprimir(FichaPonto $ficha, $dtInicio, $dtFim, BancoHoras $bancoHoras)
{
echo "<h1>Relatorio</h1>";
$pontos = $ficha->getPontos($dataInicio, $dataFim);
foreach($pontos as $ponto) {
echo $ponto->data . "...";
}

$saldoBancoHoras = $bancoHoras->calculaBancoHoras($ficha);
echo "Saldo do banco de horas: " . $bancoHoras;
}
}
class BancoHoras
{
public function calculaBancoHoras(FichaPonto $ficha)
{
//calcula o banco de horas do funcionario e retorna inteiro
}
}

Neste exemplo, o cálculo é colocado corretamente em outra classe. Caso a regra de cálculo mude, a alteração é feita na classe BancoHoras . Caso o relatório precise ter alteração de layout, a mudança fica na classe RelatorioPonto .

Porém, se você observar, a classe Relatório ainda possui uma lógica para buscar os pontos e calcular o banco de horas, o que gera um acoplamento. Alterações no método getPontos ou calculaBancoHoras terá impacto nessa classe, o que mostra o acoplamento. Além disso, essa classe não precisa saber como buscar pontos ou calcular banco de horas, deve apenas receber os parâmetros corretos e exibir. Sendo assim vamos refatorar:

<?php
class RelatorioPonto
{
public function imprimir($pontos, $saldoBancoHoras)
{
echo "<h1>Relatorio</h1>";

foreach($pontos as $ponto) {
echo $ponto->data . "...";
}

echo "Saldo do banco de horas: " . $saldoBancoHoras;
}
}

Pronto, agora a classe recebe apenas os parâmetros necessários e exibe os dados para o usuário. O cálculo desses itens são um requisito para o relatório, porém fica a cargo de quem chama este método.

Então é isso, o importante é sempre entender, na essência, a responsabilidade de cada classe e não ter medo de criar classes novas. Muito vejo desenvolvedores com receio de criar muitas classes achando que aumentará a complexidade do código mas a verdade é justamente o oposto, aumenta a clareza no código e diminui o acoplamento.

Os códigos estão no repositório: https://github.com/felippeduarte/medium/tree/master/solid

Espero que tenham gostado. No próximo artigo falaremos sobre o Open/closed principle.

Referência: Robert C. Martin

--

--