Fluent Interface é usado para deixar o código muito mais limpo, fácil de entender e dar manutenção. A idéia é que o código fique mais parecido com a forma que falamos. Parecido com uma linguagem de domínio, no nosso caso o Português! Vamos dar um exemplo. Suponhamos que vamos construir uma aplicação simples como um cadastro de clientes. Temos uma funcionalidade que é listar clientes do cadastro. Como você escreveria o código?
- List<Cliente> clientes = dao.getClientes(); // Lê-se: Get clientes do DAO (isso soa estranho!)
Que tal escrever com mais fluência, assim:
- List<Cliente> clientes = cadastro.clientes().lista(); // Lê-se Lista os clientes do Cadastro (bom)
Simples? Agora imagine que mudou a funcionalidade do seu sistema e você precisa listar todos os clientes com status ativo. Como poderia ficar seu código na camada de modelo?
- List clientesAtivos = cadastro.clientes().status().ativo().lista(); // Lê-se: lista clientes do cadastro com status ativo (bem fluente)
Muito parecido como falamos, parecido com uma DSL. Fácil de manter esse código?
Agora vou dar um exemplo e mostrar o código que muita gente ainda faz, e depois vou refatorá-lo para melhor o código.
Seu sistema precisa trazer todos os clientes com status ativo que estão com saldo devedor maior que 10.000 reais. Uma maneira que muita gente faz é:
- Cliente cliente = new Cliente();
- cliente.setStatus(Status.ATIVO);
- cliente.setSaldo(-10000);
- ClienteDAO dao = new ClienteDAO();
- List<Cliente> clientes = dao.obterClientes(cliente);
Quais são os problemas desse código? Vários! Ele funciona normalmente e vai trazer os resultados esperados, porém ele pode se tornar muito ruim para dar manutenção por não ser fluente. Vamos ver por quê?
Precisei instanciar um cliente e passei parâmetros para ele, como por exemplo o status e o saldo negativo. Porém a classe Cliente dessa forma está parecendo que não tem inteligência nenhuma, sendo simplesmente uma classe para transportar dados para fazer consulta no banco de dados. Um objeto deve, em termos de Orientação a Objetos, saber o porquê de sua existência e conhecer seus estados válidos. O objeto não deve fornecer métodos que invalidem seu estado, por exemplo cliente.setStatus(’Abobrinha’); Não devemos pensar em uma classe como uma mera coleção de atributos. Objetos também não são atributos mais funções, e sim estado + comportamento. O Phillip Calçado explica muito bem isso! Refatorando a linha 2 ficaria assim:
- Cliente cliente = new Cliente();
- cliente.status.ativar();
Vamos voltar a fluent interface. Outro problema do código acima é o nome do método dao.obterClientes(cliente). Esse método não diz o que ele faz. O desenvolvedor terá então que olhar sua implementação para entender o que o código faz. Isso é muito ruim e atrapalha o desenvolvimento! Uma maneira melhor é então refatorar o código para melhorar o nome do nosso método:
- List<Cliente> clientesAtivosDeverodes = dao.listaDeClientesAtivosComSaldoDevedorMaiorQue(10000);
Porém o nome desse método está parecendo um pouco macarrônico. Pelo menos é melhor que o anterior, já que agora o desenvolvedor não precisa olhar a implementação para ver o que ele faz. Porém como ficaria isso na fluent interface?
- List<Cliente> clientesAtivosDeverodes = cadastro.clientes().saldoDevedorMaiorQue(10000).status().ativo().lista();
Ou então você poderia escrever assim:
- List<Cliente> clientesAtivosDeverodes = cadastro.clientes().status().ativo().saldoDevedorMaiorQue(10000).lista();
Não importa a ordem, pois a fluência é a mesma! Outra coisa interessante é que eu não precisei instanciar um objeto Cliente e passar como parâmetro pois ele foi montado internamente. Portanto a listagem que antes tinha 5 linhas na regra de negócio agora tem apenas 1 linha. E não preciso ler o código de implementação para entender. Manutenção mais simples!
Quando pedimos para trazer os clientes de uma listagem é comum fazermos verificações no código do tipo:
- if(cliente.getStatus().equals(Status.ATIVO)){ // Le-se: cliente get Status igual status ativo (soa estranho)
- if(cliente.isStatusAtivo()){ // Le-se: cliente é status ativo
- if(cliente.status().isAtivo()){ // Le-se: Status do cliente é ativo (Mais fluente!!)
Mas como fica o código interno para eu fazer a fluent interface? Devo criar classes auxiliares?
Não importa como você vai implementar a fluent interface desde que fique fluente e fácil de manter o código. Não importa se você fizer classe abstrata ou interface, o importante é que o domínio da sua aplicação tenha só o que é referente ao domínio. A programação fica mais intuitiva trabalhando com interfaces ou classes abstratas aplicando fluent interface pois quando pedimos para a IDE listar os métodos só será listado o que faz sentido na fluência.
Vou dar um exemplo de como ficaria a implementação usando interfaces. Não é legal ficar criando classes auxiliares, e sim apenas o que faz sentido no domínio. Preciso, então, criar apenas 2 interfaces e vou utilizar o conceito de Repositório, o qual faz sentido no domínio. Portanto é preciso primeiro entender o conceito de repositório. O código ficaria assim:
- public interface RepositorioDeClientes {
- public RepositorioDeStatus status();
- public List<Cliente> lista();
- public RepositorioDeClientes obter(Cliente cliente);
- public void incluir(Cliente cliente);
- public RepositorioDeClientes saldoDevedorMaiorQue(int valor);
- }
- public interface RepositorioDeStatus {
- public RepositorioDeClientes ativo();
- public List<Cliente> lista();
- }
- public class ClienteDAO implements RepositorioDeClientes, RepositorioDeStatus{
- private Cliente cliente = null;
- private List<String> condicoes = new ArrayList<String>(); // para o listar() saber que tem uma condição no where do select. Isso é apenas ilustrativo! Pode ser feito de qualquer maneira.
- public List<Cliente> lista(){
- return null; // TODO fazer a busca no banco. Pode ser hibernate ou qualquer outra coisa
- }
- public RepositorioDeStatus status(){
- return this;
- }
- public RepositorioDeClientes ativo() {
- cliente.status().ativar();
- return this;
- }
- public RepositorioDeClientes obter(Cliente cliente) {
- this.cliente = cliente;
- return this;
- }
- public RepositorioDeClientes saldoDevedorMaiorQue(int valor){
- this.cliente.setSaldo(valor);
- condicoes.add(“saldo > “ + valor);
- return this;
- }
Junho 16, 2008 em 4:11 pm
Tudo é uma questão de ponto de vista.
O código fluente
List clientesAtivos = cadastro.clientes().status().ativo().lista()
poderia ser
List clientesAtivos = dao.getClientesAtivos()
Como recomenda-se que existam comentários Javadoc em implementações (não que todos os programadores façam isso), o descritivo seria suficiente para qualquer programador. O retorno do método é auto-suficiente, não necessitaria de um nome explícito “lista()”.
Junho 16, 2008 em 5:00 pm
Correto Fábio!
O javadoc é importante sim, porém mais importante é o nome do método, pois o javadoc pode facilmente ficar desatualizado. Se um código está mal escrito, ou pode ser otimizado, devemos refatorá-lo para facilitar a manutenção e deixá-lo mais legível, e o javadoc poderá ficar desatualizado.
Mesmo que o javadoc esteja integro, o entendimento do código não será tão rápido se o programador tiver que ler o javadoc ao invés do próprio código.
A fluent interface também facilita a conversa com o cliente, pois essa conversa será algo bem parecido com o que está no código. No domínio da aplicação não é bom ter DAOs misturados com a regra de negócio pois DAO não existe no domínio do cliente. Dao é uma infraestrutura que pode ser abstraída do domínio.
Já sobre o método lista(), isso não é regra e cada um pode implementar da maneira que achar fluente.
Eu decidi criar esse método para que a ordem dos métodos não importasse. É até parecido com a forma que o Criteria do Hibernate trabalha. Outro bom exemplo de fluent interface é o Criteria.
Junho 17, 2008 em 11:06 pm
Ricardo,
Sem ter me aprofundado muito na questão, tenho algumas dúvidas:
1) Eu teria de criar um repositorio para cada atributo de um objeto?
2) Me parece um sintaxe muito complexa ainda. Imagine que outro desenvolvedor tenha de consumir o ClienteDAO. Ele terá de conhecer o comportamento de cada metodo a ser chamado. Se isso fosse distribuído como uma Lib para outro projeto, sem os fontes, isso seria um problema. Não me parece que os nomes dos métodos estejam revelando sua inteção nesses casos. Por exemplo: cadastro.clientes().status().ativo().
Me parece um problema na concepção do atributo status. Por que não fazer cadastro.clientes().status(”ATIVO”
ou usar uma Enum pra isso?
Sei que essas questões que levantei existem mais por restrição da linguagem do que por questões de modelagem, mas seria interessante continuar o estudo desse caso.
Grande Abraço,
Felipe
Junho 17, 2008 em 11:32 pm
Oi Felipe,
Muito obrigado por me lembrar de falar sobre o enum. O enum estaria dentro da classe Status.
Sobre os repositórios, não precisa criar um pra cada atributo. No exemplo eu criei um pra cliente e um para status apenas porque eu queria essa fluencia.
O cliente não precisará conhecer a implementação do ClienteDAO justamente porque os métodos são auto-explicativos. O Criteria do hibernate é um exemplo de fluent interface, e você precisa conhecer a implementação do Criteria? O Hibernate é distribuido em jar e isso não é problema!
Você tem uma sugestão para melhorar a sintaxe que você acha ainda complexa?
Junho 17, 2008 em 11:39 pm
Bom, na verdade o problema na minha opinião está na modelagem do exemplo. Concordo que não é problema utlizar Fluent Interface em uma lib. A questão é se os metodos estão aderentes à premissa de revelar suas intenções. Mas ainda não estou preparado evoluir o seu exemplo. Como falei, talvez seja por restrições de tecnologia. Mas vou analisar melhor seu código pra te passar uma resposta mais completa da minha opinião.
Grande Abraço,
Felipe
Junho 18, 2008 em 12:14 am
Felipe,
Sobre a modelagem, nesse exemplo eu poderia simplesmente substituir a classe Status por um enum, só que assim eu não conseguiria dar um exemplo mais completo da fluent interface. Imagine então que o status seja mais complexo que uma simples string “Ativo” ou “DESATIVADO”, mas que tenha toda uma lógica dentro da classe Status. Por esse motivo eu criei a classe status.
Junho 24, 2008 em 2:55 am
E ae Ricardo!!
Bom… Você falou dos benefícios da fluent interface e tal… mas acho bom tb citar alguns pontos negativos né…
Vc não acha que na maioria dos casos uma fluent interface traz uma complexidade desnecessária para a criação do código?
Como por exemplo fazer:
cadastro.clientes().status().ativo().lista()
Na ideia fico bonitinho e tal, mas na pratica dá um puta trabalho chato implementar essa estrutura ai!!!
Sendo que AS VEZES poderiamos muito bem fazer:
cadastro.obterTodosOsClientesComEstatusAtivo();
também ficaria intuitivo e mais rápido de implementar.
ao invés de ter todo um trabalho para implemetar uma estrutura de encadeamento com vários ‘pontos’ como seria o caso de ‘cadastro.clientes().status().ativo().lista()’ faria da forma mais simples.
Acho que a idéia de usar fluent interface é muito legal e em alguns casos muito útil, como foi no exemplo do Guilherme Chapiewski. Porém tenho receio do pessoal achar que fluent interface é a ultima moda e fazer o código inteiro usando isso, e adicionando muita complexidade desnecessária no desenvolvimento, caracterizando assim um Anti-Pattern!!!
Talvez seria interessante mostrar quando usar e quando não usar. Acho que um bom momento pra se usar uma fluent interface seria para desenvolver uma biblioteca para outros utilizarem (WebServices) por exemplo. Ai vc tornaria muito simples e flexível de ser utilizado. Agora creio que se for pra vc mesmo utilizar é muito mais rápido e pratico vc utilizar o código tradicional, com nome do método gigante q informa o que o método faz, não necessitando de javadoc e pronto e acabou. Não me lembro de ter precisado utilizar fluent interface… vc usa normalmente?? Com que freqüência?
Bom, só uma correção:
Se vc lê:
1- cliente get Status igual status ativo
cliente.getStatus().equals(Status.ATIVO))
2 – cliente é status ativo
cliente.isStatusAtivo()
3 – Status do cliente é ativo
cliente.status().isAtivo()
Não!!!!! Usando a sua lógica o terceiro item se lê: “cliente status é ativo” o que não é muito intuitivo. Se vc quer o efeito “Status do cliente é ativo” na minha opinião poderia fazer:
status.doCliente(cliente).ehAtivo(); //ai sim vc esta lendo da forma que fala
Outro exemplo:
cadastro.clientes().status().ativo().lista()…. eu leio “cadastro cliente status ativo lista” e não “listar clientes do cadastro com status ativo” como vc disse. Para ler deste jeito acredito q deveria ser feito da seguinte maneira
listar().clientesDoCadastro(cadastro).comStatus().ativo(); // quero ver o trabalho pra implementar isso!!!!! ehehhe
ou seja o nome dos métodos EXATAMENTE da forma que se fala.. ai sim pra mim está fluente. O que acha?
Falow ai kara!!!! E parabéns pelo blog!
Junho 24, 2008 em 3:59 am
Felipe,
Para melhorar completamente a fluencia, uma alternativa seria construir uma DSL, mas tem a complexidade que você disse.
No exemplo que dei, apesar de didático, não teve muita complexidade para implementar. Só precisei trabalhar com interfaces e repositórios. Mas o que você falou é verdade, se tiver complexidade para implementar é melhor fazer da maneira convencional. Porém tem um ótimo livro sobre DDD do Evans que pode te ajudar na hora de implementar. A fluent interface não deve ser usada no sistema inteiro, apenas no model da sua aplicação. O livro separa bem isso, pois temos três pacotes: modelo, infraestrutura e aplicação. A fluent interface está no modelo e deve ser usada caso for ajudar na fluência do código. O risco de ser usado erroneamente existe, é por isso que deve-se ler o livro!
Ainda não implementei fluent interface nos projetos pois nenhum deles foi arquitetado seguindo DDD, porém o conceito de código intuitivo eu uso. Para mim esse ainda é um caso de estudo e por isso estou expondo minhas idéias para discusão. Assim o conhecimento sobre esse assunto será bom para quando houver a necessidade de implementar.
A fluência poderia ser melhorada facilmente se desconsiderarmos as restrições da linguagem. Eu implementei e senti as restrições na pele e o resultado foi usado como exemplo
A idéia não foi escrever o código como se estivesse lendo um texto, e sim da maneira que eu entendo quando leio o código. Porém pode ser que uma outra pessoa tenha uma leitura diferente sim!
Thanks!
Julho 31, 2008 em 3:50 am
Olá Ricardo, tudo bom?
Embora não seja trivial de se implementar, é usual, e com ganho, implementar Fluent Interface diretamente em QueryObjects. Por exemplo:
//query object
Busca busca = QueryObjectFactory.create();
…
List clientesAtivos = repositorioDeClientes
.executa(busca.por(atributo
.doTipo(”status”),
igualAh(ATIVO)));
É importante a sacada de deixar o nome de variaveis “Syntactic Sugar”, como “busca”, pois somente FluentAPI muitas vezes não resolve.
Considerações: atributo. e ATIVO utilizam de importação estática
Julho 31, 2008 em 12:29 pm
Oi Alessandro!
Interessante a idéia de QueryObjects.
Vou publicar um novo post mostrando uma fluência bem maior que esse post, utilizando de técnicas de Groovy e clousures.
Abraços,
Agosto 8, 2008 em 4:12 am
[...] Recentes Ricardo Almeida em Fluent InterfaceAlessandro Lazarotti em Fluent InterfaceNicholas em Escalabilidade [...]