
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:
- 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; - 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:
- Um método que se chama Filter e obtém os Books da categoria Philosofy; o famoso método mentiroso;
- Quem chama esse método não tem a menor ideia do que ele faz de fato, precisará entrar dentro da classe para saber;
- 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:
- 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:
- Persiste a Violação do Princípio OCP;
- Não é possível estender a funcionalidade; - 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:
- Persiste a Violação do Princípio OCP;
- Não é possível estender a funcionalidade; - 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).
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…
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:
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.