Logo

dev-resources.site

for different kinds of informations.

Reduzindo a quantidade de Branchs na criação de Objetos com uma estrutura plugável

Published at
10/8/2021
Categories
java
factory
inversion
dependency
Author
walteralleyz
Categories
4 categories in total
java
open
factory
open
inversion
open
dependency
open
Author
12 person written this
walteralleyz
open
Reduzindo a quantidade de Branchs na criação de Objetos com uma estrutura plugável

Você já se deparou com o cenário em que criar objetos, de forma dinâmica, usando algum parâmetro era necessário?
Digamos que estejamos criando um sistema para academias, e para diferenciar os tipos de clientes nós vamos modelar classes diferentes. De início vamos ter os planos básico, avançado e de luxo e com o tempo podemos acrescentar novos planos a partir de novas atualizações.

public class Basico {}

public class Avcado {}

public class DeLuxo {}
Enter fullscreen mode Exit fullscreen mode

Podemos perceber que se o nosso cliente estiver buscando um sistema em que ele possa definir os tipos de planos, ele vai sofrer com forte dependência do time de desenvolvimento, pois para adicionar um plano ele precisará esperar uma nova atualização. Não é o objetivo desse artigo solucionar esse problema.

Normalmente para instanciar essas classes de forma dinâmica, nós poderíamos criar uma função para receber um parâmetro e retornar o objeto selecionado. Mas para isso, nossas classes precisam adquirir um super tipo que as torne polimórficas.

public interface UserPlan {
  void doSomething();
}
Enter fullscreen mode Exit fullscreen mode

Agora podemos implementar essa interface em nossas classes e criar o nosso método.

public UserPlan criarPlano(String plan) {
  if(plan.equals("basico")) return new Basico();
  else if(plan.equals("avancado")) return new Avcado();
  else return new DeLuxo();
}
Enter fullscreen mode Exit fullscreen mode

Muito fácil, não é mesmo? O problema que criamos utilizando desse padrão é que sempre que uma nova classe plano for criada, precisaremos editar essa função e adicionar uma nova branch. Se esse método estiver contido dentro da própria classe cliente (a classe que dependeria dessa função), teremos um problema em dobro, sempre tendo de abrir a classe para dar manutenção e tendo de alterar todos os pontos em que a criação desses objetos atinge.

Um exemplo simples de entender é que, se a classe cliente dependesse do nome do objeto criado para executar alguma ação, e a implementação dessa ação estivesse contida dentro da classe cliente, também precisaríamos editar esse código gerando um ciclo de retrabalho interminável.

public class Cliente {

  public void fazAlgoDeAcordoComObjeto(String plan) {
    UserPlan plan = criarPlano(plan);

    if(plan.getClass().getSimpleName().equals("Basico"))
    // implementa algo aqui

    else if(plan.getClass().getSimpleName().equals("Avcado"))
    // implementa algo aqui

    else
    // implementa algo aqui

  }
}
Enter fullscreen mode Exit fullscreen mode

Esse tipo de implementação cria uma dependência (ou acoplamento) terrivelmente abusiva, que nos traz um novo problema sempre que precisamos realizar alguma alteração.

Para contornar esse contra padrão e eliminar as branchs, podemos nos aproveitar de uma estrutura de dados que já utiliza comparação de acordo com um parâmetro específico; uma estrutura de chave-valor.

Vamos começar eliminando da classe cliente a implementação do método fazAlgoDeAcordoComObjeto, e utilizar injeção de dependência para chamar o método doSomething que as classes implementam por meio da interface UserPlan:

public class Cliente {

  private final UserPlan userPlan;

  public Cliente(UserPlan userPlan) {
    this.userPlan = userPlan;
  }

  public void fazAlgoDeAcordoComObjeto() {
    userPlan.doSomething();
  }

}
Enter fullscreen mode Exit fullscreen mode

Eliminamos vários pontos de manutenção distribuindo a responsabilidade.

Agora podemos criar uma classe utilitária que vai cuidar da criação do objeto para nós.
Essa classe vai possuir um HashMap (que recebe chave e valor) para guardar a chave de comparação e o método de criação do objeto.

public class UserPlanCreator {

  private static final Map<String, PlanCreator> plans = new HashMap<>();

  static {
    plans.put("basico", Basico::new);
    plans.put("avancado", Avcado::new);
    plans.put("luxo", DeLuxo::new);
  }

  private UserPlanCreator() {}

  public static UserPlan criarPlano(String plano) {
    return plans.get(plano).create();
  }

}
Enter fullscreen mode Exit fullscreen mode

Uma validação de nulo pode ser implementada no método criarPlano.

Você deve ter percebido que a nossa estrutura Map não retorna o super tipo e sim uma interface que a gente não implementou ainda. Eu fiz essa escolha de design, porque criar um objeto dentro de um iniciador estático iria tornar essa criação única, fazendo o método sempre retornar o mesmo objeto.
Para evitar esse comportamento, precisamos retornar o método de criar e não o objeto instanciado e nesse momento as interfaces funcionais podem nos ajudar.

Uma interface funcional é uma estrutura que possui um único método abstrato e pode ser utilizada para implementar lambdas ou referências de métodos. Assim seria a nossa interface funcional:

@FuncionalInterface
public interface PlanCreator {

  UserPlan create();
}
Enter fullscreen mode Exit fullscreen mode

Pronto, agora a gente só precisa garantir que o lambda, ou referência de método, retorne o objeto do tipo UserPlan (e por isso estamos usando um super tipo nas classes de planos 😀).

Apesar desse monte de código você vai perceber que as responsabilidades estão bem definidas, e a classe cliente só precisa chamar o método do objeto que ela vai receber, sem precisar lidar com a comparação e sem precisar conhecer os objetos que ela possui dependência. Ainda eliminamos vários pontos de manutenção, garantindo que quando uma nova classe for criada precisaremos alterar apenas uma estrutura do todo.

Por fim, nosso código para criar um plano ficaria assim:

public static void main(String[] args) {
  // geralmente a entrada do tipo do plano viria de algum input
  Client client = new Client(UserPlanCreator.criarPlano("basico"));
  client.doSomething();
}
Enter fullscreen mode Exit fullscreen mode
dependency Article's
30 articles in total
Favicon
Ore: Advanced Dependency Injection Package for Go
Favicon
vcpkg - how to modify dependencies
Favicon
CocoaPods is in Maintenance Mode
Favicon
Safely restructure your codebase with Dependency Graphs
Favicon
Understanding Dependencies in Programming
Favicon
A zoom installer script for linux
Favicon
Loose Coupling and Dependency Injection (DI) principle
Favicon
Dependency Injection in swift
Favicon
Dependency relation in AWS CDK
Favicon
CORS how to enable them in .NET?
Favicon
Angular Dependency Injection
Favicon
Dependency management made easy with Dependabot and GitHub Actions
Favicon
Jetpack compose — Dependency injection with Dagger/HILT
Favicon
Dependencies in node project
Favicon
Fixing vulnerabilities found in a dependency tree
Favicon
How to create your own dependency injection framework in Java
Favicon
Reduzindo a quantidade de Branchs na criação de Objetos com uma estrutura plugável
Favicon
NodeJs - Dependency injection, make it easy
Favicon
A Step by Step Guide to ASP.NET Core Dependency Injection
Favicon
The Basics of Dependency Maintenance in NPM/yarn
Favicon
The troubles of modern software dependency management and what to do about them
Favicon
Loose Coupling Basics
Favicon
Correctly defining CDK dependencies in L3 constructs
Favicon
What dependency hell looks like, and how to avoid it
Favicon
How to create your own dependency injection framework in Java
Favicon
Dependency Inversion Principle in Swift
Favicon
Angular: Create a custom dependency injection
Favicon
Dagger with a Hilt
Favicon
How to find what is the dependency of a function, class, or variable in ES6 via AST
Favicon
Data Dependency Graph

Featured ones: