Open Closed Principle aplicado ao Angular
O princípio Open Closed ou Aberto/Fechado, é um dos 5 cincos princípios que formam o famoso acrônimo SOLID. Cada letra é representada por um princípio de design de código voltado ao paradigma da Orientação a Objetos.
O Open Closed Principle, defende que as classes devem estar abertas para extensão, mas fechadas para modificação. Temos como ganho uma arquitetura mais flexível, reduzindo drasticamente o impacto nas mudanças realizadas no software.
Vamos tomar como exemplo uma aplicação de vendas, onde temos dois perfis: um administrador que tem acesso completo e o comercial que tem um acesso mais limitado.
No código acima podemos ter alguns problemas de design. A princípio pode parecer simples, mas conforme as regras crescem (por exemplo: inclusão de novos perfis ou até mesmo uma outra regra adicional), mais frágil e suscetível a bugs essa classe se torna. Portanto, aqui estamos quebrando o princípio do Aberto/Fechado.
Podemos refatorar esse código, tornando-o mais coeso. A primeira ideia é criar uma abstração da classe de vendas. Note aqui o ponto mais importante: Vendas NÃO deve conhecer sobre perfis. Deixamos essa classe fechada para modificações.
Agora criamos mais duas classes com responsabilidades bem definidas: Uma para venda do perfil administrador e outra para venda do perfil comercial. As duas herdam da classe de vendas (ou poderiam implementar a interface, caso prefira evitar herança) e precisam ter os métodos dela, porém cada uma com sua regra específica.
Perceba que agora temos a classe de vendas aberta para extensões. Podemos estender em quantos perfis fossem necessários sem quebrar o contrato da classe principal.
É curioso dizer que esses exemplos foram extraídos de um outro artigo que escrevi sobre o Princípio da Inversão de Dependência, letra D do SOLID. Por mais que aqui o princípio seja outro, o conceito por trás de um bom design de código não muda, mostrando que todos os princípios conversam entre si.
Trazendo o cenário para os componentes do Angular
Aproveitando esse mesmo contexto de vendas, vamos imaginar um componente de card que renderiza alguns dados financeiros. Se o perfil for administrador deve mostrar de um jeito, se for comercial deve mostrar de outro. Veremos primeiro a implementação que infringe o Open Closed Principle:
Fica muito claro que temos no componente exatamente o mesmo problema que tínhamos com a classe: várias condições que não deveriam existir. Para nos adequarmos ao princípio, precisamos deixá-lo fechado para modificações, ou seja, remover as condições com *ngIf
. Precisamos também encontrar uma forma de deixá-lo aberto para extensão.
Projeção de conteúdo
Vamos entender a Projeção de conteúdo (ou Content Projection) extraindo sua definição da documentação do Angular:
A projeção de conteúdo é um padrão no qual você insere, ou projeta , o conteúdo que deseja usar dentro de outro componente. Por exemplo, você pode ter um componente
Card
que aceita conteúdo fornecido por outro componente.
Esta é a solução perfeita para aplicarmos o Open Closed Principle. Utilizamos o <ng-content>
para esperar um conteúdo a ser projetado que venha de fora. Ganhamos coesão dentro do componente card e ao mesmo tempo flexibilidade para os componentes externos.
Excelente! Trouxemos a mesma prática mostrada anteriormente agora para o contexto de componentes no Angular. O Template HTML está fechado para modificação.
Mas podemos deixar esse código melhor. Em VendaCardComponent
temos um Output visible
que por sua vez invoca um método para visualizar informações que somente o perfil administrador tem acesso. Mais uma vez não estamos respeitando os princípios do SOLID. Esse código apresenta um forte acoplamento para o componente.
Para limparmos o nosso componente e deixá-lo coeso, primeiramente precisamos expor o Output visible
. É neste momento também em que tornamos ele aberto para extensão.
Agora sim eliminamos qualquer regra de perfis dentro do componente de card. O método para visualizar informações privilegiadas poderá ser anexado de forma externa, como por exemplo em uma diretiva.
Utilizando o decorator HostListener
, podemos ouvir o Output visible
que nosso componente de card emite e executar nosso código. É uma prática muito poderosa e pouco conhecida!
Agora podemos adicionar a diretiva apenas onde precisamos. No caso ela será necessária apenas para os componentes de perfil administrador. Os componentes de perfil comercial nem precisam saber da existência desta diretiva.
Veja como é possível trazer vários conceitos envolvendo boas práticas para o Angular. Nesse caso temos o conceito da Composição aplicado de forma bastante pragmática.
Trocamos as condições que dificultavam a manutenção no código por um padrão modular. A ideia é que nossos componentes comportem-se como “plugins”, adicionando algo novo sem que isso interfira no comportamento do que já existe.
Solução alternativa com Injeção de Dependência
Podemos ter o mesmo resultado aplicando o Element Injector em nossa diretiva. A vantagem dessa prática é que deixamos mais explícita a fonte do nosso Output para que a função seja aplicada. A desvantagem é que geramos um vínculo da diretiva com o componente de card, perdendo sua flexibilidade e a possibilidade maior de reuso.
Conclusão
Neste artigo podemos entender o Princípio Aberto/Fechado e quais são os benefícios de utilizá-lo em nosso código. Além disso, foi possível aplicar este conceito dentro do contexto do Angular, tirando proveito de seus recursos como a Projeção de Conteúdo e a Injeção de Dependência com diretivas.