O princípio aberto fechado, em inglês Open Closed Principle, ou somente OCP, é um dos princípios mais interessantes da OOP. Ele diz que:
“Entidades de software (classes, módulos, funções, etc) devem ser abertas para extensão mas fechadas para modificações” – Bertrand Meyer (1988)
Parece meio contraditório… Mas na prática isso significa que você deve ser capaz de estender uma classe sem modificá-la. Isso acontece com herança, quando você utiliza uma class abstrata, ou até uma não abstrata mas com métodos virtuais. Você estendende a classe base, sem modificá-la, mas completando seu comportamento.
O OCP é sugerido porque, uma vez concluída uma classe, ela é uma unidade funcional que, se alterada, fica sob risco de efeitos colaterais não esperados na aplicação (bugs). Você pode resolver boa parte do problema com testes, mas ainda assim está introduzindo um risco. Nada é mais sólido do que código testado em ambiente de desenvolvimento e produção.
Para aplicar o padrão, tudo começa com a identificação do que não deve mudar e do que pode mudar. As partes que podem mudar você deixa abstratas ou virtuais, ou separa com, por exemplo, uma associação, via padrão Strategy.
Por exemplo, suponha que, em um blog, eu tenha uma ação de postar um comentário. E quero que alguém seja notificado quando um comentário acontecer. No meu controller teria um método assim:
public ActionResult PostarComentario(int id, FormCollection collection) { //algum código new NotificadorSMTP().AvisarDoComentario(comentario); //mais algum código }
Só que, se eu quiser mudar o NotificadorSMTP por um NotificadorExchange, vou ter que alterar a classe de controller. Isso não é bom, porque, primeiro, em nada tem a ver com o controller, que é responsável pelo fluxo da interface gráfica, e segundo, o controller já está funcionando e testado. Se eu identifiquei que isso é uma mudança possível, não posso trabalhar com o notificador desta forma. Uma opção seria trabalhar com um Strategy com injeção de dependência e inversão de controle. Ficaria assim:
public class PostsController { private Notificador _notificador; public PostsController(Notificador notificador) { _notificador = notificador; } public ActionResult PostarComentario(int id, FormCollection collection) { //algum código _notificador.AvisarDoComentario(comentario); //mais algum código } }
Agora já posso trabalhar com o controlador sem depender do NotificadorSMTP, ou do NotificadorExchange. A classe apropriada, derivada da classe “Notificador” será passada.
Uma opção mais elegante seria trabalhar com Domain Events. Na hora de adicionar um comentário ao post este levantaria um evento de domínio:
public class Post : Entidade { //outros métodos, campos, propriedades, etc... public virtual void AdicionarComentario(string corpo, Comentarista comentarista) { var comentario = new Comentario(this, corpo, comentarista); DomainEvents.Raise(new ComentarioAdicionado(this, comentario)); //outras ações... } }
E eu teria então uma classe que assinaria o evento de domínio:
public class NotificadorPorEmailEmCasoDeComentarios : IEnderecadorDeEvento<ComentarioAdicionado> { public void EnderecarEvento(ComentarioAdicionado args) { var textoNotificacao = ...;//formatação _notificadorEmail.Notificar(args.Post.Autor.Email, textoNotificacao); } //outros métodos, etc... }
É uma opção bastante interessante, onde eu removo a responsabilidade de notificação do controller, já que ela não deveria estar lá desde o começo. Outro dia eu falo mais de eventos de domínios. É um tema que está na minha pauta faz tempo.
Pra completar, sugiro vocês darem uma olhada no artigo do Uncle Bob sobre o assunto, listado na página que deu origem também ao nome SOLID. O livro que deu origem ao termo, Object-Oriented Software Construction, do Bertrand Meyer, também vale a pena dar uma olhada.
Giovanni Bassi
Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.