Interface Fluente e StepBuilders

Tempo de leitura: menos de 1 minuto

Interface Fluente

Interface Fluente

Por que criar interfaces fluentes (Fluent Interfaces)?

Simples, lembra do Builder, esse padrões de projeto nos ajudam a escrever códigos menos complexos e intuitivos, minimizando os erros durante o desenvolvimento.

A interface fluente é muito parecida com o builder, é muito usada na criação de API e utilitários, fazendo com que desenvolvedor seja guiado, auto explicando seu uso a cada passo.

Mas por onde começar? Vejamos um exemplo. A classe abaixo é composta por outras duas classes:

[code language=”java”]
public class Nf {
Integer numero;
List<Item> itens;
Cliente cliente;

public Nf(Integer numero){
this.numero = numero;
itens = new ArrayList<Item>();
}

void adicionarItem(Item item) {
itens.add(item);
}

void setCliente(Cliente cliente) {
this.cliente = cliente;
}

void gerarNf() {
// …
}
}
[/code]

Para instanciarmos essa classe fazemos assim:

[code language=”java”]
Nf nf = new Nf(1);
nf.setCliente(new Cliente("Eder"));
nf.adicionarItem(new Item("Caneta", 1));
nf.adicionarItem(new Item("Lápis", 2));
nf.gerarNf();
[/code]

Essa classe por ser pequena, não trás grande complexidade, olhando a forma de uso, mesmo verboso criar uma instancia de Nf é simples.
Refatoremos agora a classe Nf usado o padrão Interface fluente:

[code language=”java”]
public class Nf {

Integer numero;
List<Item> itens;
Cliente cliente;

public Nf(Integer numero){
this.numero = numero;
itens = new ArrayList<Item>();
}

Nf com(int quantidade, String nome) {
itens.add(new Item(nome, quantidade));
return this;
}

Nf paraCliente(String nome) {
cliente = new Cliente(nome);
return this;
}

void gerarNf() {
// …
}

}
[/code]

Pouca coisa foi mudado certo? Vejamos o uso:

[code language=”java”]
new Nf(1).paraCliente("Eder")
.com(1, "Caneta")
.com(2, "Lápis")
.gerarNf();
[/code]
O que acharam? Mais intuitivo? Essa é a grande vantagem a Interface Fluente, a forma de uso ficou bem mais clara, a própria classe explica, guia o desenvolvedor no seu uso.

Mas ainda existe um problema aqui, se usarmos os métodos na ordem errada nossa interface ficará não fluente. Exemplo:

[code language=”java”]
new Nf(1).com(1, "Caneta")
.paraCliente("Eder")
.com(2, "Lápis")
.gerarNf();
[/code]

Esquisito né? A leitura já não está fluente, além disso, nada impede de esquecer o preenchimento do cliente:

[code language=”java”]
new Nf(1).com(1, "Caneta")
.com(2, "Lápis")
.gerarNf();
[/code]

Para quem esses itens foram vendidos? Isso pode causar erro, caso o cliente seja obrigatório em uma NF.

StepBuilders

Para resolver esse problema, existe um outro padrão de projeto bem interessante, chamado StepBuilders.

Ele tem o objetivo de resolver problemas do builder tradicional e da interface fluente, tais como:

  • Não guiar o usuário corretamente para criação de um objeto;
  • O usuário pode finalizar a construção do objeto a qualquer momento, chamando o método que finaliza a execução, mesmo sem todas as informações necessárias. Deixando o objeto em um estado incoerente

O StepBuilder funciona simplesmente encadeando interfaces, cada uma representando um passo na criação de um objeto ou execução de uma ação.

Usando o mesmo exemplo da Nf, vejamos agora com fica nossa interface fluente implementando também o padrão StepBuilder:

[code language=”java”]
public class Nf {

Cliente cliente;
List<Item> itens;
Integer numero;

public Nf(final Integer pNumero) {
numero = pNumero;
itens = new ArrayList<Item>();
}

public static NumeroStep emitirNf() {
return new NfSteps();
}

public static interface ClienteStep {
ItemStepStep paraCliente(String nome);
}

public static interface EmitirStep {
EmitirStep eCom(int quantidade, String nome);
Nf gerarNf();
}

public static interface ItemStepStep {
EmitirStep com(int quantidade, String nome);
}

public static interface NumeroStep {
ClienteStep numero(Integer numero);
}

private static class NfSteps implements NumeroStep,
ClienteStep, ItemStepStep, EmitirStep {
Nf nf;

public EmitirStep com(final int quantidade, final String nome) {
nf.itens.add(new Item(nome, quantidade));
return this;
}

public EmitirStep eCom(final int quantidade, final String nome) {
nf.itens.add(new Item(nome, quantidade));
return this;
}

public Nf gerarNf() {
return nf;
}

public ClienteStep numero(final Integer numero) {
nf = new Nf(numero);
return this;
}

public ItemStepStep paraCliente(final String nome) {
nf.cliente = new Cliente(nome);
return this;
}
}
}
[/code]

O uso é bem parecido com a anterior, a diferença é que a IDE vai te ajudar e não errar.

[code language=”java”]
Nf.emitirNf()
.numero(1)
.paraCliente("Eder")
.com(1, "Caneta")
.eCom(2, "Lápis")
.eCom(1, "Borracha")
.gerarNf();
[/code]

Caso você tente trocar a ordem dos métodos, o Java lançará um erro de compilação, algo parecido com isso:

[code language=”java”]
Error:(34, 25) java: D:\workspace\untitled\src\Nf.java:34: cannot find symbol
symbol : method paraCliente(java.lang.String)
location: interface Nf.NumeroStep
[/code]

Eu gosto muito de escrever interfaces fluentes, principalmente na criação de utilitários. Assim que der falo sobre alguns utilitários que criei.

Se gostou deixe-me um comentário.

Abraços.

Fontes:

 

Nenhum comentário


  1. Design Patterns é algo que eu realmente curti desde o primeiro momento em que o vi, deixa o código muito mais elegante e organizado seguindo um padrão de projeto.

    Responder

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *