O desenvolvimento de software não é algo trivial, como costuma dizer meu amigo Vinicius Quaiato. Se o software for algo “pequeno” (entenda-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, sendo que, 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, sim, é 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, sendo que, nesta primeira parte, implementaremos a DI (Dependency Injection) de forma manual e no segundo artigo utilizaremos uma ferramenta para sua implementação.
Injeção de dependência, o que é?
Injeção de dependência é 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 idéia fique um pouco mais clara, considere a situação proposta na Figura 1.
Figura 1: Situação de exemplo (downloads)
A Figura 1 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) e finalmente, clsDownData (camada mais baixa de acesso a dados).
Note, 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 correto? Mas a pergunta aqui é: qual 0 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. São elas:
- 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 do mesmo.
- Propriedade: dependências do objeto são injetadas via setter em alguma(s) propriedade(s) do mesmo.
- 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: depende da situação problema, entretanto, injeção de dependência via construtor é mais comumente encontrada nas aplicações. Vinicius Quaiato escreveu um bom artigo em seu blog sobre a definição da melhor abordagem. 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. Neste artigo, iremos implementar a injeção de dependência de forma manual e no segundo artigo, implementaremos ID utilizando o Unity, assim, 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, iremos utilizar a situação problema proposta pela Figura 1. Temos um objeto “clsDown” que, para que possa existir, depende do objeto “clsDownBusiness” que, por dua vez depende do objeto “clsDownData”.
Para que entenda o problema desta situação, 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“. Para que a lógica possa funcionar corretamente, o objeto “clsDownData” cuida do acesso a dados.
Assim, considere o trecho de código apresentado pela Listagem 1.
[csharp]
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
}
[/csharp]
Listagem 1: Relação de dependência entre as classes
Em uma rápida observação do código apresentado pela Listagem 1 é possível observar as relações de dependência entre os objetos. Note, 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 começar criando 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.
[csharp]
public interface IDownBusiness
{
public void SalvarDownloadBD(Download objDown);
}
[/csharp]
Listagem 2: Código sugestivo para “IDownBusiness“
[csharp]
public interface IDownData
{
public bool ConexaoBanco();
}
[/csharp]
Listagem 3: Código sugestivo para “IDownData“
[csharp]
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
}
}
[/csharp]
Listagem 4: “Injetando dependência” em nossos objetos de forma indireta “IDownData“
Nas Listagens 2, 3 e 4 temos 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.
Muito bem pessoal, 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 pode ser visto aqui. Como o foco deste texto consiste apenas na apresentação dos conceitos de injeção de dependência, ficamos por aqui.
No próximo artigo, a idéia é utilizar a mesma situação problema para criarmos a ID, mas utilizando uma framework para isso – o Unity.
Não esqueça de deixar seu feedback sobre este texto através de seus comentários.
Pingback: Injeção de dependência em aplicações ASP.NET MVC com Ninject – Fabrício Sanchez
Pingback: Injeção de Dependência: Timeline da série – Fabrício Sanchez