Refactoring: Substituindo Condicional por Polimorfismo usando enum!
Esse refactoring é muito interessante quando feito sobre condicionais que lidam com parâmetros que podem mudar com freqüência. Esses parâmetros podem ser tipos de relatórios, comandos utilizados para invocar métodos remotamente, estados de uma máquina entre outros.
O objetivo da utilização do polimorfismo é justamente dar flexibilidade e permitir que o sistema evolua (mude) sem precisar adicionar novos blocos de else if no código toda vez que um novo parâmetro for adicionado, alterado ou removido! Sendo assim, não precisa sair alterando todos os blocos if else do sistema, apenas os pontos mais suscetíveis a mudança e que a flexibilidade fará com que as operações de manutenção possam ser mais agradáveis.
Nosso foco são estruturas que dependem de parâmetros para executar ações como por exemplo gerar um extrato. Os extratos podem ser de vários tipos: Semanais, quinzenais, mensais, completos e tantos outros quanto os clientes necessitarem ao longo da vida útil do sistema.
Abaixo, a classe responsável por gerar um extrato utilizando um tipo passado como parâmetro:

Estruturas como essa além de serem procedurais, diminuem a performance do sistema e com o tempo aumentam os custos de manutenção do projeto, contribuindo para um código ilegível (imagine com 50 tipos de extrato) e desmotivante.
Uma forma interessante de resolver o problema é mapear os estados em uma enum e utilizá-la para mapear os estados polimorficamente:

E finalmente podemos refatorar o método condicional da classe que gera extratos para delegar a geração de extratos para as subclasses:

Lembrando que essa é uma forma simplificada da utilização do padrão Strategy, no entanto evitamos a criação excessiva de artefatos (cada estratégia numa subclassse) e utilizamos a enum para fazer esse trabalho.
Parabéns pelo post. Ficou muito simples de ler o código e entender o objetivo do exemplo.
[]s
Abu
Parabéns, vou referenciar este post em meu blog. Um abraço!
Ótimo post Branas!
Vejo a sua solução como uma ótima alternativa para implementação de uma fábrica de Strategies. Se as Strategies retornadas pelo enum forem stateless, pequenas tanto em número quanto em complexidade, esta é uma abordagem muito apropriada, simples e reforça todos os princípios e melhores práticas de orientação a objetos. Apenas acrescentaria uma interface definindo o contrato da Strategy que o enum implementaria, desacoplando a estratégia (interface) da fábrica (enum).
Definitivamente vou recomendar esta abordagem em nossos projetos.
Grande abraço!
Se a idéia é reforçar a orientação a objetos, esta técnica de Strategy com Enum é um fracasso.
Não permite herança, não permite polimorfismo. Não há como herdar uma estratégia, nem tornar uma estratégia a implementação de outra interface.
Por esta razão, prefiro o bom e velho uso de interfaces e factories onde as implementações se auto-registram.
Discordo do Bruno. Essa estratégia é excelente e promove várias das melhores práticas de orientação a objetos.
Pode ser melhorada, como disse em meu comentário anterior poderíamos colocar uma interface desacoplando a estratégia de suas implementações.
Acho muito interessante a idéia de usar enum para implementar as estratégias e também a fábrica em uma mesma classe. Lembro que este acoplamento (fábrica - implementações de estratégias) pode gerar problemas a medida que acrescentarmos mais estratégias, mas sua simplicidade é muito conveniente em muitas ocasiões.
A solução proposta pelo Bruno é a evolução natural, mas pesada demais para ser usada indiscriminadamente em um projeto real.
IMHO, saber os prós e contras de Strategy + Factories (solução completa) e Enum Strategy (simplificação) e utilizá-las com sabedoria e sem preconceitos é um excelente caminho para melhorar a arquitetura de seu software.
@Bruno Borges
Fala Bruno! Quanto tempo! (EDS)
Então, como em qualquer processo de refactoring as coisas precisam acontecer gradativamente ou não acontecem. Se for necessário, com certeza a solução evoluirá para abordagens mais complexas. No entanto, combatendo a famosa “Generalidade Especulativa” [Foote], não precisamos inserir complexidade desnecessária. Muitas vezes é melhor condensar uma hierarquia de classes se não estiver fazendo sentido manter classes abstratas desnecessárias. Em termos de resultados, a solução proposta cumpre seu papel e flexibiliza o sistema, tornando-o menos procedural e incentivando a remoção de lógica condicional aninhada.
Abraços!
Gambiarra detected… Totalmente fora dos propósitos do enum.
@Fernando
Olá Fernando!
Acho que não precisa ser tão radical. Concordo que está fora dos propósitos iniciais do enum, mas isso não configura uma gambiarra. Se seguíssemos esse mesmo pensamento inovações muito importantes como Fluent Interfaces não seriam bem recebidas, a final, o retorno de um método não foi originalmente desenhado para promover o in-line de chamadas.
A inovação está na quebra de paradigmas, pense nisso
Abraços!
Acho que você precisa rever seus conceitos de “propósitos do enum”.
No exemplo especificado temos estados bem definidos, o que por sí só já levaria a uma implementação utilizando enum (não fugindo do propósito).
Sugiro que leia o texto abaixo no guideline sobre enums da própria Sun, já que essa técnica de polimorfismo é conhecida e utilizada.
http://java.sun.com/j2se/1.5.0/docs/guide/language/enums.html
There is another way give each enum constant a different behavior for some method that avoids these problems. You can declare the method abstract in the enum type and override it with a concrete method in each constant. Such methods are known as constant-specific methods.
Constant-specific methods are reasonably sophisticated, and many programmers will never need to use them, but it is nice to know that they are there if you need them.