Inversão de controle Parte 1: IoC e seus principais Design Patterns

Mão humana interrompendo o fluxo de queda de uma fila de dominós

Olá, tudo bem? Este é o primeiro artigo de uma série sobre Inversão de Controle. Navegando pela internet, encontrei esta definição sobre IoC e decide que seria uma boa contribuição para a comunidade divulgar os seus pontos e mostrar suas aplicações. Neste artigo vamos entender o que é Inversão de controle (Inversion of Control ou IoC), quais são os seus principais Design Patterns e qual o seu impacto na arquitetura do seu software.

O que é Inversão de controle, afinal?

Há algum tempo, todo o fluxo da aplicação era descrito pelo código. Talvez você não seja dessa época, mas até o preenchimento de um formulário tinha que seguir uma ordem pré-determinada. O processamento dos dados era realmente como uma receita de bolo: executada linha após linha. O controle do fluxo da aplicação estava na ponta dos dedos do desenvolvedor.

Com o avanço das interfaces gráficas, não dava mais para controlar o fluxo do programa como antes. O usuário poderia clicar no botão de confirmação dos dados a qualquer momento; os campos do formulário poderiam ser preenchidos em qualquer ordem. O controle do fluxo foi invertido: ao invés do desenvolvedor, agora é o usuário quem controla a ordem dos acontecimentos.

Se você não pegou a indireta, vou te ajudar: O conceito de Inversão de controle quer justamente retirar do seu algoritmo a responsabilidade de controlar todo o fluxo do software e repassar para alguma entidade cliente. Essa entidade pode ser, de fato, um usuário; uma outra aplicação (chamando uma API ou função de call-back) ou um framework orquestrando mensagens, criando objetos e assim por diante.

Você pode estar pensando, então, que IoC é o mesmo que programar orientado a eventos. O que não é completamente errado. Mas seria uma forma muito limitada de definir IoC. Toda vez que você pulveriza o fluxo do algoritmo em várias entidades de código, você está fazendo IoC. Mas o que você ganha com isso?

Quais as vantagens da Inversão de Controle?

Para entendermos melhor as vantagens que a IoC pode nos proporcionar, vamos imaginar um programa que procura no disco por arquivos “.log” e os envia anexos em um e-mail. Em uma abordagem mais “tradicional”, você poderia escrever todo o fluxo em um trecho de código só. Ou se estiver começando com orientação a objetos, talvez você criasse uma classe para implementar essa lógica.

Só que você é dev de responsa! Sendo assim, para este caso, lembrou-se das boas práticas de desenvolvimento e decidiu aplicá-las a este pequeno projeto. Ao invés de um blocão de código, você vai ter toda uma estrutura responsável por cumprir o propósito do seu software. E então? Quais classes você acha que precisaria?

Você pode pensar, de cara, em classes para procurar arquivos em disco; uma classe para representar a mensagem a ser enviada e uma outra classe que faz o papel de enviar o e-mail propriamente dito. Assim você teria uma classe que, de fato, envia e-mail com os logs anexados. Mas… Surpresa! Os requisitos mudaram e agora você tem que enviar, também, uma mensagem no aplicativo de mensagens do administrador do sistema contendo o log. Ou seja: Sua arquitetura vai ter que mudar.

Para esse caso, você vai precisar de classes para: encontrar os arquivos de log; montar a mensagem com os arquivos anexos e por fim, uma classe para enviar a mensagem. Perceba que eu não disse enviar e-mail ou especifiquei qualquer tipo de comunicador. Isso não iria excluir, obviamente as classes especializadas em enviar e-mail, anexar as mensagens etc.

Agora pense quão complicado seria ter que reescrever todo esse algoritmo para cada tipo novo de requisito. Se as suas classes forem realmente responsáveis por controlar o fluxo e as dependências, pense em quantos If’s seriam necessários para atender a cada novo requisito! Não esqueça de colocar nesse “pequeno pesadelo” os testes unitários (lembra? Você é dev de responsa!); o acoplamento entre as classes; como fazer os mocks e stubs sem ter que criar novas classes apenas para o teste funcionar?

É aqui que o IoC faz a diferença. Classes não deveriam saber criar objetos. Essa missão deveria pertencer às fábricas ou até mesmo aos builders. Ou melhor ainda: um framework de IoC poderia construir os objetos por você e gerenciar o seu ciclo de vida. Você apenas sinaliza que precisa de uma instância e o framework decide por você. Você diminui o acoplamento e as chances de ter de alterar uma classe só porque a assinatura do construtor de uma dependência foi alterada. Sem falar dos testes, que você consegue injetar os mocks sem muita dor e testar efetivamente o código e a regra de negócio.

Quais padrões de projeto fazem parte da Inversão de controle?

Como eu disse há pouco, sempre que você pulveriza o fluxo dos algoritmos, você já está fazendo inversão de controle. Mas fazer isso de uma forma desordenada pode não ser uma boa ideia. Em casos como esse, o resultado seria uma pasta gigante, cheia de arquivos, que são apenas um recortar-colar de código. Isso mais atrapalha que ajuda. Para evitar esse cenário distópico, você conta com o apoio de diversos padrões arquiteturais e de design. Longe de oferecer conteúdo pronto, eles te ajudam encontrar soluções elegantes para o seu projeto.

Pensando em IoC, eu gosto de pensar em dois grandes grupos de padrões: Gestão de Dependência e Gestão de fluxo (ou estruturais). São nomes bastante sugestivos, não é mesmo?

O grupo de padrões de Gestão de Dependência compreende os padrões que, de várias maneiras, vão oferecer aos teus objetos clientes aquilo que eles dependem para funcionar (e não. Não estou falando de café). Pensando no nosso exemplo anterior, a classe que orquestra o envio de mensagens depende das classes que fazem o contato real com o meio de transmissão – um servidor smtp, por exemplo. Como seria isso? Ela teria que decidir qual objeto criar? Ela teria que criar as próprias dependências? Isso não iria gerar alto acoplamento? Para resolver esses problemas, temos à disposição os padrões Factory, Service Locator, Context Lookup e Dependency Injector.

Por sua vez, temos os padrões de Gestão de Fluxo, ou estruturais, como você preferir. Esses padrões fazem a gestão do fluxo, provocando alterações no algoritmo em tempo de execução. Isto se dá através da alteração das estratégias utilizadas para resolver um dado problema, ou utilizando o polimorfismo para resolver detalhes diferente de um caso de uso. Ou ainda, alterando o comportamento total do sistema ao manipular as mensagens que os objetos trocam entre si. Você já deve ter percebido que vamos falar dos padrões Strategy, Template Method e Observer.

Onde a minha jornada vai dar?

Ao concluir essa série de artigos, eu tenho certeza absoluta que você vai lembrar de algum outro pattern que eu não citei; algumas partes que podem ter sido ignoradas… Tudo bem. Isso é normal. Eu não tenho a menor pretensão de esgotar o assunto. Pelo contrário! Quero te mostrar o caminho das pedras para IoC de verdade. E ao final, quem sabe em um futuro próximo, eu não esteja lendo um artigo seu aqui no blog também, cobrindo as lacunas que ficaram para trás?

Então bora dobrar as mangas e encarar de frente essa tal de inversão de controle!

Outros artigos da série

2 thoughts on “Inversão de controle Parte 1: IoC e seus principais Design Patterns

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.