Acho que tem um detalhe que vc não entendeu: **usar `if` é uma das formas de implementar esses _patterns_**.
Um *design pattern* só descreve de forma geral (teórica, genérica) o que é pra fazer. Mas **como** isso será feito fica a cargo de quem for implementar.
Vc pode fazer com `if`, `switch`, polimorfismo, ponteiro de função ou seja lá o que for que a linguagem que está usando disponibiliza. Se no fim o código faz o que o *design pattern* descreve, então vc o usou - mesmo que ache que não :-) Claro que dá pra discutir qual solução é "melhor" de acordo com vários critérios arbitrários, mas isso vai além da definição do *pattern* (de novo: ele só diz o que deve ser feito, mas não como).
E repito que o grande problema é que as implementações orientadas a objeto se tornaram tão populares que muita gente acha que é a única forma de usar DP. Ou pior, muitos acham que se não usar classes ou uma linguagem orientada a objeto (ou se "trocar por um `if`"), então não está usando DP.
---
Só pra dar um exemplo prático (adaptado do [link que indiquei acima](https://www.adamtornhill.com/articles.htm), que por sua vez tem o link pra [este artigo](https://www.adamtornhill.com/Patterns%20in%20C%203,%20STRATEGY.pdf)). Vamos supor que um site de e-commerce tenha categorias diferentes de consumidor, e cada um tem uma faixa de desconto: clientes bronze têm 2% de desconto, clientes prata têm 5% e clientes ouro têm 10%.
Então na hora de calcular o preço, eu preciso saber a categoria do cliente e aplicar o respectivo desconto. Ou seja, tem um comportamento diferente dependendo de cada caso. Ou, para ser mais técnico, eu posso selecionar um algoritmo diferente de cálculo de desconto em *runtime*, de acordo com determinados parâmetros (no caso, as condições - seja lá quais forem - que determinam a categoria de um cliente).
E olha só, toda esta situação (precisar escolher um algoritmo/comportamento diferente em cada caso) tem um nome: *strategy*!
Então eu poderia implementar com `if` ou `switch` (exemplos em JavaScript, sem nenhum motivo especial):
```javascript
// com if
function calcularPreco(categoria, valor) {
if (categoria == 'bronze') {
return valor * 0.98; // 2% de desconto
} else if (categoria == 'prata') {
return valor * 0.95; // 5% de desconto
} else if (categoria == 'ouro') {
return valor * 0.9; // 10% de desconto
}
// se não é nenhuma das categorias acima, não tem desconto
return valor;
}
// ou com switch
function calcularPreco(categoria, valor) {
switch (categoria) {
case 'bronze':
return valor * 0.98; // 2% de desconto
case 'prata':
return valor * 0.95; // 5% de desconto
case 'ouro':
return valor * 0.9; // 10% de desconto
default:
// se não é nenhuma das categorias acima, não tem desconto
return valor;
}
}
```
Qual desses é a implementação do *strategy*? **Ambos**!
> "*Ah, mas o livro do GoF diz pra usar classes, interfaces, polimorfismo, blablabla*"
E daí? E se eu estiver usando uma linguagem que não é orientada a objetos? No [link que indiquei](https://www.adamtornhill.com/articles.htm), ao final, tem vários links para artigos com implementações dos *patterns* em C (o exemplo acima foi adaptado [deste](https://www.adamtornhill.com/Patterns%20in%20C%203,%20STRATEGY.pdf)).
**Mas aqui caímos em outra questão**, que vai além dos *patterns*. E talvez seja por isso que muita gente acha que só existe uma única forma de implementar cada um deles.
Os exemplos acima têm alguns problemas (também citados no mesmo artigo). O principal - a meu ver - é que agora o cálculo de desconto está fortemente acoplado com as categorias. Se uma nova categoria surgir, eu preciso mudar o cálculo de preço.
Indo mais além, vamos supor que existam outras coisas associadas à categoria. Por exemplo, o preço do frete pode ser diferente, os descontos podem aumentar de acordo com a quantidade de itens (e essa quantidade também varia para cada categoria), clientes ouro podem escolher mais parcelas, etc etc etc. E vamos supor que para cada uma dessas situações existe uma função com um `if` ou `switch`.
Então se uma categoria nova é adicionada (ou alguma existente é removida, ou algum desses valores muda para uma delas), vc precisará mudar todas as funções associadas. O que era só um "simples `if`" se torna um pesadelo de manutenção.
E como resolver? Uma solução é desacoplar as categorias dos respectivos cálculos. E é aí que surge a implementação "clássica" com classes:
```javascript
class CategoriaBronze {
calcularDesconto(valor) {
return valor * 0.98; // 2% de desconto
}
}
class CategoriaPrata {
calcularDesconto(valor) {
return valor * 0.95; // 5% de desconto
}
}
class CategoriaOuro {
calcularDesconto(valor) {
return valor * 0.9; // 10% de desconto
}
}
function calcularPreco(categoria, valor) {
if (!categoria)
// se não tem categoria, não tem desconto
return valor;
return categoria.calcularDesconto(valor);
}
```
> Obs: claro que na implementação "clássica" existe uma interface (ou classe abstrata) `Categoria`, da qual todas as categorias herdam. Mas a ideia geral é essa.
Desta forma, para a função `calcularPreco` tanto faz se eu criar, remover ou modificar alguma categoria, pois eu não preciso mais mudá-la.
E no caso das categorias terem mais coisas (cálculo de desconto, de frete, benefícios específicos, etc), basta adicionar os métodos em cada uma. E cada função só recebe a categoria, e delega para ela os respectivos cálculos.
Ou seja, em vez disso:
```javascript
function calcularPreco(categoria, valor) {
if (categoria == 'bronze') {
return valor * 0.98; // 2% de desconto
} else if (categoria == 'prata') {
return valor * 0.95; // 5% de desconto
} else if (categoria == 'ouro') {
return valor * 0.9; // 10% de desconto
}
// se não é nenhuma das categorias acima, não tem desconto
return valor;
}
function maximoParcelas(categoria) {
// clientes ouro podem escolher mais parcelas
if (categoria == 'ouro') {
return 20;
}
return 10;
}
```
Eu poderia ter isso:
```javascript
// aqui eu mudei para ter uma classe base da qual todas herdam
class Categoria {
// por padrão, todas as categorias podem parcelar em até 10 vezes
maxParcelas() {
return 10;
}
calcularDesconto(valor) {
return valor; // por padrão, não tem desconto
}
}
class CategoriaBronze extends Categoria {
calcularDesconto(valor) {
return valor * 0.98; // 2% de desconto
}
}
class CategoriaPrata extends Categoria {
calcularDesconto(valor) {
return valor * 0.95; // 5% de desconto
}
}
class CategoriaOuro extends Categoria {
calcularDesconto(valor) {
return valor * 0.9; // 10% de desconto
}
maxParcelas() {
return 20; // clientes ouro podem parcelar em mais vezes
}
}
function calcularPreco(categoria, valor) {
if (!categoria) // se não tem categoria, não tem desconto
return valor;
return categoria.calcularDesconto(valor);
}
function maximoParcelas(categoria) {
if (!categoria)
return 5;
return categoria.maxParcelas();
}
```
No primeiro código, se eu mudar alguma categoria (seja adicionando, removendo ou modificando uma existente), precisarei verificar todas as funções (`calcularPreco` e `maximoParcelas`). E vamos supor que o sistema tem mais trocentas funções para tratar de diferentes aspectos relacionados à categoria (cada uma tem um preço de frete diferenciado, ofertas especiais em itens específicos, brindes, etc etc etc). Se eu usar `if`/`switch`, cada alteração nas categorias implica em ter que revisar o código de todas essas funções.
Já se usar o segundo código, eu não preciso alterar as funções `calcularPreco` e `maximoParcelas` (e nem todas as outras trocentas funções que mencionei). Claro que ainda vou ter que testar tudo, mas pelo menos eu não precisei revisar o código de todas elas pra saber qual precisa ser modificada (em caso de adicionar ou remover uma categoria, por exemplo, eu teria que mexer em todas se usasse `if`).
"_Ah, então só dá pra fazer com classes?_". Claro que não. Menciono novamente o [artigo](https://www.adamtornhill.com/Patterns%20in%20C%203,%20STRATEGY.pdf) que explica como fazer em C, e ele usa ponteiros de função.
---
Ou seja, se vc só "trocar por `if`", ainda estará implementando o *pattern*. Mas existem outras questões que vão além desta simples troca, como a dificuldade de manutenção em um código com alto acomplamento.
Talvez por isso muita gente associe o *pattern* com "não use `if`", ou ache que se usar `if` não está implementando o *pattern*. Está sim, mas talvez não seja da melhor forma, por causa desses problemas de manutenção.