Rodolfo Richter
Nov 7 · 7 min read

Open Closed Principle (SOLID) e Outras Coisas

Nesse Post defenderei a utilização do segundo principio SOLID, o OCP (Open Close Principle), que traduzido fica “Princípio do Aberto e Fechado”, esse famoso princípio foi inventado por Bertrand Meyer, que pensava em meios de prolongar a vida de um programa.

Também mostro as voltas que um programador que não conhece SOLID tem que dar para chegar em um resultado adequado, e o custo referente a essa falta de conhecimento.

O código mostrado no POST pode ser baixado do meu Repositório no GitHub .

Teoria
OCP estabelece que: “Entidades de software (classes, módulos, funções, etc.) devem ser Abertas para extensão mas Fechadas para modificação.”

Extensão → No mundo da Orientação a Objeto o ato de estender significa que você criará um novo tipo derivado por meio de Herança ou delegará a necessidade de uma nova funcionalidade para um “Extension Method”, subscrevendo funcionalidades ou criando novas funcionalidades de apoio para aquele tipo.

Modificação → No contexto do OCP é realizar uma alteração numa funcionalidade de classe já entregue em algum módulo superior e já funcional em produção, adaptando-a a um novo requisito que impactará em módulos dependentes.

Portando, um design que permite estender uma funcionalidade permitindo a adição de novo código e não a exclusão de código antigo é interessante, e um design que não está preparado para isso é ruim.

A aplicação do OCP visa evitar alterações em uma classe, pois alterações no contexto exposto podem introduzir bugs e dificultar a manutenção.

Problemas que ocorrem quando esse princípio é violado:

  1. Alteração de um Método em uma classe onde o OCP foi violado:
    1.0. O programador sem uma visão global do sistema descarta uma funcionalidade que precisa continuar existindo em detrimento de um novo requisito, ou seja, essa funcionalidade que ainda é utilizada por outros módulos ou mesmo por outros sistemas integrados pode sumir;
    1.1. Bugs surgirão em outros módulos e até mesmo outras aplicações;
  2. A função Alterada/Excluída ou Renomeada se possuir testes unitários, estes provavelmente serão comentados para fazer a aplicação compilar, logo, depois do problema número 1 ser corrigido, é possível que não seja lembrado de retornar o seu teste, e o sistema perderá cobertura de testes;

3. Necessidade de refazer a classe base utilizando OCP e refatorar todo o sistema para atender à modificação;

4. Aquele tempo economizado na hora da concepção da classe será pago agora, mas o custo será mais alto.

5. Caso OCP não tenha sido utilizado em nenhum lugar, supondo que grandes e variadas alterações sejam solicitadas, certamente o tempo de vida desse software foi reduzido drasticamente.

Na vida de um software alterações são solicitadas o tempo todo, e se no momento de sua concepção não houve preocupação com conceitos básicos como o OCP, essas alterações, muitas vezes até simples, podem impactar negativamente ou causar um caos.

Vantagens relacionadas à Qualidade de Software:

  • Tempo de Manutenção reduzido;
  • Facilidade na implementação de novas funcionalidades;
  • Tempo de vida da Aplicação prolongada;
  • Prevenção de Bugs na Aplicação;

Vantagens relacionadas à Qualidade da Equipe:

  • Programadores melhores;
  • O Cultivo de boas práticas favorece uma preocupação com qualidade de software e incentiva a descoberta de novos conhecimentos.

Ilusão de Desvantagens

  • Maior tempo de Concepção/Design de Classes;

A longo prazo esse tempo gasto vale a pena, pois a correção de um sistema em estado de caos é muito maior e isso não é só uma hipótese, irá acontecer.

Uma analogia pode ser feita entre a preparação necessária para concepção de uma classe e para realização de uma Cirurgia; um cirurgião precisa efetuar procedimentos relacionados a esterilização de suas mãos, a instrumentadora precisa garantir a esterilização dos instrumentos cirúrgicos, tudo isso demanda tempo, mas sem isso é inevitável o fracasso.

Exemplificarei por meio de uma classe que não segue o principio, explorarei evoluções incorretas do problema e posteriormente formas de aplicá-lo.

O exemplo é uma classe que filtra objetos do tipo Book na Memória

*Na época da solicitação o Analista de Negócios disse que queria uma classe que filtrasse Books que fossem referentes ao assunto Philosophy.

O Desenvolvedor então desenhou a classe abaixo da forma mais rápida possível e com os conhecimentos que tinha na época. Veja:

:(

Problemas mais óbvios:

  1. Um método que se chama Filter e obtém os Books da categoria Philosofy; o famoso método mentiroso;
  2. Quem chama esse método não tem a menor ideia do que ele faz de fato, precisará entrar dentro da classe para saber;
  3. Violação do princípio OCP;

*Dois meses depois o Analista de Negócios disse ao Desenvolvedor que seria necessário filtrar os Books por qualquer Categoria.

O Desenvolvedor então desenhou a classe abaixo da forma mais rápida possível e com os conhecimentos que tinha na época. Veja:

:(

Problemas mais óbvios:

  1. Persiste a Violação do Princípio OCP;
    - Não é possível estender a funcionalidade;

*Um mês depois o Analista de Negócios disse ao Desenvolvedor que seria necessário filtrar os Books por qualquer Categoria e pelo idioma.

O Desenvolvedor então desenhou a classe abaixo. Veja:

:(

Problemas mais óbvios:

  1. Persiste a Violação do Princípio OCP;
    - Não é possível estender a funcionalidade;
  2. Duplicação de código sutil.

*Um mês depois o Analista de Negócios disse ao Desenvolvedor que seria necessário filtrar os Books por qualquer Categoria e pelo idioma, mas que deveria haver a possibilidade de fazer isso de forma simultânea.

O Desenvolvedor então desenhou a classe abaixo. Veja:

:( :(

Problemas mais óbvios:

  1. Persiste a Violação do Princípio OCP;
    - Não é possível estender a funcionalidade;
  2. Código muito confuso.

*Um mês depois o Analista de Negócios disse ao Desenvolvedor outro profissional, que efetua a publicação também perguntou se ele não poderia fazer de uma forma mais flexível, pois todas as vezes ele precisa subir as DLLs de todos os Assemblies da Solução.

O Desenvolvedor então desenhou a classe abaixo, que de certa forma é bem mais flexível que as anteriores, como a função recebe um predicate a extensibilidade é passada por meio parâmetro, mas é adicionada extensibilidade somente para o where, mas não para o método, por isso ainda viola o princípio de OCP.

Lembre-se que o Desenvolvedor não sabia que todas essas alterações seriam solicitadas, claro, alguns cuidados que ele não teve eram óbvios, mas é recomendado contar com o inesperado quando se planeja uma classe.

:|

Tendo consciência que provavelmente poderia ter esquecido de algo, o Desenvolvedor conversou com Desenvolvedores mais experientes e criou a classe abaixo:

:| :)

Agora sim a classe está Aberta para Extensão e Fechada para Modificação, mas essa classe não parece ter nada especial para merecer um tipo derivado, e também ela não adiciona significado para o negócio. Veja:

Esses métodos não dizem nada sobre o negócio, se for necessário achar o local onde determinada requisição é realizada é preciso interpretar os predicates.

Criando uma versão que força a implementação de filtros diferentes para cada opção, por meio de uma interface, que também possibilitará a utilização de outras características da Orientação a Objeto e não viola o OCP (Não viola porque não há possibilidade de nenhuma implementação anterior, como uma classe abstract que só tenha métodos abstract).

Estende direto da Interface
Estende direto da Interface

Depois ele criou essa outra versão e acabou também alterando a interface para utilizar Generics, e a coleção para algo menos específico:

Das formas anteriores, apesar de atenderem o OCP, não há pista nenhuma para um outro desenvolvedor de como é a forma que ele prefere que seja implementado, em uma classe o desenvolvedor pode usar um if com Yeld, outro um if com foreach, outro linq etc…

Alterou o tipo de retorno para algo menos específico.
Faz checagem de NULL para o predicate, poderia fazer também para a list.
Faz a checagem usando uma “Feature” mais recente e inclui um construtor protected, pois é uma classe Abstract.

Acima, como usou uma classe abstract, e utilizou template method inserindo o abstract method, existe um impulso na direção da forma de implementação que ele quer que seja feita, e caso necessário também pode fazer override do método todo.

Depois que pegou o gosto por refatoração, fez essa outra, onde ele também já previne o Predicate estar nulo com um novo tipo e também muda para Func para evitar o Invoke:

Faz a checagem de NULL utilizando a classe NonNullable (Jon Skeet) e altera o Predicate para Func evitando o Invoke.

Também é possível aplicar o OCP utilizando Extension Methods

Interessante que fica sem acoplamento nenhum, acoplamento muito fraco. Mas não seria a minha preferência no momento da criação, talvez em uma emergência.

As chamadas ficariam assim:

Para o exemplo dado, eu considero mais semântico usar Specification, adiciona mais significado ao código, ficaria assim:

E as chamadas ficariam dessa forma:

Na Solution criada para esse exemplo, que você pode baixar a mesma pesquisa será realizada usando todas as opções a partir da não violação do princípio. Uma janela como essa deverá aparecer:

Obrigado pela oportunidade de ajudar e fique a vontade para me mandar críticas.
Use o conhecimento com cautela, vou terminar com uma frase do livro Princípios, Padrões e Práticas Ágeis em C# de Robert C. Martin, que é “Resistir à abstração precipitada é tão importante quando a abstração em sí.”

Fontes:
Livros: Princípios, Padrões e Práticas Ágeis em C#— Robert C. Martin, Design Patterns em C# — Judith Bishop, Design Patterns in .NET — Dmitri Nesteruk, John Skeet e vários textos sobre o assunto na Internet, além de claro, minha próprio experiência e criatividade.

Abraços.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade