Executando verificação de segurança...
Respondendo a "Mas quais problemas exatamente eles resolvem? J..." dentro da publicação [Não disponível]
2

Sim, é teórico porque acredito que a ideia é ser uma descrição do "caso geral".

Ao codificar, vc verifica se tem uma situação similar e aí vê se tem algum pattern que pode te ajudar.

Por exemplo, eu preciso criar um tipo diferente conforme determinados parâmetros? Talvez usar factory seja uma opção. Eu preciso ter um algoritmo (um comportamento) diferente conforme cada situação? Deixa eu ver se usar strategy é uma boa. E assim por diante.

A ideia de catalogar - a meu ver, pois não sei se era a intenção original dos autores - é criar um vocabulário em comum, facilitando a comunicação. Em vez de descrever em detalhes o que é pra fazer ("crie uma classe/função/whatever que faça A, depois B e C, etc"), vc só diz que é pra usar o pattern X. Toda área tem vocabulário técnico e termos próprios, a nossa não é diferente.

Claro que em TI existe muito essa coisa de dar nomes novos pra coisas velhas, mas enfim... O fato é que agora esses nomes existem e se vc mencioná-los, muita gente vai entender do que se trata. Se precisava mesmo, sinceramente, acho que não faz mais tanta diferença. Mas agora que tem e todo mundo entende, não acho ruim usar.

Carregando publicação patrocinada...
-2

Por exemplo, eu preciso criar um tipo diferente conforme determinados parâmetros? Talvez usar factory seja uma opção. Eu preciso ter um algoritmo (um comportamento) diferente conforme cada situação? Deixa eu ver se usar strategy é uma boa. E assim por diante.

Ambos os casos você consegue usar um if.

Ainda não consigo ver como design patterns ajuda nesse caso.

3

Consegue sim e isso tbm e um strategy ou uma simple factory, só nao e do GOF.

Complementando o que o amigo comentou anteriormente, tem vídeos no YouTubedo Alister Cockburn e do Uncle Bob descrevendo a soluçãodos design patterns exatamente como ele citou. Eram problemas comuns e precisavam chamar por algum nome, o que torna tudo mais simples. Os problemas ja existiam muito antes de alguem catalogar, o que fizeram foi padronizar e otimizar uma solução e que fazem sentido até hj e com um pouco de criatividade, talvez vc possa até melhorar a solução já existente porque não é um design fechado é uma proposta de caminho e não uma religião.

Pensa assim, é mas simples vc dizer o nome de alguém ou descreve-la?

O lance é, vc só vai entender a necessidade quando vc realmente tiver a dor e realmente entender a necessidade, antes disso estude e não se preocupe.

Quando vc perceber, estará procurando uma solução mais manutenível para o seu problema robusto.

3

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, que por sua vez tem o link pra este artigo). 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):

// 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, ao final, tem vários links para artigos com implementações dos patterns em C (o exemplo acima foi adaptado deste).

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:

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:

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:

// 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 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.

1
const categorias = {
  bronze: {
    maxParcelas: 2,
  },
  prata: {
    desconto: 0.95,
    maxParcelas: 4,
  },
  ouro:  {
    desconto: 0.98,
    maxParcelas: 20,
  },
  diamante: {
    desconto: 0.10,
  },
}

function calcularPreco(categoria, valor) {
  return valor * (categorias[categoria]?.desconto || 1);
}

function calcularParcelas(categoria) {
   return categorias[categoria]?.maxParcelas || 10;
}

Pronto, agora fica fácil adicionar categorias e funções.

1

Sim, esta é uma das várias implementações possíveis de strategy, com todas as vantagens que este pattern traz.

O problema é que vc ainda está preso à ideia de que DP é só sobre orientação a objetos, e que só dá pra fazer com classes. Não é, e novamente deixo o link para o artigo que mostra como implementar em C (sem usar classes e orientação a objeto, portanto).

E segue também os demais links que indiquei em outro comentário:

O que acontece é que o livro do GoF se tornou tão popular que fez com que muita gente achasse que DP e OO são a mesma coisa, ou que só dá pra fazer o primeiro usando o segundo.

-4

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.

Isso não faz o menor sentido. Design patterns é, e sempre será sobre orientação a objetos. Só porque eu uso if/switch não significa que estou usando o design pattern.

E daí? E se eu estiver usando uma linguagem que não é orientada a objetos? No link que indiquei, ao final, tem vários links para artigos com implementações dos patterns em C (o exemplo acima foi adaptado deste).

Ele apenas demonstra como "implementar" o pattern em C, não que usar if/switch é o design pattern. Não é só porque dá para "implementar" em C que significa que seja algo universal.

Se eu usar if/switch, cada alteração nas categorias implica em ter que revisar o código de todas essas funções.

Você está 'otimizando' para um cenário absurdo, em que terá trocentas funções.
Além disso você superestima o esforço gasto pra adicionar uma nova categoria na versão com if/switch.

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).

Mas se precisar adicionar uma função calcularFrete(categoria) em que cada categoria tem um cálculo de frete diferente, você vai precisar alterar em todas as classes. E no caso do if/switch, é só adicionar a nova função de frete.

1

Design patterns é, e sempre será sobre orientação a objetos

Não é, tanto que em outro comentário indiquei este link: Non-OOP Design Patterns?. E novamente eu destaco o primeiro comentário que tem lá:

I think the biggest disservice the popular design pattern books did was create a whole slew of people that believed the patterns only apply to object oriented languages.

Em tradução livre (com ênfase minha):

Acho que o maior desserviço dos livros que popularizaram os design patterns foi que eles fizeram uma quantidade enorme de pessoas acreditarem que os patterns só se aplicam a linguagens orientadas a objeto.

Que parece ser exatamente o que está acontecendo aqui :-)

E pra quem negativou, sugiro dar uma olhada também aqui e aqui pra entender que design pattern não é sobre orientação a objeto.


Quanto ao restante, é sempre um trade-off que deve ser avaliado caso a caso. Se o sistema for relativamente simples, não justifica a complexidade de ter classes e toda aquela parafernalha, por exemplo.

Tanto que há muitos que defendem - e eu concordo - que as vantagens da orientação a objeto só aparecem em projetos complexos, com bases de código bem grandes. Se não é o seu caso, se acha que não precisa, então não use. Eu já precisei refatorar um código que começou simples (if/switch atendia bem), mas foi ficando complexo e viu-se vantagem em mudar pra algo como o exemplo acima (a manutenção nos anos seguintes ficou bem mais fácil).

Mas se precisar adicionar uma função calcularFrete(categoria) em que cada categoria tem um cálculo de frete diferente, você vai precisar alterar em todas as classes. E no caso do if/switch, é só adicionar a nova função de frete.

Não necessariamente. Por exemplo, se o frete só é diferenciado pra categoria ouro, e pro resto é igual, eu só preciso criar na classe base (com o valor padrão) e na classe ouro (com o valor diferente).

E mesmo que tenha que adicionar em todas as classes, será feito apenas uma vez. Depois, se o frete mudar para alguma categoria, ou se eu criar uma categoria nova ou remover uma existente, não preciso mais mexer na função (se fosse com if, eu teria que mexer sempre nela).

Mas como já disse, cada caso é um caso. Se as regras não mudam tanto assim, aí tanto faz. Ninguém disse que é obrigatório usar DP. Mas eles podem ser úteis se souber quando e como usar.

0

No mundo Pascal/Delphi tem um termo bastante famoso, "if bom é if morto".
Uso excessivo de If aumenta a complexidade do código e o auto acoplamento.
Conforme frase retirada do livro "If bom é if morto" do Thulio Bittercourt:

Quando o programador recorre frequentemente a instruções IF para controlar o fluxo do programa, ele pode inadvertidamente aumentar a complexidade cognitiva, exigindo que quem lê o código mantenha múltiplas possíveis ramificações e condições em mente.
Esse aumento na carga mental não apenas dificulta a compreensão do código, mas também eleva o risco de erros, uma vez que fica mais complicado garantir a cobertura de todos os caminhos durante os testes.

3
0