Injeção de dependência ?
A injeção de dependência é geralmente utilizada quando se deseja diminuir o acoplamento entre diferentes componentes/módulos de um sistema, de forma que o controle de instanciação das classes dependentes é realizado fora das classes, ou seja, todas as dependências entre os componentes/módulos não são definidos programaticamente, mas através da configuração de um container, onde o mesmo é responsável por injetar em cada componente/módulo suas dependências necessárias declaradas no código da classe. Mais claramente, utilizando a injeção de dependência é possível instanciar diversas classes concretas, sem manter-se aclopado a mesma.
Criação do projeto
Vamos criar um projeto do tipo ASP.NET MVC com o nome de Site.Apresentação no respectivo caminho C:\Site conforme a figura 1.0.
Figura 1.0
Sem criar um projeto de testes, vamos renomear nossa solução para Site e criar mais dois projetos do tipo Class Library com os nomes de Site.Dados, que será o projeto onde ficara nosso Model e todas as chamadas ao banco de dados, e Site.Servicos, que ficara responsável por disponibilizar os recursos e serviços pertinentes a aplicação, ao final nossa aplicação deve ficar igual a figura 1.1.
Figura 1.1
Agora no projeto de Dados, vamos criar uma pasta com o nome de Model, como utilizaremos LinqToSQL, dentro da mesma criamos um arquivo do tipo LINQ to SQL Classes com o nome de SiteDB. Para nosso projeto eu criei uma tabela no banco chamada tbUsuarios, então arrastamos nossa tabela para o arquivo criado e a renomeamos para Usuario, igual à figura 1.2.
Figura 1.2
Ainda no projeto de dados vamos criar uma interface na raiz do projeto com o nome de IUsuarioDB com o seguinte código:
using
System.Linq;
using
Site.Dados.Model;
namespace Site.Dados
{
public interface IUsuarioDB
{
IQueryable<Usuario> SelecionaUsuarios();
}
}
Em seguida criamos uma classe chamada UsuarioDB que ira implementar a interface criada anteriormente da seguinte forma:
using
System.Linq;
using
Site.Dados.Model;
namespace Site.Dados
{
public class UsuarioDB : IUsuarioDB
{
SiteDBDataContext _db;
public UsuarioDB(SiteDBDataContext
db)
{ this._db = db; }
public IQueryable<Usuario> SelecionaUsuarios()
{
return
_db.Usuarios;
}
}
}
Note que é criado um construtor que recebe como parâmetro um objeto do tipo SiteDBDataContext onde atualiza o objeto da classe que é utilizado para realizar as consultas no DB.
Agora no projeto de Serviços vamos adicionar uma referencia ao projeto Site.Dados conforme a figura 1.3.
Figura 1.3
Criamos agora uma interface na raiz do projeto com o nome de IUsuarioServ com o seguinte código:
using
Site.Dados.Model;
namespace
Site.Servicos
{
public interface IUsuarioServ
{
Usuario SelecionaUsuario(int
codUsuario);
}
}
Em seguida criamos uma classe chamada UsuarioServ que implementara a interface criada conforme é exibido abaixo:
using
Site.Dados.Model;
using
Site.Dados;
using
System.Linq;
namespace
Site.Servicos
{
public class UsuarioServ : IUsuarioServ
{
IUsuarioDB _db;
public UsuarioServ(IUsuarioDB
db)
{ this._db = db; }
public Usuario
SelecionaUsuario(int codUsuario)
{
return _db.SelecionaUsuarios()
.Where(u => u.codUsuario == codUsuario)
.SingleOrDefault();
}
}
}
Note que também é criado um construtor, porém, este recebe como parâmetro um objeto do tipo IUsuarioDB que atualiza o objeto da classe que é utilizado para chamar os métodos declarados na interface IUsuarioDB do projeto de Dados.
Vamos agora adicionar duas referências no projeto de Apresentação, uma do projeto de Serviços e outra do projeto de Dados.
Em seguida modificaremos o HomeController para que fique da seguinte forma:
using
System.Web.Mvc;
using
Site.Servicos;
using
Site.Dados.Model;
namespace
Site.Apresentacao.Controllers
{
[HandleError]
public class HomeController : Controller
{
IUsuarioServ _serv;
public HomeController(IUsuarioServ
serv)
{ this._serv = serv; }
public ActionResult
Index()
{
Usuario
u = _serv.SelecionaUsuario(1);
ViewData["Message"]
= u.nome;
return View();
}
public ActionResult
About()
{
return
View();
}
}
}
Note novamente o construtor que atualiza o objeto da interface IUsuarioServ referente ao projeto de Serviços. Repare que se compilarmos a aplicação não é exibido nenhum erro, porém, se a executarmos é gerada uma Exception do tipo InvalidOperationException, conforme é mostrado na figura 1.4.
Figura 1.4
Este erro ocorre, pois, como definimos que o construtor do Controller recebera um objeto do tipo IUsuarioServ como parâmetro, algo deve injetar esta dependência ao mesmo, ai entra o StructureMap.
StructureMap
Vamos salvar o arquivo StructureMap.dll no diretório C:\Site de nossa aplicação. Agora adicionaremos a referencia da DLL nos projetos de Apresentação e Dados.
Já no projeto de Dados vamos criar uma classe chamada DBServiceRegistry na raiz do projeto com o seguinte conteúdo:
using
StructureMap.Configuration.DSL;
using
Site.Dados.Model;
namespace Site.Dados
{
public class DBServiceRegistry:Registry
{
protected override void configure() {
ForRequestedType<SiteDBDataContext>()
.TheDefault
.Is
.ConstructedBy(() => new SiteDBDataContext());
}
}
}
Novamente no projeto de Apresentação dentro do diretório View vamos criar duas classes, uma chamada Bootstrapper com o conteúdo:
using
StructureMap;
using
StructureMap.Configuration.DSL;
using
Site.Dados;
using
Site.Servicos;
using
Site.Dados.Model;
namespace
Site.Apresentacao
{
public static class Bootstrapper
{
public static void ConfigureStructureMap()
{
ObjectFactory.Initialize(structureMap =>
{
structureMap.AddRegistry(new DBServiceRegistry());
structureMap.AddRegistry(new SiteRegistry());
});
}
}
public class SiteRegistry : Registry
{
protected override void configure()
{
ForRequestedType<IUsuarioDB>()
.TheDefaultIsConcreteType<UsuarioDB>();
ForRequestedType<IUsuarioServ>()
.TheDefaultIsConcreteType<UsuarioServ>();
}
}
}
E outra chamada ViewEngine com o conteúdo:
using
System.Web.Mvc;
namespace
Site.Apresentacao.Views
{
public class ViewEngine : WebFormViewEngine
{
public ViewEngine()
{
MasterLocationFormats = new[] {
"~/Views/{1}/{0}.master",
"~/Views/Shared/{0}.master"
};
ViewLocationFormats = new[] {
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.aspx",
"~/Views/Shared/{0}.ascx",
};
PartialViewLocationFormats =
ViewLocationFormats;
}
}
}
Desta vez dentro do diretório Controllers criamos outra classe, agora chamada StructureMapControllerFactory com o seguinte código implementado:
using System;
using
System.Web.Mvc;
using
StructureMap;
namespace
Site.Apresentacao.Controllers
{
public class StructureMapControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(Type controllerType)
{
IController result = null;
if (controllerType != null)
{
try
{
result = ObjectFactory.GetInstance(controllerType)
as System.Web.Mvc.Controller;
}
catch (StructureMapException)
{
System.Diagnostics.Debug.WriteLine(ObjectFactory.WhatDoIHave());
throw;
}
}
return
result;
}
}
}
E por fim alteramos o método Application_Start do arquivo Global.asax para que fique da seguinte forma:
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
Bootstrapper.ConfigureStructureMap();
ControllerBuilder.Current.SetControllerFactory
(new Site.Apresentacao.Controllers.StructureMapControllerFactory());
ViewEngines.Engines.Add(new
Views.ViewEngine());
}
Agora sim. Vamos iniciar nossa aplicação e note que, desta vez é exibido o nome do usuario enviado como parametro pelo controller até o projeto de serviço, que por sua vez chama o projeto de dados e retorna o usuário em questão.
Figura 1.5
Conclusão
Já sabemos que, quanto maior o acoplamento entre os componentes do projeto, maior é a dependência entre um módulo e outro, que pode resultar em alterações e manutenções em diversas partes do sistema. Obviamente que, o desejável é que o acoplamento seja o menor possível entre os componentes, para que, quando efetuada qualquer alteração em código tenha o menor impacto possível nas classes dependentes e vice versa.
Espero ter ajudado a perceber como a injeção de dependência é um ótimo recurso para diminuirmos o acoplamento em um projeto.