Contextos e Injeção de Dependência (CDI) para Java EE

Uma introdução aos serviços disponíveis na especificação CDI que podem ajudar na estruturação de sua aplicação e redução do acoplamento entre componentes

Fique por dentro
Este artigo é útil para o desenvolvedor que queira entender como aplicar a especificação CDI no gerenciamento de dependências entre elementos de uma aplicação. Essa especificação pode facilitar o objetivo da construção de soluções com alta coesão e baixo acoplamento, através do gerenciamento de dependências de forma declarativa e diversos serviços de suporte ao ciclo de vida de componentes.

Este artigo apresenta uma visão geral de conceitos relacionados à injeção de dependência e como seu uso pode ser útil na construção de aplicações Java simples e bem estruturadas. Veremos como é possível construir soluções onde o gerenciamento de conexões entre componentes é feito de forma simplificada e flexível, reduzindo o acoplamento entre as diversas partes do sistema.

Além disso, vamos ver como a injeção de dependência (DI) disponível na plataforma Java EE facilita a organização de componentes. Organizar e gerenciar o relacionamento entre componentes de uma solução é uma condição fundamental para manter a mesma de forma organizada e lógica.

Como sabemos, um projeto de software consiste em transformar um conjunto de requisitos em uma solução. E para atingir esse objetivo, muitas vezes separamos esses requisitos em diversos elementos, ou componentes, que possuem um conjunto de responsabilidades e colaboram para viabilizar as diferentes funcionalidades. Observe que usamos aqui a noção de componente de forma bem abrangente (e vamos continuar utilizando no texto a seguir), não implicando na adoção de uma determinada estrutura ou tecnologia, mas apenas a ideia de que a realização dos requisitos seja feita fragmentando e realizando os mesmos em diversos módulos, ao invés de em um único elemento.

Outro nome para essa separação é modularização, técnica usada há bastante tempo como parte da estratégia de separar grandes problemas em problemas menores e mais fáceis de serem resolvidos, alocando responsabilidades específicas e relacionadas a determinados componentes.

Ao realizarmos essa divisão de responsabilidades entre diversos componentes da solução, podemos avaliar a qualidade dessa separação e da interação entre os componentes através de duas medidas: coesão e acoplamento. De forma geral, devemos buscar projetar e construir componentes com alto grau de coesão e baixo acoplamento, pois isso facilita a compreensão e manutenção da solução.

Coesão e acoplamento

Coesão e acoplamento não são conceitos novos no desenvolvimento de software e muito menos exclusivos da plataforma Java. Ao contrário, são conceitos que estão por aí desde meados de 1960 e que, volta e meia, são alvo de novas e engenhosas propostas de solução. Para entender como a injeção de dependência se encaixa nesse contexto, vamos falar um pouco sobre coesão, acoplamento e outros conceitos relacionados.

E por que esses conceitos são importantes e recorrentes? Porque nenhum componente é uma ilha: Qualquer sistema, a partir de um grau mínimo de complexidade, será composto por diversas peças que colaboram entre si, e gerenciar essa complexidade em diversos níveis é um problema recorrente no desenvolvimento de software. Considere as dependências que existem entre métodos, classes, componentes, bibliotecas e aplicações. Para cada um desses tipos de relacionamento podemos ter estratégias distintas para facilitar a redução de acoplamento e aumentar a coesão.

Coesão

Coesão mede o quanto as responsabilidades de um determinado componente estão relacionadas ao mesmo. Quanto maior a coesão, melhor o projeto do elemento.

Como definimos um componente de forma ampla, vamos usar como exemplo um método de uma classe. Uma forma de avaliar o nível de coesão do mesmo seria a seguinte: Todas as linhas de código desse método são relacionadas ao objetivo do mesmo? Você consegue dizer que o método possui uma única responsabilidade, e que toda a sua estrutura contribui para essa responsabilidade, ou consegue identificar diversas responsabilidades distintas nesse método?

Por exemplo, em um mesmo método, você pode acessar um banco de dados, validar as informações contra um serviço externo, formatar os dados para exibição em uma tela e verificar se o usuário dessa aplicação possui perfil de administrador ou usuário comum. Muita coisa? Sim, e você pode identificar facilmente métodos assim, verificando, por exemplo, os que são mais extensos ou que fazem diversas “coisas”. Esses são métodos que comumente apresentam baixa coesão, característica normalmente encontrada onde diversas responsabilidades são “amarradas” em um mesmo elemento.

De maneira mais ampla, dessa vez analisando a classe, considere se os métodos da mesma são relacionados. Uma mesma classe é responsável pelos diversos aspectos descritos anteriormente, ou você consegue determinar a responsabilidade de uma classe como única? A facilidade com que consegue responder a essa pergunta pode determinar se sua classe é coesa ou não. E qual a vantagem de ter uma classe coesa? Quanto menos você precisar navegar na sua estrutura de projeto para tratar de um determinado assunto, mais fluente e prática é a manutenção do seu sistema.

Tipos de coesão

Entre os tipos de coesão que podemos citar, temos:

  • Acidental: Esse tipo de coesão, como sugerido pelo nome, ocorre sem que seja feito qualquer planejamento. As responsabilidades são agrupadas não de forma projetada, mas sim por conveniência, como, por exemplo, uma classe utilitária com diversas funções não relacionadas a um único assunto;
  • Comunicação: Aqui, os elementos são agrupados em função de atualizarem a mesma estrutura de dados ou produzirem os mesmos dados de saída (por exemplo, diversos métodos que geram relatórios correlatos);
  • Funcional: Caso os elementos existam no componente para atender a uma mesma função. Para esse tipo de coesão, deve ser possível descrever a função do componente de forma única, mesmo que existam diversos elementos para atender a essa função. Por exemplo, um módulo que possui um conjunto de funções relacionadas entre si para manter os dados de um catálogo de produtos;
  • Lógica: Podemos dizer que um módulo possui coesão lógica quando o mesmo realiza funções similares. Considere, por exemplo, um módulo responsável apenas por funções de validação de dados ou tratamento de erros;
  • Procedural: Para existir coesão procedural é necessário que os elementos do componente sejam parte de um algoritmo ou procedimento executado em etapas para se chegar a um fim. Por exemplo, o processo de geração de um arquivo, que segue passos como: abrir o arquivo, posicionar um cursor no ponto de escrita, escrever o conteúdo e fechar o arquivo;" [...] continue lendo...

Artigos relacionados