Existe um padrão de design que se aplicaria a modelos de desconto?

35

Existem padrões de design conhecidos para implementar modelos de desconto?

Por modelos de desconto, quero dizer o seguinte:

  1. Se um cliente compra o Produto X, o Produto Y e o Produto Z, recebe um desconto de 10% ou US $ 100.

  2. Se um cliente compra 100 unidades do Produto X, recebe um desconto de 15% ou US $ 500

  3. Se um cliente trouxe no último ano mais de US $ 100 mil, ele recebe um desconto fixo de 20%

  4. Se um cliente comprou 2 unidades do Produto X, ele recebe 1 unidade do Produto X (ou Produto Y) gratuitamente.

  5. ...

Existe um padrão genérico que pode ser aplicado para lidar com todos os cenários acima? Estou pensando em alguns modelos, mas incapaz de encontrar um genérico.

Kanini
fonte
IIRC todos os tutoriais que eu vi com exemplos que envolvem descontos (há muitos) sugerem Estratégia padrão
mosquito
2
@ Kanini Este é um problema do mundo real? Nesse caso, esse sistema é em tempo real ou em tempo diferido? Essas regras são apresentadas como um mecanismo de regras ou valores de banco de dados? A pesquisa de descontos é hierárquica com base na prioridade? Padrão de estratégia iria funcionar na maioria dos casos, mas as regras devem ser tidos fora para fazê-lo funcionar
Ubermensch
3
Além disso, se alguém comprar 2 unidades do Produto X, um Produto Y e um Produto Z, receberia 10% e um produto extra X?
Ubermensch
@gnat alguns links para alguns desses tutoriais, por favor.
usar o seguinte comando

Respostas:

18

Se o problema for a necessidade de aplicar vários descontos, em determinadas circunstâncias, convém considerar o padrão da Cadeia de Responsabilidade .

Em poucas palavras, você passa as informações que deseja processar para o primeiro processador e decide a partir daí se as repassa para outros processadores antes de retornar o resultado.

Dessa forma, você pode alterar a estrutura e a sequência dos processadores sem alterar o código de chamada.

pdr
fonte
A cadeia de responsabilidade é outra boa. Pode até ser acoplado a uma estratégia. No caso em que apenas um desconto pode ser aplicado, cada estratégia é encadeada com outra. Cada cadeia calcula seu desconto (se o cliente é elegível), compara-o ao desconto anterior e passa os dados do cliente, pedido e desconto para a próxima cadeia. +1.
Thomas Owens
1
Apenas minha opinião, mas acho mais provável que a "Cadeia de responsabilidade" esteja sendo projetada em excesso para este caso. Uma lista simples de modelos de desconto (se necessário, com um número para pedido) deve ser feita. A lista em si é independente do cliente e de seus pedidos, pois todos os clientes devem ser tratados igualmente. "Cadeia de responsabilidade" é mais apropriada quando a lista de modelos de desconto muda com muita frequência no tempo de execução de uma maneira altamente dinâmica.
Doc Brown
11

Os padrões de estratégia, decorador e estado se destacam para mim como possíveis pontos de partida. O estado pode ser particularmente útil para 2 ou 3, pois 2 depende do estado do pedido e 3 depende do estado do cliente dentro de um período de tempo. O Strategy e o Decorator se destacam dos demais, pois você pode usar a estratégia para implementar vários algoritmos de cálculo de preço do pedido e o decorador para adicionar novos descontos ao pedido.

No entanto, lembre-se de que os padrões de design são apenas modelos. Pode não haver um único padrão que se aplique, mas sim um sistema de padrões. Considere também fazer modificações nos modelos descritos para ajustá-los melhor à sua solução. É melhor ter um bom design do que forçar um padrão em que não necessariamente ajude apenas por poder dizer que você tem um padrão.

Thomas Owens
fonte
Isso não é realmente o que o padrão de estado pretende fazer, não é?
Pd
@ pdr Não vejo por que não. Mas isso depende da sua implementação. Se o objeto do cliente rastrear descontos dependentes do cliente, pode haver uma operação para devolver os descontos aos quais o cliente é elegível. À medida que o cliente compra coisas, os atributos mudam e a implementação desse método muda por meio do padrão de estado.
Thomas Owens
1
Hmm, entendo o que você quer dizer. Eu acho que depende se o cliente é um objeto semi-permanente no aplicativo, ou apenas algo que vive no banco de dados e precisa ser atualizado. Não está claro da questão, é justo o suficiente. +1
pdr
3
De minhas experiências, esses tipos de regras de negócios com descontos são modificados o tempo todo pelos departamentos de marketing / vendas inconstantes. Há uma grande necessidade de torná-los controlados por dados e modificáveis ​​pelo usuário, em vez de totalmente orientados por código. Como isso afetaria a escolha do modelo?
precisa saber é o seguinte
@jfrankcarr Na minha opinião, não seria. Eu preenchia os valores para conjuntos de itens que levam a um desconto, as porcentagens desativadas e assim por diante de algum tipo de configuração. Tipo de construção dinâmica das transições e atributos das minhas máquinas de estado dos meus decoradores e estratégias.
Thomas Owens
10

Bem, eu projetaria um modelo de desconto como um par "Pré-condição" e "Desconto", em que "Pré-condição" é uma classe com métodos

  bool IsFulfilled(Customer c);

ou e

  bool IsFulfilled(Customer c, Order o);

e Discount tem um método void ApplyTo(Customer c). Isso permite combinar qualquer tipo de pré-condição com qualquer tipo de desconto (acho que essa é uma forma de "padrão de ponte").

Se você tiver um número fixo de pré-condições, poderá resolver o problema criando subtipos específicos (padrão de estratégia). No entanto, quando é permitido que suas pré-condições sejam muito complexas, com instruções lógicas como AND, OR e NOT, você pode implementar melhor algum tipo de interpretador de regras para as condições. As regras podem ser uma sequência de texto simples escrita em um "idioma específico do domínio" simples.

O mesmo vale para a classe "Desconto", você pode ter alguns subtipos para diferentes tipos de descontos ou uma abordagem geral em que as regras de desconto são fornecidas em forma de texto, avaliadas por algum intérprete.

Doc Brown
fonte
Minha intuição sugere que este poderia ser o que ele está procurando no contexto da pergunta
Ubermensch
4
  • Provavelmente, precisamos de uma interface IDiscount, pois todos os descontos diferentes são a mesma coisa, e queremos lidar com eles conceitualmente como descontos genéricos.

  • A classe "pedido deste cliente" provavelmente precisa de uma coleção de descontos. Lista? Jogo da velha? Lista vinculada? Não se importe ainda. Aplicam-se descontos à compra, não ao cliente!

  • Mantenha a criação da instância de desconto separada do cliente, carrinho de compras, histórico etc. Isso mudará muito - como apontou @jfrankcarr.

  • Provavelmente, uma classe diferente para cada desconto, pois o algoritmo e os parâmetros para cada desconto variam muito e de forma imprevisível.

  • Vejo muita manipulação de eventos, pois o cálculo do desconto responde a alterações no carrinho de compras e vice-versa.

Aplicação de padrão de design

  • Eu vejo um strategy pattern. IDiscount é a interface para implementar diferentes algoritmos de desconto.
  • Eu vejo um factory. Certamente não é uma abstract factory patternaula completa , mas uma única classe neste momento da análise. Razoavelmente, deve haver um único local em que haja contexto suficiente para decidir quais descontos se aplicam e depois criá-los. Uma classe. Se as regras para a aplicação de descontos explodirem posteriormente devido a uma grande proliferação do Departamento de Marketing, qualquer lógica adicional de Construção de descontos ainda deverá se fundir nessa classe básica de fábrica, eu acho.

  • Eu posso ver Chain of Responsibility. Isso não é mutuamente exclusivo da factoryideia. Em vez de iterar uma coleção de descontos, cada desconto chama a próxima pessoa. A classe "pedido do cliente" não possui uma coleção de descontos nesse caso.

  • O fator "hmmmm ...." na Cadeia de Responsabilidade, eu acho, é que cada Desconto tem uma referência ao próximo. A implicação é que a ordem é importante. O que não é o caso. Além disso, o conceito que o CR incorpora é que um objeto não pode atender à solicitação e, portanto, é passado "até a próxima autoridade superior". Nosso modelo é diferente. A única solicitação é calcular. Todo desconto faz isso. A saída ou efeito pode ser nulo, mas todo desconto é calculado. Eu instintivamente me inclino a uma maior fidelidade do mundo real.

Suposições

  • Os descontos são baseados no carrinho de compras atual e / ou no histórico de compras
  • Zero ou mais descontos podem ser aplicados. Não há descontos mutuamente exclusivos
  • O cálculo adequado não depende da ordem em que os descontos são aplicados.

O que muda, o que permanece o mesmo?

  • Os descontos são muito diferentes. Número e tipo de parâmetros diferentes para compor cada regra.

  • Os argumentos para descontos qualificados são alterados conforme o carrinho de compras.

  • O número de descontos disponíveis muda

  • Os descontos que esse cliente qualifica para alterações à medida que seu carrinho de compras muda.

  • O histórico de compras não muda no contexto desta compra

  • O custo total é alterado dinamicamente em função das linhas de compra e dos descontos aplicados.

  • Após a aplicação inicial, a produção de um desconto pode mudar, como a quantidade da compra, por exemplo.

radarbob
fonte
Ótima e completa resposta ... MAS eu só tenho uma preocupação e é que a seção de suposições não deve estar lá, pelo menos para não levar a resposta. A idéia é que o padrão ajude exatamente a dar conforto e a esquecer as "premissas", a regra genérica não precisa saber como os cálculos são feitos e o que qualquer implementação detalhada usa no contexto (cliente, itens do carrinho , Hora do dia, estação etc.). Realmente ajuda completa embora
le0diaz
As marcações iniciais e a seção 'Pressupostos' são apenas o meu raciocínio "mostre seu trabalho" sobre o problema em si, o qual, obviamente, tem uma importância no design do modelo de desconto. Por exemplo, minhas suposições sobre ordem de execução de desconto e interdependência me levam a enfatizar a Cadeia de Responsabilidade. Observe que estou pensando na intenção do padrão, não na complexidade como o @docbrown. Sou um defensor exuberante por refletir a intenção no design.
Radarbob
1

Logicamente, um modelo de desconto pode ser qualquer coisa , então você não pode presumir que pode programar todos os casos com antecedência. Nem alguém que responde à sua pergunta pode ter certeza absoluta do que realmente precisa. No entanto, supondo que você obtenha os tipos usuais de descontos encontrados no mundo real ...

Uma grande questão é se os descontos serão programados ou se você deseja que os usuários os inscrevam. Como mencionado acima, você nunca pode programá-los, mas geralmente o objetivo é tentar torná-lo mais como os casos comuns, em vez de programá-los todos. Isso se aplica até certo ponto, mesmo se os programadores forem usados ​​para criar todos os descontos.

Martin Fowler menciona "Método de instância individual" em "Padrões de análise: modelos de objetos reutilizáveis" como parte de como implementar "Regras de postagem" para sistemas de contabilidade, mas as regras parecem bastante semelhantes às suas. Eu daria mais detalhes, mas é um trabalho protegido por direitos autorais e

Para uma interface com o usuário, você precisa criar casos de uso bastante simples ou criar um interpretador e um construtor de consultas. Possivelmente ambos, um para casos simples e outro mais avançado. Se você escreve um intérprete, esse é provavelmente um bom argumento para usar o padrão Interpreter, pois é relativamente simples de codificar em comparação com um gerador de analisador, e o tempo de análise mais lento provavelmente não importa. (Se você gosta de usar geradores de analisador, não me deixe pará-lo).

Não tente fazer tudo com um intérprete - em algum momento você está apenas programando em sua própria linguagem ruim, para que você possa usar uma linguagem real. Se a sua linguagem interpretada suportar funções (provavelmente deve suportar chamá-las - defini-las é duvidosa), elas podem ser codificadas em uma linguagem real. Não vá mais longe nesse caminho do que o necessário.

Não importa o que você faça, alguém desejará que o desconto se baseie em sua compra dentro de 30 dias úteis de uma promoção - onde os dias úteis contam apenas se não houver feriado na região definida pelo código postal da loja ou pelo cliente. Código postal. Portanto, não tente projetar o sistema perfeito com antecedência - suponha que às vezes seja necessário escrever código para novos tipos de descontos e projetá-lo adequadamente.

psr
fonte
0

Existe algum motivo para perguntar se existe um padrão útil para isso? Que tipo de padrão é necessário - estrutural ou comportamental?

Idealmente, se eu escrevesse um software para isso, basta um algoritmo . Uma função simples que calcula o desconto total da seguinte maneira:

cart.calculateDiscount(productVector);

Você não precisa de nada além disso!

Para esclarecer: eu entendo que haverá muitas regras - a mais básica dessa representação deve estar na forma de uma base de regra (conjunto de atributos de dados e desconto resultante) e a função como a acima iteraria para computá-la. Se as regras forem adicionadas ou removidas, você não deverá alterar o código - basta alterar a base de regras.

O padrão será necessário apenas se precisarmos que objetos diferentes precisem acessar APIs um do outro ou se comunicar para implementar uma tarefa.

PS: Pense nisso - quando o firewall processa pacotes e passa ou rejeita pacotes (ou modifica) - que padrão de design ele usa? A resposta é NENHUMA das opções acima descritas!

Dipan Mehta
fonte
Claro que você precisa de mais do que isso !!!. A idéia é que o algoritmo não esteja totalmente associado à implementação do código. Se você verificar os cenários, é altamente provável que surjam mais cenários, e você mencionou de alguma forma, ele não é realmente do que qualquer outra 'regra' dependerá. É ingênuo pensar que uma regra dependerá apenas da lista de produtos, mas, na realidade, depende do cliente, do tempo, da estação e assim por diante. Não sei o que a implementação Firewall você marcada, mas os que eu verifique têm muitos padrões de design / estrutural
le0diaz