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

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 downloadsexiste. 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 clsDownBusinesstambé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.
Obrigado pelo seu comentário

Postagens Relacionadas

Related Posts Plugin for WordPress, Blogger...

Programador GB