Tempo de leitura: menos de 1 minuto

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:
- Construindo objetos de forma inteligente: Builder Pattern e Fluent Interfaces
- Design Patterns para melhorar seus testes, Parte 2: StepBuilder
Link permanente
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.
Link permanente
parabéns pelo post Eder..
Link permanente
Valeu Junião
Link permanente