Gibt es ein Designmuster, das für Rabattmodelle gelten würde?

35

Gibt es bekannte Entwurfsmuster für die Implementierung von Rabattmodellen?

Mit Rabattmodellen meine ich Folgendes:

  1. Wenn ein Kunde Produkt X, Produkt Y und Produkt Z kauft, erhält er einen Rabatt von 10% oder 100 USD.

  2. Wenn ein Kunde 100 Einheiten von Produkt X kauft, erhält er einen Rabatt von 15% oder 500 USD

  3. Wenn ein Kunde im letzten Jahr mehr als 100.000 US-Dollar eingespielt hat, erhält er einen pauschalen Rabatt von 20%

  4. Wenn ein Kunde 2 Einheiten von Produkt X gekauft hat, erhält er 1 Einheit von Produkt X (oder Produkt Y) gratis.

  5. ...

Gibt es ein generisches Muster, das für alle oben genannten Szenarien angewendet werden kann? Ich denke an einige Modelle, kann aber keine generische finden.

Kanini
quelle
IIRC alle Tutorials, die ich mit Beispielen mit Rabatten gesehen habe (es gibt viele), schlagen Strategie-Muster vor
Mücke
2
@Kanini Ist das ein echtes Problem? In einem solchen Fall ist dieses System in Echtzeit oder zeitversetzt? Werden diese Regeln als Regelengine oder Datenbankwerte dargestellt? Ist die Suche nach Rabatten nach Priorität hierarchisch? Ein Strategiemuster würde in den meisten Fällen funktionieren, aber Ihre Regeln müssen herausgerechnet werden, damit es funktioniert
Ubermensch
3
Auch wenn jemand 2 Einheiten von Produkt X, ein Produkt Y und ein Produkt Z kauft, würde er sowohl 10% als auch ein zusätzliches Produkt X erhalten?
Ubermensch
@gnat einige Links zu einigen dieser Tutorials bitte.
user16764

Antworten:

18

Wenn das Problem darin besteht, dass Sie unter bestimmten Umständen mehrere Rabatte anwenden müssen, sollten Sie das Muster der Verantwortungskette berücksichtigen .

Kurz gesagt, Sie geben die Informationen, die Sie verarbeiten möchten, an den ersten Prozessor weiter, und von dort entscheidet er, ob er sie an weitere Prozessoren weitergibt, bevor er das Ergebnis zurückgibt.

Auf diese Weise können Sie die Struktur und Reihenfolge der Prozessoren ändern, ohne den aufrufenden Code zu ändern.

pdr
quelle
Die Kette der Verantwortung ist eine weitere gute. Könnte sogar mit einer Strategie gekoppelt sein. In einem Fall, in dem nur ein Rabatt angewendet werden kann, ist jede Strategie mit einer anderen verkettet. Jede Kette berechnet ihren Rabatt (sofern der Kunde berechtigt ist), vergleicht ihn mit dem vorherigen Rabatt und leitet die Kunden-, Bestell- und Rabattdaten an die nächste Kette weiter. +1.
Thomas Owens
1
Nur meine Meinung, aber ich finde es wahrscheinlicher, dass "Chain of Responsibility" für diesen Fall übertrieben ist. Eine einfache Liste von Rabattmodellen (bei Bedarf mit einer Bestellnummer) sollte dies tun. Die Liste selbst ist unabhängig vom Kunden und seinen Aufträgen, da alle Kunden gleich zu behandeln sind. "Chain of Responsibilty" ist besser geeignet, wenn sich die Liste der Rabattmodelle zur Laufzeit sehr dynamisch ändert.
Doc Brown
11

Strategie-, Dekorations- und Statusmuster sind für mich die potenziellen Ausgangspunkte. Der Status kann für 2 oder 3 besonders nützlich sein, da 2 vom Status der Bestellung und 3 vom Status des Kunden innerhalb eines Zeitraums abhängt. Strategy and Decorator heben sich von den anderen ab, da Sie mithilfe der Strategie mehrere Algorithmen zur Berechnung des Auftragspreises implementieren und Decorator der Bestellung neue Rabatte hinzufügen können.

Denken Sie jedoch daran, dass Entwurfsmuster nur Modelle sind. Möglicherweise gilt nicht ein einziges Muster, sondern ein Mustersystem. Nehmen Sie auch Änderungen an den beschriebenen Modellen vor, damit sie besser zu Ihrer Lösung passen. Es ist besser, ein gutes Design zu haben, als ein Muster zu erzwingen, bei dem es nicht unbedingt hilft, nur um sagen zu können, dass Sie ein Muster haben.

Thomas Owens
quelle
Das ist aber nicht wirklich das, was das staatliche Muster bewirken soll, oder?
pdr
@ pdr Ich verstehe nicht warum nicht. Dies hängt jedoch von Ihrer Implementierung ab. Wenn Ihr Kundenobjekt kundenabhängige Rabatte nachverfolgt, gibt es möglicherweise eine Operation, mit der die Rabatte zurückgegeben werden, für die der Kunde berechtigt ist. Wenn der Kunde etwas kauft, ändern sich die Attribute und diese Methodenimplementierung ändert sich über das Statusmuster.
Thomas Owens
1
Hmm, ich verstehe was du meinst. Ich denke, es hängt davon ab, ob der Kunde ein semi-permanentes Objekt in der Anwendung ist oder nur etwas, das in der Datenbank vorhanden ist und aktualisiert werden muss. Es ist nicht klar aus der Frage, so fair genug. +1
pdr
3
Nach meinen Erfahrungen werden diese Arten von Geschäftsregeln für Preisnachlässe durch launische Marketing- / Verkaufsabteilungen ständig geändert. Es besteht ein großes Bedürfnis, sie datengesteuert und benutzermodifizierbar zu machen, anstatt sie vollständig codegesteuert zu machen. Wie würde sich dies auf die Wahl des Modells auswirken?
Jfrankcarr
@jfrankcarr In meinen Augen würde es nicht. Ich würde die Werte für Elementgruppen ausfüllen, die zu einem Rabatt führen, die Prozentsätze reduzieren usw., die von einer Konfiguration ausgehen. Sozusagen dynamisch meine Zustandsmaschinenübergänge und Attribute meiner Dekorateure und Strategien aufbauen.
Thomas Owens
10

Nun, ich würde ein Rabattmodell als Paar "Vorbedingung" und "Rabatt" entwerfen, wobei "Vorbedingung" eine Klasse mit Methoden ist

  bool IsFulfilled(Customer c);

oder und

  bool IsFulfilled(Customer c, Order o);

und Rabatt hat eine Methode void ApplyTo(Customer c). Dies gibt Ihnen die Möglichkeit, jede Art von Vorbedingung mit jeder Art von Rabatt zu kombinieren (ich denke, dies ist eine Form von "Brückenmuster").

Wenn Sie eine feste Anzahl von Voraussetzungen haben, können Sie das Problem lösen, indem Sie bestimmte Untertypen (Strategiemuster) erstellen. Wenn Ihre Vorbedingungen jedoch sehr komplex sein dürfen, können Sie mit logischen Anweisungen wie AND, OR und NOT eine Art Regelinterpreter für die Bedingungen implementieren. Die Regeln können eine einfache Textzeichenfolge sein, die in einer einfachen "domänenspezifischen Sprache" geschrieben ist.

Dasselbe gilt für die Klasse "Rabatt". Sie können entweder einige Untertypen für verschiedene Arten von Rabatten oder einen allgemeinen Ansatz verwenden, bei dem die Rabattregeln in Textform angegeben sind und von einem Interpreter ausgewertet werden.

Doc Brown
quelle
Meiner Intuition nach könnte dies das sein, wonach er im Kontext der Frage
sucht
4
  • Möglicherweise benötigen Sie eine IDiscount-Schnittstelle, da alle unterschiedlichen Rabatte gleich sind und wir sie konzeptionell als generische Rabatte behandeln möchten.

  • Die Klasse "Bestellung dieses Kunden" benötigt wahrscheinlich eine Sammlung von Rabatten. Liste? Hash? Verknüpfte Liste? Interessiert mich noch nicht. Rabatte gelten für den Kauf, nicht für den Kunden!

  • Halten Sie den Aufbau der Discount-Instanz von Kunden, Einkaufswagen, Verlauf usw. getrennt. Wie @jfrankcarr hervorhob, ändert sich viel.

  • Wahrscheinlich eine andere Klasse für jeden Rabatt, da der Algorithmus und die Parameter für jeden Rabatt stark und unvorhersehbar variieren.

  • Ich sehe eine Menge Event-Handling als Rabattberechnung, die auf Warenkorbänderungen reagiert und umgekehrt.

Design Pattern-Anwendung

  • Ich sehe ein strategy pattern. IDiscount ist die Schnittstelle zur Implementierung verschiedener Rabattalgorithmen.
  • Ich sehe ein factory. Sicherlich keine ausgewachsene abstract factory pattern, sondern eine einzelne Klasse zu diesem Zeitpunkt in der Analyse. Es muss vernünftigerweise einen einzigen Ort geben, an dem ein ausreichender Zusammenhang besteht, um zu entscheiden, welche Rabatte gelten, und um sie dann zu erstellen. Eine Klasse. Wenn die Regeln für die spätere Anwendung von Rabatten aufgrund einer Pilzparty der Marketingabteilung explodieren, muss in dieser Basisfabrikklasse nach meinem Dafürhalten noch eine zusätzliche Rabattkonstruktionslogik zusammengeführt werden.

  • Ich kann sehen Chain of Responsibility. Dies schließt sich nicht gegenseitig aus factory. Anstatt eine Rabattsammlung zu wiederholen, ruft jeder Rabatt den nächsten an. In der Klasse "Kundenbestellung" sind in diesem Fall keine Rabatte enthalten.

  • Der Faktor "hmmmm ...." in der Kette der Verantwortung ist meiner Meinung nach, dass jeder Rabatt einen Verweis auf den nächsten hat. Die Implikation ist, dass Ordnung wichtig ist. Welches ist nicht der Fall. Das Konzept des AdR sieht außerdem vor, dass ein Objekt die Anfrage nicht bearbeiten kann, sodass sie "an die nächsthöhere Behörde" weitergeleitet wird. Unser Modell ist anders. Die einzige Bitte ist zu berechnen. Jeder Rabatt tut dies. Die Ausgabe oder der Effekt kann null sein, aber jeder Rabatt wird berechnet. Ich neige instinktiv zu einer höheren Treue in der realen Welt.

Annahmen

  • Die Rabatte basieren auf dem aktuellen Warenkorb und / oder dem Kaufverlauf
  • Es können null oder mehr Rabatte gelten. Es gibt keine sich gegenseitig ausschließenden Rabatte
  • Die ordnungsgemäße Berechnung hängt nicht von der Reihenfolge ab, in der die Rabatte angewendet werden.

Was ändert sich, was bleibt gleich?

  • Rabatte sind sehr unterschiedlich. Unterschiedliche Anzahl und Art der Parameter für jede Regel.

  • Argumente für qualifizierende Rabatte ändern sich, wenn sich der Warenkorb ändert.

  • Die Anzahl der verfügbaren Rabatte ändert sich

  • Die Rabatte, die dieser Kunde bei Änderungen seines Einkaufswagens erhält, können geändert werden.

  • Die Einkaufsgeschichte ändert sich im Rahmen dieses Einkaufs nicht

  • Die Gesamtkosten ändern sich dynamisch in Abhängigkeit von den Kaufpositionen und den gewährten Rabatten.

  • Nach der erstmaligen Anwendung kann sich die Ausgabe eines Rabatts ändern, z. B. wenn sich die Kaufmenge ändert.

Radarbob
quelle
Tolle und vollständige Antwort ... ABER ich habe nur Bedenken und bin der Meinung, dass der Abschnitt Annahmen nicht vorhanden sein sollte, zumindest nicht, um die Antwort zu leiten. Die ganze Idee ist, dass das Muster dabei hilft, die "Annahmen" genau zu verstehen und zu vergessen, die generische Regel muss nicht wissen, wie die Berechnungen durchgeführt werden und was eine detaillierte Implementierung aus dem Kontext verwendet (Kunde, Warenkorbartikel) , Tageszeit, Jahreszeit usw.). Wirklich voll helfen
le0diaz
Die ersten Aufzählungszeichen und der Abschnitt "Annahmen" sind nur meine Argumente für "Zeigen Sie Ihre Arbeit" zum Problem selbst, das sich natürlich auch auf das Design des Rabattmodells auswirkt. Zum Beispiel haben mich meine Annahmen über die Ausführungsreihenfolge von Rabatten und die gegenseitige Abhängigkeit dazu veranlasst, die Verantwortungskette zu deaktivieren. Beachten Sie, dass ich über die Absicht des Musters nachdenke und nicht über die Komplexität wie bei @docbrown. Ich bin ein überschwänglicher Befürworter, wenn es darum geht, Absichten im Design zu reflektieren.
Radarbob
1

Ein Rabattmodell kann logischerweise alles sein , sodass Sie nicht davon ausgehen können, dass Sie alle Fälle im Voraus programmieren können. Auch kann niemand, der Ihre Frage beantwortet, völlig sicher sein, was Sie tatsächlich benötigen. Unter der Annahme, dass Sie die üblichen Rabatte erhalten, die in der realen Welt zu finden sind ...

Eine große Frage ist, ob die Rabatte programmiert werden oder ob Sie möchten, dass Benutzer sie eingeben. Wie oben erwähnt, kann man sie nicht niemals programmieren lassen, aber normalerweise besteht das Ziel darin, die Dateneingabe so zu gestalten, wie es für häufige Fälle der Fall ist, anstatt sie alle zu programmieren. Dies gilt zum Teil auch dann, wenn alle Rabatte von Programmierern erstellt werden.

Martin Fowler erwähnt "Einzelinstanzmethode" in "Analysemuster: wiederverwendbare Objektmodelle" als Teil der Implementierung von "Buchungsregeln" für Buchhaltungssysteme, aber die Regeln scheinen Ihren ziemlich ähnlich zu sein. Ich würde mehr Details geben, aber es ist ein urheberrechtlich geschütztes Werk und

Für eine Benutzeroberfläche müssen Sie entweder relativ einfache Anwendungsfälle entwickeln oder einen Interpreter und einen Abfrage-Builder erstellen. Möglicherweise beides, eines für einfache Fälle und eines für Fortgeschrittene. Wenn Sie einen Interpreter schreiben, ist dies wahrscheinlich ein recht guter Fall für die Verwendung des Interpreter-Musters, da der Code im Vergleich zu einem Parser-Generator relativ einfach ist und die langsamere Analysezeit wahrscheinlich keine Rolle spielt. (Wenn Sie Parser-Generatoren verwenden möchten, lassen Sie sich von mir nicht aufhalten.)

Versuchen Sie jedoch nicht, alles mit einem Dolmetscher zu erledigen - irgendwann programmieren Sie nur noch in Ihrer eigenen, miesen Sprache, also können Sie auch eine echte verwenden. Wenn Ihre interpretierte Sprache Funktionen unterstützt (wahrscheinlich sollte sie das Aufrufen unterstützen - es ist zweifelhaft, sie zu definieren), können diese in einer realen Sprache codiert werden. Gehen Sie nicht weiter, als Sie müssen.

Egal, was Sie tun, irgendwann möchte jemand, dass der Rabatt davon abhängt, ob er innerhalb von 30 Werktagen nach einer Werbeaktion gekauft hat - wobei Werktage nur dann zählen, wenn in der durch die Postleitzahl des Geschäfts oder die des Kunden definierten Region kein Feiertag war Postleitzahl. Versuchen Sie also nicht, das perfekte System im Voraus zu entwerfen - nehmen Sie an, dass Sie manchmal Code für neue Arten von Rabatten schreiben und entsprechend designen müssen.

bA
quelle
0

Gibt es irgendeinen Grund zu fragen, ob es ein nützliches Muster dafür gibt? Welche Art von Muster ist erforderlich - strukturell oder verhaltensmäßig?

Wenn ich dafür eine Software schreibe, ist im Idealfall nur ein Algorithmus erforderlich . Eine einfache Funktion, die den Gesamtrabatt wie folgt berechnet:

cart.calculateDiscount(productVector);

Mehr brauchen Sie nicht!

Zur Verdeutlichung: Ich verstehe, dass es viele Regeln geben wird - die grundlegendste Form einer solchen Darstellung sollte die Form einer Regelbasis sein (Satz von Datenattributen und daraus resultierender Abschlag), und die Funktion wie oben würde sie iterieren, um sie zu berechnen. Wenn Regeln hinzugefügt oder entfernt werden, sollten Sie den Code nicht ändern - ändern Sie einfach die Regelbasis.

Muster werden nur benötigt, wenn wir unterschiedliche Objekte benötigen, um auf APIs voneinander zuzugreifen oder um zu kommunizieren, um eine Aufgabe einzurichten.

PS: Denken Sie darüber nach - wenn die Firewall Pakete verarbeitet und diese weiterleitet oder ablehnt (oder ändert) - welches Entwurfsmuster wird verwendet? Die Antwort ist KEINE der oben beschriebenen!

Dipan Mehta
quelle
Natürlich braucht man mehr als das !!!. Die Idee ist, dass der Algorithmus nicht eng mit der Code-Implementierung gekoppelt ist. Wenn Sie überprüfen, ob die Szenarien sehr wahrscheinlich sind, dass weitere Szenarien auftauchen, haben Sie es auch schon erwähnt, und er weiß nicht, wovon eine andere Regel abhängt. Es ist naiv zu glauben, dass eine Regel nur von der Produktliste abhängt, in Wirklichkeit jedoch von Kunde, Zeit, Jahreszeit und so weiter. Ich weiß nicht, welche Firewall-Implementierung Sie überprüft haben, aber die, die ich überprüft habe, HABEN viele Struktur- / Entwurfsmuster
le0diaz