Front End

18 fev, 2013

Injeção de dependência com JavaScript

Publicidade

Se dermos uma olhada nos design patterns que ajudam a dissociar objetos em seus aplicativos, uma das técnicas mais simples para se usar é a injeção de dependência. Ela é uma prática comum em Backbone.js, principalmente ao atribuir modelos para views, mas não vi isso tanto quanto eu acho que deveria. Aqui, eu vou examinar o que é a injeção de dependência, como ela ajuda e como eu a estou inserindo para uma boa utilização no meu projeto mais recente.

O que é injeção de dependência?

A injeção de dependência é um design pattern (muitas vezes também conhecido como inversão de controle), em que é dada uma dependência a um objeto – seja através de um método setter ou através do constructor – e não do objeto que depende de ser criado dentro dele mesmo. Essa técnica pode ser utilizada em muitas situações diferentes. Isso pode ser notado pelo fato de que existem livros escritos inteiramente sobre injeção de dependência

Li um livro recentemente (Clean Code: A Handbook of Agile Software Craftsmanship – que é um livro surpreendente de ler para manter seu código limpo, legível e de fácil manutenção), e ele deu uma olhada em injeção de dependência, a partir do ponto de vista da construção de um hotel.

Em primeiro lugar, considere que a construção é um processo muito diferente do uso. Enquanto escrevo isso, tem um novo hotel em construção que eu posso ver pela minha janela, em Chicago. Hoje, é uma caixa vazia de concreto com um guindaste de construção e um elevador aparafusado ao exterior. Todas as pessoas ocupadas lá usam capacetes e roupas de trabalho. Em um ano ou mais, o hotel estará concluído. O guindaste e o elevador terão desaparecido. O edifício será limpo, envolto em paredes de janelas de vidro e pintura atraente. As pessoas trabalhando e se hospedando lá parecerão muito diferente também.

Os sistemas de software deveriam separar o processo de inicialização quando os objetos de aplicação são construídos e as dependências estão “ligadas” em conjunto, a partir da lógica de tempo de execução que toma conta após a inicialização.

Injeção de dependência é um dos mecanismos que podemos usar para criar a separação que está sendo falada nessa citação. Mas como exatamente isso é feito? Basta criar um objeto do qual outra coisa vai depender, então criar o objeto que vai depender dele e passar o primeiro objeto a ele. Essa formulação é um pouco difícil de fazer sentido, então vamos dar uma olhada em um exemplo de código rápido que inclui como foi feito anteriormente e como alterá-lo para usar injeção de dependência.

// Without Dependency Injection
var Foo = function() {
this.bar = new Bar();
}

var foo = new Foo();

// With Dependency Injection
var Foo = function(bar) {
this.bar = bar;
}

var bar = new Bar();
var foo = new Foo(bar);

Muito simples. Você vai notar que a injeção de dependência requer mais códigos. Bem, praticamente qualquer coisa que você faz para dissociar o código vai acabar com mais código no final, mas é a facilidade de manutenção e a flexibilidade que são realmente importantes. O que você não vê no exemplo é que não é necessário usar Bar. Nós podemos enviar em qualquer objeto que segue a mesma interface que Foo precisa que ele tenha. Se Foo não utiliza cada método único de Bar, você não precisa ter toda a interface que Bar tem, só precisa das partes que Foo usa, graças à flexibilidade do JavaScript.

Por que injeção de dependência é útil

Existem alguns bons motivos para usar a injeção de dependência, com os quais eu já tive um pouco de contato. Vamos dar uma olhada um pouco mais aprofundada em três das maiores razões: flexibilidade, reutilização, e testabilidade.

Flexibilidade

Como já mencionado, este método torna os objetos mais flexíveis. Eles não estão mais ligados a essa classe específica; agora você pode fornecer qualquer objeto que você quiser, desde que tenha uma interface que corresponda aos requisitos do objetos que dependem deles.

Uma coisa que pode não ser óbvia sobre isso é que você pode realmente injetar objetos que possuem comportamentos diferentes e/ou valores de retorno em relação a seus métodos, o que poderia finalmente mudar o comportamento do objeto dependente. Por exemplo, o objeto pode ser dependente de um classificador de array. O objeto que você injetar pode fornecer os meios para determinar em qual a ordem dos elementos da array devem estar (dizendo ao classificador quais elementos são mais/menos do que outros), o que mudaria completamente que a aparência da array classificada.

Você pode não precisar de toda essa flexibilidade, mas pode nunca saber quais mudanças precisarão ser feitas no futuro, e essa configuração permite que você tenha mais flexibilidade para mudar mais facilmente, caso esse objeto ou do qual ele é dependente precise ser mudado.

Em Backbone, views usam injeção de dependência para receber seus modelos. Isso permite que uma view mostre os dados de qualquer modelo, assumindo que tem a mesma interface (algumas propriedades para exibir e mesmos métodos para chamar). A view também não tem que depender de uma coleção da qual puxar o modelo, porque passamos o modelo em nós mesmos.

Reusabilidade

Um dos maiores motivos pelos quais a programação orientada a objeto foi criada foi para a reutilização de componentes. No entanto, temos a capacidade de criar objetos que não são muito reutilizáveis, porém, quando os criamos para atender a fins muito específicos, em vez de generalizá-los para atender a necessidades mais amplas. A injeção de dependência pode ajudar com isso.

Ao mover as especificações de implementações para um objeto que injetamos, podemos ampliar o alcance e a utilidade do objeto. Ele pode ser usado para várias situações, dependendo do objeto injetado, em vez de usar uma única implementação que pode atender apenas a uma única finalidade. Então, quando nós precisássemos de um objeto para servir a outros propósitos similares, você acabaria repetindo um monte de código criando um novo objeto, em vez de ser capaz de reutilizar o código construído no objeto original.

Além disso, uma vez que não é totalmente dependente de qualquer classe, ele pode ser reutilizado numa situação em que os objetos dos quais teria dependido não estão presentes. Objetos independentes são mais portáteis.

Testabilidade

Eu realmente tenho tentado obter uma pequena série de testes unitários, mas ainda estou aprendendo sozinho. Mike M Lin – um dos contribuintes do meu blog – já está trabalhando em um rascunho para uma introdução para testes unitários. Mas isso não significa que não podemos falar sobre ele um pouco aqui.

Quando você faz um teste unitário, você quer testar uma única unidade (daí o nome) e, na programação orientada a objetos, isso geralmente se refere a um único tipo ou objeto. Sem injeção de dependência, se um teste falhar, não saberemos se o problema está relacionado ao objeto que estamos testando ou a uma de suas dependências internas. Além disso, se um teste for aprovado, ele pode ainda não estar funcionando corretamente, porque a dependência interna pode ter nos dado a resposta errada, e então o objeto que estamos testando pode ter feito algo de errado com ele, porém isso ainda terminou sendo a resposta correta devido a dois erros, de alguma forma, fazendo isso corretamente. Garantir isso é extremamente improvável, mas não temos nenhuma confiança real de que isso não pode acontecer.

Se tivermos um objeto no qual podemos injetar uma dependência, podemos injetar um objeto falso (criado com o framework de testes unitários), que pode dar respostas estáticas que se aplicam a esse teste, para que possamos saber que estamos recebendo as respostas corretas da dependência interna, ou seja, podemos saber com certeza se o objeto dependente está funcionando corretamente.

Isso também nos permite inserir spies para garantir que o objeto falso seja usado de forma correta internamente. Embora isso não seja realmente necessário, ele pode te dar a garantia de que tudo está funcionando exatamente como planejado.

Como eu estou usando injeção de dependência

Você já pode ter aprendido muito, porém às vezes um exemplo do mundo real pode realmente ajudar a arrematar sua mente sobre um conceito e ajudar você a entender como e quando ele pode ser usado. Eu estive trabalhando recentemente em um projeto. Já demonstrei meu Minecraft Server Manager um tempo atrás, o qual decidi reconstruir completamente. A versão que eu mostrei era em grande parte apenas uma prova de conceito. Agora, estou trabalhando em uma nova versão com mais recursos, o que, na verdade, me obrigava a reescrever a maioria dos aplicativos, porque o anterior não foi construído para escalar bem (como eu disse, foi uma prova de conceito). Eu também queria tirar proveito de algumas bibliotecas que tinham sido lançadas desde que o original foi feito (principalmente Twitter Bootstrap e Backbone.Marionette).

De qualquer forma, vamos direto ao ponto: eu tinha decidido fazer esse aplicativo se comunicar entre os componentes quase inteiramente através de eventos globais acionados a partir do objeto de aplicação central (App.vent). Devido a isso, quase todos os componentes dependiam diretamente do principal objeto de aplicação. Isso significa que, para cada componente que eu testasse, eu teria que criar o objeto de aplicação real e instanciá-lo. Isso causou alguns problemas: acoplamento apertado, então não havia praticamente nenhuma maneira de eu abandonar Marionette, se eu já escolhi, e testes foram necessários para fazer um pull no mesmo App que eu estava usando na aplicação real, o que não deveria ser necessário.

Então, agora em vez de depender diretamente do App, decidi que eu iria passar App.vent para os constructors dos componentes. Isso me permite passar em um EventAggregator simulado para testar e me permite fazer alterações no aplicativo – até removendo o EventAggregator do App totalmente – sem qualquer alteração para os componentes que o utilizam.

Conclusão

A injeção de dependência é uma ótima maneira de dissociar os objetos. Ele cria flexibilidade, reutilização e fácil capacidade de teste, e não é difícil de fazer. Configurar o objeto pode te dar um pouco mais de trabalho, mas é por isso que eles inventaram factories. Obviamente, essa não é a única maneira de fazer essas coisas, mas é definitivamente uma grande arma para ter em seu arsenal.

***

Texto original disponível em http://www.joezimjs.com/javascript/dependency-injection-with-javascript/