quinta-feira, 26 de junho de 2014

Compreender a “Dependency Injection” (I)

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