Introdução
Ao ler documentos, artigos ou livros sobre programação, com certeza já se deparou com o termo Dependency Injection (Injecção de Dependência).
Mas o que é exactamente Dependency Injection (DI)? E porque queremos usar DI?
Acontece que a DI é um pattern extremamente útil para o desenvolvimento de aplicações, com algum grau de complexidade. Em pequenas aplicações, o uso da DI tem um benefício reduzido, podendo, mesmo, criar complexidade desnecessária.
O principal resultado da utilização de DI é a obtenção de um sistema com Baixo Acoplamento (loose coupling), o que resulta em benefícios em termos de extensibilidade, testabilidade e late binding.
Compreender a Dependency Injection
Vamos iniciar uma série de artigos que nos ajudarão a compreender o que é a DI e os seus benefícios e veremos alguns exemplos de aplicação.
Parte I – Apresentação e Conceitos Base (este artigo)
Parte II – Introdução prática da DI
Parte III – Boa programação da DI
Parte IV – Contentores IoC – Exemplos de Aplicação
Nesta primeira parte, vamos começar por especificar alguns dos conceitos base relacionados com a DI.
Na segunda parte, através da análise de exemplos simples, vamos motivar a introdução do conceito de DI e verificar alguns benefícios da sua utilização. Na primeira parte, temos uma descrição mais teórica dos conceitos, na segunda, vamos introduzir a DI na prática.
Na terceira parte, vamos analisar algumas armadilhas quando se tenta gerir dependências. Vamos verificar como o desenvolvimento convencional nos pode deixar com código fortemente acoplado (mesmo quando pensamos que temos uma boa separação de conceitos). Vamos ver então como a adição da DI permite resolver este problema.
Finalmente, vamos apresentar alguns Contentores de IoC, com exemplos de configuração e aplicação.
Ao longo dos artigos serão feitas referências aos princípios S.O.L.I.D., que consistem num conjunto de 5 princípios da Programação Orientada a Objectos (POO), definidos por Robert C. Martin. Não entraremos em detalhes sobre estes princípios, sendo isso tratado futuramente noutro artigo.
Esta série de artigos apresenta apenas uma pequena introdução à DI, pretendendo destacar a utilidade e vantagens da sua utilização, bem como alguns exemplos de aplicação. Para um conhecimento mais aprofundado sobre o tema, recomenda-se a pesquisa e leitura da extensa bibliografia online.
Conteúdo
O que é a Dependency Injection
Um dos problemas com a DI é que existem dezenas de definições diferentes, que provocam uma confusão que se alastra à terminologia, objectivos e mecanismo.
Uma definição possível é a seguinte:
A DI é um padrão de desenho de software que permite que a escolha de componentes seja feita em run-time, ao invés de compile-time.
Infelizmente, esta definição, embora formalmente correcta, está um pouco incompleta. Menciona decisões em run-time (também chamadas de late binding), mas a DI é muito mais do que isso.
Vejamos outra definição:
Dependency Injection é um conjunto de princípios e padrões de desenho de software que nos permitem desenvolver código com Baixo Acoplamento.
Seemann, Mark, Dependency Injection in .NET, Manning, 2012
Esta definição é muito melhor. A DI tem tudo a ver com a criação de sistemas com baixo acoplamento, o que permite o late binding, entre outras coisas.
Então, o que pretendemos, nesta série de artigos, é ver como podemos criar código com Baixo Acoplamento. |
Baixo Acoplamento, porquê?
Porque devemos criar código com baixo acoplamento?
Porque isso nos oferece uma preciosa ajuda numa quantidade de áreas. Eis algumas:
Extensibilidade
Extensibilidade representa a facilidade em adicionar novas funcionalidades ao código. A palavra "facilidade" significa que podemos fazer actualizações em locais específicos, sem que isso signifique ter que actualizar pedaços ao longo de todo o código base.
Late Binding
Conforme já mencionado, late binding é a capacidade de escolher quais os componentes que usamos em runtime, em vez de compile-time. Isto só é possível se o código tiver baixo acoplamento – o código só se preocupa com abstrações em vez de um tipo concreto em particular. Isso permite trocar componentes sem a necessidade de modificar o código.
Desenvolvimento Paralelo
Se o código tiver baixo acoplamento, torna-se mais fácil ter várias equipas de desenvolvimento a trabalhar no mesmo projeto. Podemos ter uma equipa a trabalhar na camada de negócio, e outra a trabalhar na camada de serviços. Como as camadas são independentes, as equipas estarão a trabalhar em código fonte diferente, que não afeta diretamente o outro.
Facilidade de Manutenção
Quando os componentes são independentes, a funcionalidade é isolada. Isto significa que se for necessário descobrir bugs ou ajustar a funcionalidade, sabemos exatamente onde procurar.
Testabilidade
Os Testes Unitários (Unit Testing) são um tema extremamente importante. O principal objetivo é testar pequenas unidades de código em isolamento. Quando temos código com baixo acoplamento, podemos facilmente criar dependências simuladas (mock/fake), de modo que podemos facilmente isolar as partes do código que realmente desejamos testar.
Inversion of Control (IoC)
Muitas pessoas referem-se a DI como Inversion of Control (Inversão de Controlo). Estes dois termos são por vezes usados como sinónimos, mas DI é um subconjunto de IoC. Cabe aqui fazer uma pequena desambiguação entre os conceitos DI e IoC.
O termo Inversion of Control, originalmente, significava qualquer estilo de programação onde uma framework global ou o próprio runtime controlava o workflow de execução da aplicação. Isto era o oposto do que acontecia na programação procedimental.
Quando desenvolvemos uma aplicação web, seguimos o lifecycle das páginas ASP.NET, mas não estamos no controlo, a framework do ASP.NET é que está. Quando desenvolvemos um serviço WCF, podemos estar a escrever o código do serviço, mas não estamos no controlo, a framework WCF é que está.
Hoje em dia estamos tão habituados a utilizar frameworks (muito comuns na programação .NET, ou Java), que deixou de se dar um significado especial, mas não é a mesma coisa que ter o controlo total da execução, como acontece, por exemplo, numa aplicação de linha de comandos, mesmo no mundo .NET.
Antes da DI ter um nome definido, era comum chamar-se às frameworks que geriam dependências como Inversion of Control Containers e rapidamente o termo IoC passou a subentender-se como inversão de controlo sobre dependências. Mais tarde Martin Fowler introduziu o termo Dependency Injection para designar especificamente IoC no contexto de gestão de dependências. Desde então, DI tem sido aceite como a terminologia mais correcta.
Em resumo, IoC é um termo mais abrangente, que inclui, mas não está limitado à DI.
Padrões de desenvolvimento de Dependency Injection
A implementação da DI pode ser executada recorrendo a um variado número de design patterns:
- Contructor Injection
- Property Injection
- Method Injection
- Ambient Context
- Service Locator
Nesta série de artigos vamos olhar em especial para a Constructor Injection, que é o principal padrão utilizado. O leitor poderá debruçar-se sobre os outros padrões, quando se sentir mais confortável com a DI.
Isto pode parecer um pouco complexo e poderá estar a equacionar se realmente vale a pena envolver-se na DI. Na realidade, estes padrões e princípios não são assim tão complicados. Vamos efectuar calmamente o nosso caminho, de modo a termos uma boa ideia do que vai acontecendo.
No próximo artigo: Compreender a “Dependency Injection” (II), as coisas vão ficar um pouco mais claras, quando começarmos a criar componentes e a aplicar a DI, verificando as suas vantagens.
Sem comentários:
Enviar um comentário