Interface Fluente e StepBuilders

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:

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() {
		// ...
	}
}

Para instanciarmos essa classe fazemos assim:

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();

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:

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() {
		// ...
	}

}

Pouca coisa foi mudado certo? Vejamos o uso:

new Nf(1).paraCliente("Eder")
         .com(1, "Caneta")
         .com(2, "Lápis")
         .gerarNf();

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:

new Nf(1).com(1, "Caneta")
         .paraCliente("Eder")
         .com(2, "Lápis")
         .gerarNf();

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

new Nf(1).com(1, "Caneta")
         .com(2, "Lápis")
         .gerarNf();

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:

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;
		}
    }
}

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

Nf.emitirNf()
  .numero(1)
  .paraCliente("Eder")
  .com(1, "Caneta")
  .eCom(2, "Lápis")
  .eCom(1, "Borracha")
  .gerarNf();

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

Error:(34, 25) java: D:\workspace\untitled\src\Nf.java:34: cannot find symbol
symbol  : method paraCliente(java.lang.String)
location: interface Nf.NumeroStep

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:

 

Anúncios

4 Comentários

  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.

    Curtir

  2. parabéns pelo post Eder..

    Curtir

  3. […] que seria mais fácil de usar que uma API usando Fluent […]

    Curtir

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

%d blogueiros gostam disto: