CodeGym/Cursos Java/Módulo 3/Inversão de Dependência

Inversão de Dependência

Disponível

9.1 Inversão de Dependência

Lembre-se, uma vez dissemos que em um aplicativo de servidor você não pode simplesmente criar fluxos por meio de new Thread().start()? Somente o contêiner deve criar threads. Agora, desenvolveremos ainda mais essa ideia.

Todos os objetos também devem ser criados apenas pelo container . Claro, não estamos falando de todos os objetos, mas sim dos chamados objetos de negócios. Eles também são frequentemente referidos como caixas. As pernas dessa abordagem crescem a partir do quinto princípio do SOLID, que requer livrar-se de classes e mudar para interfaces:

  • Módulos de nível superior não devem depender de módulos de nível inferior. Tanto aqueles quanto outros devem depender de abstrações.
  • As abstrações não devem depender de detalhes. A implementação deve depender da abstração.

Os módulos não devem conter referências a implementações específicas e todas as dependências e interações entre eles devem ser construídas exclusivamente com base em abstrações (ou seja, interfaces). A própria essência desta regra pode ser escrita em uma frase: todas as dependências devem estar na forma de interfaces .

Apesar de sua natureza fundamental e aparente simplicidade, essa regra é violada com mais frequência. Ou seja, toda vez que usamos o operador new no código do programa/módulo e criamos um novo objeto de um tipo específico, assim, ao invés de depender da interface, forma-se a dependência da implementação.

É claro que isso não pode ser evitado e os objetos devem ser criados em algum lugar. Mas, no mínimo, você precisa minimizar o número de locais onde isso é feito e nos quais as classes são especificadas explicitamente, bem como localizar e isolar esses locais para que não fiquem espalhados pelo código do programa.

Uma solução muito boa é a ideia maluca de concentrar a criação de novos objetos em objetos e módulos especializados - fábricas, localizadores de serviços, contêineres IoC.

De certa forma, tal decisão segue o Princípio da Escolha Única, que diz: "Sempre que um sistema de software deve suportar muitas alternativas, sua lista completa deve ser conhecida apenas por um módulo do sistema" .

Portanto, se no futuro for necessário adicionar novas opções (ou novas implementações, como no caso da criação de novos objetos que estamos considerando), bastará atualizar apenas o módulo que contém essas informações e todos os outros módulos permanecerá inalterado e poderá continuar seu trabalho, como de costume.

Exemplo 1

new ArrayList Em vez de escrever algo como , faria sentido List.new()para o JDK fornecer a você a implementação correta de uma folha: ArrayList, LinkedList ou mesmo ConcurrentList.

Por exemplo, o compilador vê que há chamadas para o objeto de diferentes threads e coloca uma implementação thread-safe lá. Ou muitas inserções no meio da planilha, então a implementação será baseada em LinkedList.

Exemplo 2

Isso já aconteceu com sorts, por exemplo. Quando foi a última vez que você escreveu um algoritmo de classificação para classificar uma coleção? Em vez disso, agora todos usam o método Collections.sort(), e os elementos da coleção devem suportar a interface Comparable (comparável).

Se sort()você passar uma coleção com menos de 10 elementos para o método, é bem possível classificá-la com uma classificação de bolha (Bubble sort) e não com Quicksort.

Exemplo 3

O compilador já está observando como você concatena strings e substituirá seu código por StringBuilder.append().

9.2 Inversão de dependência na prática

Agora o mais interessante: vamos pensar em como podemos aliar teoria e prática. Como os módulos podem criar e receber corretamente suas “dependências” e não violar a Inversão de Dependência?

Para fazer isso, ao projetar um módulo, você deve decidir por si mesmo:

  • o que o módulo faz, qual função ele executa;
  • então o módulo precisa do seu ambiente, ou seja, com quais objetos/módulos ele terá que lidar;
  • E como ele vai conseguir?

Para cumprir os princípios da Inversão de Dependência, você definitivamente precisa decidir quais objetos externos seu módulo usa e como obterá referências a eles.

E aqui estão as seguintes opções:

  • o próprio módulo cria objetos;
  • o módulo pega objetos do container;
  • o módulo não tem ideia de onde vêm os objetos.

O problema é que, para criar um objeto, você precisa chamar um construtor de um tipo específico e, como resultado, o módulo não dependerá da interface, mas da implementação específica. Mas se não quisermos que os objetos sejam criados explicitamente no código do módulo, podemos usar o padrão Factory Method .

"O ponto principal é que, em vez de instanciar diretamente um objeto por meio de new, fornecemos à classe cliente alguma interface para criar objetos. Como essa interface sempre pode ser substituída pelo design correto, obtemos alguma flexibilidade ao usar módulos de baixo nível em módulos de alto nível" .

Nos casos em que é necessário criar grupos ou famílias de objetos relacionados, uma fábrica abstrata é usada em vez de um método de fábrica .

9.3 Usando o Localizador de Serviços

O módulo pega os objetos necessários de quem já os possui. Supõe-se que o sistema possua algum repositório de objetos, no qual os módulos podem “colocar” seus objetos e “pegar” objetos do repositório.

Essa abordagem é implementada pelo padrão Service Locator , cuja ideia principal é que o programa tenha um objeto que saiba como obter todas as dependências (serviços) que podem ser necessárias.

A principal diferença das fábricas é que o Service Locator não cria objetos, mas na verdade já contém objetos instanciados (ou sabe onde / como obtê-los e, se criar, apenas uma vez na primeira chamada). A fábrica em cada chamada cria um novo objeto do qual você obtém propriedade total e pode fazer o que quiser com ele.

Importante ! O localizador de serviços produz referências aos mesmos objetos já existentes . Portanto, você precisa ter muito cuidado com os objetos emitidos pelo Localizador de Serviços, pois outra pessoa pode utilizá-los ao mesmo tempo que você.

Os objetos no localizador de serviços podem ser adicionados diretamente através do arquivo de configuração e, na verdade, de qualquer maneira conveniente para o programador. O Service Locator em si pode ser uma classe estática com um conjunto de métodos estáticos, um singleton ou uma interface e pode ser passado para as classes necessárias por meio de um construtor ou método.

O localizador de serviços às vezes é chamado de antipadrão e é desencorajado (porque cria conexões implícitas e apenas dá a aparência de um bom design). Você pode ler mais de Mark Seaman:

9.4 Injeção de Dependência

O módulo não se preocupa com as dependências de "mineração". Ele apenas determina o que precisa para funcionar, e todas as dependências necessárias são fornecidas (introduzidas) de fora por outra pessoa.

Isso é o que se chama - Injeção de Dependência . Normalmente, as dependências necessárias são passadas como parâmetros de construtor (Injeção de Construtor) ou por meio de métodos de classe (Injeção de Setter).

Essa abordagem inverte o processo de criação de dependências - em vez do próprio módulo, a criação de dependências é controlada por alguém de fora. O módulo do emissor ativo de objetos torna-se passivo - não é ele quem cria, mas outros criam para ele.

Essa mudança de direção é chamada de Inversão de Controle ou Princípio de Hollywood - "Não ligue para nós, ligaremos para você".

Esta é a solução mais flexível, dando maior autonomia aos módulos . Podemos dizer que só ele implementa totalmente o "Princípio da Responsabilidade Única" - o módulo deve estar totalmente focado em fazer bem o seu trabalho e não se preocupar com mais nada.

Fornecer ao módulo tudo o que é necessário para o trabalho é uma tarefa separada, que deve ser realizada pelo “especialista” apropriado (geralmente um determinado contêiner, um contêiner IoC, é responsável pelo gerenciamento de dependências e sua implementação).

Na verdade, tudo aqui é como na vida: em uma empresa bem organizada, os programadores programam, e as mesas, computadores e tudo o que precisam para trabalhar são comprados e fornecidos pelo gerente do escritório. Ou, se você usar a metáfora do programa como construtor, o módulo não deve pensar em fios, outra pessoa está envolvida na montagem do construtor, e não nas próprias partes.

Não seria exagero dizer que o uso de interfaces para descrever dependências entre módulos (Dependency Inversion) + a correta criação e injeção dessas dependências (principalmente Dependency Injection) são técnicas fundamentais para desacoplamento .

Eles servem como alicerce sobre o qual o baixo acoplamento do código, sua flexibilidade, resistência a mudanças, reutilização e sem os quais todas as outras técnicas fazem pouco sentido. Essa é a base do baixo acoplamento e da boa arquitetura.

O princípio de Inversão de Controle (juntamente com Injeção de Dependência e Localizador de Serviço) é discutido em detalhes por Martin Fowler. Existem traduções de ambos os artigos: "Inversion of Control Containers and the Dependency Injection pattern" e "Inversion of Control" .

Comentários
  • Populares
  • Novas
  • Antigas
Você precisa acessar para deixar um comentário
Esta página ainda não tem nenhum comentário