SOLID – Open/Closed Principle (OCP)

Olá, pessoal!
Algumas vezes, a solução mais rápida para codificar uma funcionalidade ou corrigir um erro é adicionar mais um Else-If em uma estrutura já existente, não é?
Bom, não sempre. Apesar de funcional, esse tipo de prática viola o segundo princípio do SOLID, chamado de Open/Closed Principle.
Continue lendo o artigo para entender essa violação e como eliminá-la!

Introdução

O Open/Closed Principle – ou OCP – inicialmente pode parecer um pouco contraditório, mas é bastante simples de compreender.

O princípio define que “entidades de software devem estar abertas para extensão, mas fechadas para modificação”, com o propósito de reduzir estruturas condicionais e, consequentemente, a complexidade ciclomática. A finalidade, na verdade, é permitir que entidades possam receber novos comportamentos sem necessariamente sofrerem alterações excessivas no código.

Neste contexto, “entidades” se referem a classes, módulos, funções, componentes, bibliotecas ou qualquer outra unidade sujeita a alterações no software. Na prática, porém, o OCP geralmente é aplicado na modelagem de classes do projeto para aprimorar a arquitetura.

Um dos objetivos ao utilizar o OCP é combater o crescimento de estruturas If no código-fonte, como no exemplo abaixo:

Considere que apenas a condição “D” é verdadeira. Para chegar até essa avaliação, o fluxo de processamento precisa testar as condições “A”, “B” e “C”. No melhor cenário (que eu chamaria de “cenário de sorte”), a avaliação dessas condições testa apenas variáveis locais. No entanto, na pior das hipóteses, os testes podem acessar o banco de dados ou serviços externos, comprometendo o desempenho da aplicação. Pode-se afirmar, então, que estes fluxos de dados sucessivos aumentam a complexidade do código, além, claro, de deixar o código feio! 😀

Há alguns anos, tomei conhecimento de uma técnica de arquitetura que jamais esqueci. Durante o treinamento com um arquiteto de software, os Ifs sucessivos foram ilustrados como flechas apontando para a direita, descrevendo o fluxo de um código:

Estruturas condicionais representadas por flechas

Segundo ele, quando o código chega à esse nível, devemos girar as flechas para a direita, de modo que elas fiquem verticais, para representar classes:

Subclasses representadas por flechas

Isso significa que cada condição deve ser “transformada” em uma classe por meio de herança. Como resultado, as condições são eliminadas do código e cada comportamento é movido para uma classe exclusiva, satisfazendo não só o OCP, mas também o Single Responsibility Principle apresentando no artigo anterior.

Exemplo prático

O exemplo do OCP envolve um cenário relativamente comum. Considere uma classe que realiza algumas operações em um banco de dados Firebird, como selecionar os primeiros 100 registros de uma tabela e retorná-los em JSON:

O uso da classe é simples. Nada de especial.

Sabemos que a cláusula First para selecionar as primeiras ocorrências é um comando particular do Firebird, certo? O que aconteceria, então, se um novo cliente solicitasse que a aplicação trabalhasse com Oracle? O comando SQL retornaria um erro, informando que o comando First não existe.

Bom, a classe terá que ser modificada para que a rotina funcione. Basta apenas parametrizar o método, incluindo uma condição para executar o SQL conforme o SGBD selecionado:

Ficou feio, não é? Mas vai piorar um pouco mais…

Por questões comerciais, nas próximas versões a aplicação também deverá trabalhar com o Microsoft SQL Server e PostgreSQL. Neste caso, um parâmetro boolean já não é mais o suficiente. A classe deverá ser modificada para trabalhar com os quatro SGBDs. Para isso, imagine que a tipo do parâmetro foi substituído por string, recebendo o nome do SGBD selecionado:

String Literals, várias condições If… há muita coisa errada aí. Vocês notaram também que destaquei a palavra “modificada” duas vezes nos parágrafos anteriores? O objetivo é enfatizar que a classe sofreu duas modificações conforme novos requisitos foram solicitados. Mas, espere aí… como é mesmo a definição do OCP?

“Software entities should be open for extension, but closed for modification”
(Entidades de software devem estar abertas para extensão, mas fechadas para modificação)

A classe acima não está fechada para modificação, já que foi necessário alterar o método para cada novo SGBD. Logo, ela quebra o Open/Closed Principle.

Aplicando o OCP

Lembram-se da técnica de converter condições If em classes? É isso que faremos!

Em primeiro lugar, a classe TDataBaseLayer será transformada em uma classe base, declarando um método chamado GetFirstRecordsSQL como abstrato:

Em seguida, para cada condição existente, será declarada uma classe herdada de TDataBaseLayer para implementar o método GetFirstRecordsSQL:

Com essa pequena reestruturação de classes, o OCP já deixa de ser violado. A classe TDataBaseLayer está aberta para extensão (cada tipo de SGBD é uma herança) e fechada para modificação (novos SGBDs não exigem alterações na classe base). Dessa forma, caso seja necessário trabalhar também com MySQL, por exemplo, a classe TDataBaseLayer não seria modificada. Ao invés disso, criaríamos uma nova extensão da classe! 🙂

Ainda não terminamos. Precisamos ainda ajustar o consumidor dessa funcionalidade.
O próximo passo é eliminar as String Literals, declarando os SGBDs como um tipo enumerado:

Agora, codificaremos um Factory Method (opa!) para retornar a instância da classe de acordo com o SGBD selecionado.

Observe o nível de abstração ao definir o retorno do método como o tipo da classe base, tornando-o “genérico” para todos os SGBDs. Em tempo de execução, uma das classes filhas será instanciada de acordo com o parâmetro informado.

Por fim, na classe cliente, não há muita alteração. Apenas substituímos a criação do objeto pelo Factory:

Bem melhor! Além de satisfazer o Open/Closed Principle, o código fica mais profissional, não acham? 🙂

Pessoal, vale ressaltar que o exemplo desse artigo é bastante simples, desenvolvido apenas para demonstrar a aplicação do OCP. Em ambientes reais, com classes extensas e regras de negócio complexas, o OCP traz grandes benefícios! Garanto!

Conclusão

Viram que usei um Case para selecionar a classe referente ao banco de dados, certo? Case também é uma estrutura condicional. No entanto, no código anterior, era necessário replicar as instruções If para cada vez que precisássemos verificar o tipo do SGBD. Por exemplo, se o método SelectCurrentDate fosse criado para retornar data atual do servidor do banco de dados, as quatro instruções If seriam necessárias para executar a SQL correta. Com o OCP, a única estrutura condicional estará no método DataBaseFactory. Uma vez retornada a instância da classe do SGBD desejada, não será necessário, em momento algum, utilizar instruções If para verificar o tipo do banco de dados novamente.

Vamos fechar com as vantagens:

  • Reduz a complexidade ciclomática da arquitetura, eliminando condições If;
  • Facilita a manutenção, já que cada classe possui uma responsabilidade única;
  • A adição de novas condições (neste caso, um novo SGBD) não exige a modificação da classe base. Basta somente criar uma nova herança;
  • Contribui para a arquitetura sustentável do projeto, possibilitando evoluções sem comprometer outras funcionalidades.

Uma última dúvida: como você usou o “AsJSONObject”?
Este método é um Class Helper de um framework muito útil desenvolvido pelo Ezequiel Juliano para converter DataSets em JSON e vice-versa. Para utilizá-lo, acesse o link abaixo do GitHub:

https://github.com/ezequieljuliano/DataSetConverter4Delphi

 

Obrigado pela atenção, leitores!
Vejo vocês na letra “L” do SOLID.


 

André Celestino