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:
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.
Listagem 2: Código sugestivo para "IDownBusiness"
Listagem 3: Código sugestivo para "IDownData"
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.
Fora de tópico Mostrar Código Esconder Código Mostrar EmoticonEsconder Emoticon