Arquitetura de Informação

12 abr, 2011

Injeção de dependência: desacoplando sua aplicação

Publicidade

O desenvolvimento de software não é algo trivial, como costuma dizer meu amigo Vinicius Quaiato.
Se o software for algo “pequeno” (entende-se por pequeno aplicações de
pequeno porte), é possível que um ou outro desenvolvedor entenda que,
como não foram utilizados alguns padrões, o desenvolvimento daquela
aplicação é simples.

Entretanto, construir bons softwares (e bons programadores sabem
disso) requer uma série de características do desenvolvedores. E, a principal delas é o cuidado com a arquitetura.

Se você é o tipo de desenvolvedor que não se preocupa com a escrita de testes unitários, design pattern,
inversão de controle, etc., aqui vai um conselho: ou começa a se
preocupar com isso, ou seus sistemas tornar-se-ão legados
prematuramente.

É bem verdade que seja possível escrever aplicações
com a ausência de tais conceitos, entretanto, a reflexão que o
desenvolvedor deve realizar aqui é “este software, está bem escrito? A
manutenibilidade, é boa? É extensível? É testável?”

Se suas respostas às perguntas acima forem: não, não, não e não, será
preciso repensar a maneira como você escreve suas aplicações.

No artigo de hoje, veremos um destes conceitos fundamentais para a
escrita de aplicações bem elaboradas: o conceito de injeção de
dependência. E nesta primeira parte, implementaremos a DI (Dependency Injection) de forma manual, e logo depois utilizaremos uma ferramenta para sua implementação.

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

Trata-se de um design pattern que consiste na
passagem de uma classe para outra, sendo que esta última irá utilizá-la
(consumí-la), visando eliminar o forte acoplamento geralmente existente
entre os módulos da aplicação. Para que essa ideia fique um pouco mais
clara, considere a situação proposta logo abaixo:

A figura acima apresenta uma situação hipotética, onde um módulo para o gerenciamento de downloads existe. Para este exemplo, estamos propondo três camadas (classes com naturezas diferentes):

  • clsDown (uma camada de nível mais alto), 
  • clsDownBusiness (camada onde são implementadas as regras de negócios da aplicação),
  • clsDownData (camada mais baixa de acesso a dados).

Note que para que uma instância da classe clsDown exista, é fundamental que a classe clsDownBusiness também exista. E para que esta última exista, clsDownData
também precisa existir. Portanto, a dependência entre as camadas está
apresentada. Mas a pergunta aqui é “qual o problema desta
abordagem?”

A problemática implícita (motivação para implementação da ID)

A
característica de “dependência” mencionada acima, onde uma classe
concreta (de objetos reais) depende diretamente de outra é uma relação
desinteressante do ponto de vista da arquitetura do software. Isso se
deve há alguns motivos, dentro os quais podemos destacar os dois mais
importantes:

  • Testabilidade:
    escrever testes unitários para aplicações é uma prática extremamente
    recomendável, e, para que esta possa ser realizada com sucesso, as
    classes e métodos necessitam ser definidas de forma isolada. Testar
    classes com dependencia(s) direta(s) de outra(s) é uma prática
    praticamente impossível de ser realizada com sucesso.
  • Extensibilidade:
    a correta aplicação da injeção de dependência resulta em módulos
    “plugáveis”, extensíveis. Isto é, módulos podem ser utilizados por
    outras aplicações sem que estas sintam o impacto da “herança”.

Injeção de dependência: alguns conceitos fundamentais

Basicamente, a injeção de dependência pode ser implementada de três formas:

  • Construtor:
    as dependências do objeto são injetadas diretamente em seu construtor.
    Vale salientar que, esta abordagem deve ser adotada quando se pretende
    injetar todas as dependências externas.
  • Propriedade: dependências do objeto são injetadas via setter em alguma(s) propriedade(s).
  • Interface:
    o objeto a ser injetado oferece fornece uma abstração de seus serviços
    (na forma interface ou mesmo classe abstrata), sendo que a injeção é
    realizada via instância da abstração.

Não existe uma regra geral que defina qual a melhor abordagem dentre
as três mencionadas. Como quase tudo em computação dependerá da situação
problema, a injeção de dependência via construtor é mais
comumente encontrada nas aplicações.

Obs.: Vinicius Quaiato escreveu um bom
artigo em seu blog sobre a definição da melhor abordagem. E você pode
efetuar a leitura deste artigo aqui.

Existem diversos frameworks para implementação de injeção de
depencia em uma aplicação .NET, sendo que o mais famoso deles é o
Unity. Por ora, implementaremos a injeção de dependência de
forma manual, depois implementaremos ID utilizando o Unity. Dessa forma você poderá mensurar qual a melhor abordagem e qual se adequa
mais a seu contexto.

Implementando injeção de dependência

Para que possamos entender de fato este conceito, utilizaremos a
situação problema proposta pela figura citada logo acima. Temos um objeto “clsDown” que,
para que possa existir, depende do objeto “clsDownBusiness” que, por
dua vez depende do objeto “clsDownData”.

Temos objeto “clsDown
que pode ser entendido neste caso como uma camada de visualização. Para
que os dados sejam exibidos corretamente, a lógica foi implementada no
objeto “clsDownBusiness“. E para que a lógica  possa funcionar corretamente, o objeto “clsDownData” cuida do acesso a dados.

Assim, considere o trecho de código apresentado Listagem 1:

namespace InjecaoDependencia_1.Controllers
{
public class clsDownController : Controller
{
public ActionResult NovoDownload(Download objDown)
{
var objDownBusiness = new clsDownBusiness();
objDownBusiness.SalvarDownloadBD(objDown);
return View();
}
}
}

public class clsDownBusiness
{
public void SalvarDownloadBD(Download objDown)
{
var objDownData = new clsDownData();
objDownData.ConexaoBanco();

//Salva no BD
}
}

public class clsDownData
{
public void ConexaoBanco()
{
//Conecta no banco de dados
}
}

public class Download
{
//Definição de download
}

Em uma
rápida observação do código apresentado pela listagem acima é possível
ver as relações de dependência entre os objetos.

Note que a action “NovoDownload” precisa utilizar do objeto “objDownBusiness“, que é do tipo “clsDownBusiness” e, esta última, precisa da informação de conexão com o banco de dados, que é provida pelo objeto “objDownData“, que é do tipo “clsDownData“.

Para que
possamos implementar o mecanismo de injeção de dependência, vamos
criar abstrações (interfaces) onde as dependências existem. No
caso, a abstração ocorre para o objeto que será “injetado”. Assim, a
dependência entre “clsDown” e “clsDownBusiness” será realizada através de uma interface, chamada por nós de “IDownBusiness“. Assim, temos nossa arquitetura um pouco modificada.

A Listagem 2 apresenta o código sugestivo para “IDownBusiness“, a Listagem 3 apresenta o código sugestivo para “IDownData” e a Listagem 4 apresenta a estrutura da aplicação modificada.

public interface IDownBusiness
{
public void SalvarDownloadBD(Download objDown);
}

Listagem 2: Código sugestivo para “IDownBusiness


public interface IDownData
{
public bool ConexaoBanco();
}

Listagem 3: Código sugestivo para “IDownData


public class clsDownController : Controller
{
private IDownBusiness iObjDownBusiness;

public clsDownController(IDownBusiness _iObjDownBusiness)
{
this.iObjDownBusiness = _iObjDownBusiness;
}

public ActionResult NovoDownload(Download objDown)
{
iObjDownBusiness.SalvarDownloadBD(objDown);
return View();
}
}

public class clsDownBusiness : IDownBusiness
{
private IDownData iObjDownData;

public clsDownBusiness(IDownData _iObjDownData)
{
this.iObjDownData = _iObjDownData;
}

public void SalvarDownloadBD(Download objDown)
{
iObjDownData.ConexaoBanco();

//Salva no BD
}
}

public class clsDownData : IDownData
{
public void ConexaoBanco()
{
//Conecta no banco de dados
}
}

Listagem 4: “Injetando dependência” em nossos objetos de forma indireta “IDownData

Nas Listagens 2, 3 e 4 tivemos a inserção de todos os conceitos importantes neste artigo:

  • Listagem 2: criamos uma abstração (interface) do objeto a ser injetado em “clsDown” e, dentro dela, assinamos o método “SalvarDownloadBD”.
  • Listagem 3: criamos uma abstração (interface) do objeto a ser injetado em “clsDownBusiness” e dentro dela assinamos o método “ConexaoBanco”.
  • Listagem 4:
    em função da implementação das abstrações, temos agora que “injetar” a
    dependência destas nas classes que as consomem. O primeiro aspecto a ser
    notado é: as classes “clsDownBusiness” e “clsDownData” implementam as interfaces “IDownBusiness” e “IDownData” respectivamente. Isso pode ser observado nas linhas 17 e 34. Outra observação importante é que: a classe “clsDown” espera um objeto qualquer que implemente a interface “IDownBusiness“, por inércia, temos a dependência direta com classe “clsDownBusiness” quebrada. O mesmo se repete em relação as classes “clsDownBusiness” e “clsDownData“. Estas situações podem ser observadas nas linhas 3, 5, 6, 7, 19, 21, 22 e 23.

Nossa injeção de dependência foi realizada. Um próximo passo poderia ser a utilização de Abstract Factory, por exemplo, para centralizar todas as dependências. Como o foco deste texto consiste apenas na apresentação dos conceitos de injeção de dependência, ficamos por aqui.

Até a próxima! E não esqueça de deixar seu feedback através dos comentários.