Czy istnieje wzór, który miałby zastosowanie do modeli rabatowych?


35

Czy są znane wzorce projektowe do wdrażania modeli zniżek?

Mówiąc o modelach rabatowych mam na myśli:

  1. Jeśli klient kupi Produkt X, Produkt Y i Produkt Z, otrzyma rabat w wysokości 10% lub 100 USD.

  2. Jeśli klient kupi 100 jednostek produktu X, otrzyma rabat w wysokości 15% lub 500 USD

  3. Jeśli klient przyniósł w ubiegłym roku więcej niż 100 000 USD, otrzymuje stałą 20% zniżkę

  4. Jeśli klient kupił 2 jednostki produktu X, otrzymuje 1 jednostkę produktu X (lub produktu Y) za darmo.

  5. ...

Czy istnieje ogólny wzorzec, który można zastosować do obsługi wszystkich powyższych scenariuszy? Mam na myśli kilka modeli, ale nie mogę znaleźć jednego ogólnego.


IIRC wszystkie samouczki, które widziałem z przykładami obejmującymi rabaty (jest ich wiele) sugerują wzorzec strategii
gnat

2
@Kanini Czy to problem w świecie rzeczywistym? Czy w takim przypadku ten system działa w czasie rzeczywistym czy w czasie odroczonym? Czy te reguły są przedstawiane jako wartości silnika reguł lub wartości bazy danych? Czy wyszukiwanie rabatów jest hierarchiczne według priorytetu? Wzór strategii działałby w większości przypadków, ale aby reguły zadziałały, należy uwzględnić zasady
Ubermensch,

3
A jeśli ktoś kupi 2 jednostki produktu X, jeden produkt Y i jeden produkt Z, otrzyma zarówno 10%, jak i dodatkowy produkt X?
Ubermensch

@gnat poproszę kilka linków do niektórych z tych samouczków.
user16764

Odpowiedzi:


18

Jeśli problem polega na tym, że musisz zastosować wiele rabatów, w danych okolicznościach możesz rozważyć wzorzec łańcucha odpowiedzialności .

W skrócie, przekazujesz informacje, które chcesz przetworzyć, do pierwszego procesora i od tego momentu decydujesz, czy przekazać je kolejnym procesorom przed zwróceniem wyniku.

W ten sposób możesz zmienić strukturę i sekwencję procesorów bez zmiany kodu wywołującego.


Łańcuch odpowiedzialności jest kolejnym dobrym. Może nawet być połączone ze strategią. W przypadku, gdy można zastosować tylko jedną zniżkę, każda strategia jest połączona z inną. Każdy łańcuch oblicza rabat (jeśli klient się kwalifikuje), porównuje go z poprzednim rabatem i przekazuje dane klienta, zamówienia i rabatu do następnego łańcucha. +1.
Thomas Owens

1
Po prostu moja opinia, ale bardziej prawdopodobne jest, że „Łańcuch odpowiedzialności” w tej sprawie jest nadprojektowany. Powinna to zrobić prosta lista modeli rabatów (w razie potrzeby z numerem zamówienia). Sama lista jest niezależna od klienta i jego zamówień, ponieważ wszyscy klienci powinni być traktowani jednakowo. „Łańcuch odpowiedzialności” jest bardziej odpowiedni, gdy lista modeli rabatów zmienia się bardzo często w czasie wykonywania w bardzo dynamiczny sposób.
Doc Brown

11

Wzory strategii, dekoratora i stanu wyróżniają się jako potencjalne punkty wyjścia. Stan może być szczególnie użyteczny dla 2 lub 3, ponieważ 2 zależy od stanu zamówienia, a 3 zależy od stanu klienta w określonym przedziale czasu. Strategia i Dekorator wyróżniają się na tle innych, ponieważ można użyć strategii do wdrożenia wielu algorytmów obliczania ceny zamówienia i Dekoratora, aby dodać nowe rabaty do zamówienia.

Pamiętaj jednak, że wzorce projektowe to tylko modele. Może nie obowiązywać pojedynczy wzorzec, ale raczej system wzorców. Zastanów się również nad modyfikacjami opisanych modeli, aby lepiej pasowały do ​​twojego rozwiązania. Lepiej mieć dobry projekt niż wymuszać wzór, w którym niekoniecznie pomaga to tylko dlatego, że można powiedzieć, że masz wzór.


To nie jest tak naprawdę do tego, co ma robić ten stan, prawda?
pdr

@pdr Nie rozumiem, dlaczego nie. Ale to zależy od twojej implementacji. Jeśli obiekt klienta śledzi rabaty zależne od klienta, może wystąpić operacja zwrotu rabatów, do których klient jest uprawniony. Gdy klient kupuje rzeczy, zmieniają się atrybuty, a implementacja tej metody zmienia się za pomocą wzorca stanu.
Thomas Owens

1
Hmm, rozumiem co masz na myśli. Myślę, że to zależy, czy klient jest półtrwałym obiektem w aplikacji, czy tylko czymś, co żyje w bazie danych i wymaga aktualizacji. Pytanie nie jest jasne, więc jest to dość sprawiedliwe. +1
pdr

3
Z moich doświadczeń wynika, że ​​tego rodzaju reguły biznesowe dotyczące rabatów są cały czas modyfikowane przez zmienne działy marketingu / sprzedaży. Istnieje duża potrzeba dostosowania ich do danych i modyfikacji przez użytkownika, a nie do kodu. Jak wpłynęłoby to na wybór modelu?
jfrankcarr

@jfrankcarr Moim zdaniem nie. Wypełniam wartości dla zestawów przedmiotów, które prowadzą do rabatu, wartości procentowe i tak dalej z jakiejś konfiguracji. W pewnym sensie dynamicznie buduję moje przejścia maszyny stanów i atrybuty moich dekoratorów i strategii.
Thomas Owens

10

Cóż, zaprojektowałbym model rabatu jako parę „Warunek wstępny” i „Rabat”, gdzie „Warunek wstępny” to klasa z metodami

  bool IsFulfilled(Customer c);

albo i

  bool IsFulfilled(Customer c, Order o);

i Rabat ma metodę void ApplyTo(Customer c). Daje to możliwość połączenia dowolnego rodzaju warunku wstępnego z dowolnym rodzajem rabatu (myślę, że jest to forma „wzorca pomostowego”).

Jeśli masz określoną liczbę warunków wstępnych, możesz rozwiązać problem, budując określone podtypy (wzorzec strategii). Jednak gdy twoje warunki wstępne mogą być bardzo złożone, z logicznymi stwierdzeniami, takimi jak AND, OR i NOT, możesz lepiej zaimplementować jakiś interpretator reguł dla warunków. Reguły mogą być zwykłym ciągiem tekstowym napisanym prostym „językiem specyficznym dla domeny”.

To samo dotyczy klasy „Rabat”, możesz albo mieć pewne podtypy dla różnych rodzajów rabatów, albo ogólne podejście, w którym reguły rabatów są podawane w formie tekstowej, oceniane przez jakiegoś tłumacza.


Moja intuicja sugeruje, że może tego właśnie szukał w kontekście pytania
Ubermensch

4
  • Prawdopodobnie potrzebujesz interfejsu IDiscount, ponieważ wszystkie różne rabaty są takie same i będziemy chcieli traktować je koncepcyjnie jako ogólne rabaty.

  • Klasa „zamówienie tego klienta” prawdopodobnie wymaga kolekcji rabatów. Lista? Haszysz? Połączona lista? Jeszcze mnie to nie obchodzi. Rabaty dotyczą zakupu, a nie klienta!

  • Zachowaj oddzielne budowanie instancji Rabatu od klienta, koszyka, historii itp. To się bardzo zmieni - jak zauważył @jfrankcarr.

  • Prawdopodobnie inna klasa dla każdego rabatu, ponieważ algorytm i parametry dla każdego rabatu różnią się gwałtownie i nieprzewidywalnie.

  • Widzę dużo obsługi zdarzeń, ponieważ obliczanie rabatów reaguje na zmiany w koszyku i na odwrót.

Zastosowanie wzorca projektowego

  • Widzę strategy pattern. IDiscount to interfejs do implementacji różnych algorytmów rabatowych.
  • Widzę factory. Na pewno nie w pełni rozwinięta abstract factory pattern, ale jedna klasa w tym momencie analizy. Rozsądnie musi istnieć jedno miejsce, w którym istnieje wystarczający kontekst, aby zdecydować, które rabaty mają zastosowanie, a następnie je utworzyć. Jedna klasa. Jeśli zasady stosowania rabatów później eksplodują z powodu imprezy z grzybami z Działu Marketingu, jakakolwiek dodatkowa logika Konstrukcji Zniżek musi nadal być spójna w tej podstawowej klasie fabryki.

  • Widzę Chain of Responsibility. Nie wyklucza to wzajemnie tego factorypomysłu. Zamiast iteracji kolekcji rabatów, każda zniżka dzwoni do następnego faceta. Klasa „zamówienie klienta” w tym przypadku nie zawiera kolekcji rabatów.

  • Myślę, że czynnikiem „hmmmm ....” w łańcuchu odpowiedzialności jest to, że każda zniżka ma odniesienie do następnego. Oznacza to, że porządek ma znaczenie. Co nie jest prawdą. Ponadto koncepcja, którą ucieleśnia KR, polega na tym, że jeden obiekt nie może obsłużyć żądania, więc jest ono przekazywane „do następnego wyższego organu”. Nasz model jest inny. Jedynym żądaniem jest obliczenie. Każda zniżka to robi. Wynik lub efekt może być zerowy, ale każdy rabat jest obliczany. Instynktownie skłaniam się ku wyższej wierności w świecie rzeczywistym.

Założenia

  • Zniżki oparte są na bieżącym koszyku i / lub historii zakupów
  • Mogą obowiązywać zero lub więcej zniżek. Nie ma wzajemnie wykluczających się rabatów
  • Prawidłowe obliczenie nie zależy od kolejności stosowania rabatów.

Jakie zmiany, co pozostaje bez zmian?

  • Zniżki są bardzo różne. Inna liczba i rodzaj parametrów, z których składa się każda reguła.

  • Argumenty za kwalifikującymi się rabatami zmieniają się wraz ze zmianą koszyka.

  • Zmienia się liczba dostępnych rabatów

  • Zniżki, które klient kwalifikuje się do zmian wraz ze zmianą koszyka.

  • Historia zakupów nie zmienia się w kontekście tego zakupu

  • Całkowity koszt zmienia się dynamicznie w zależności od zastosowanych linii zakupowych i rabatów.

  • Po początkowym zastosowaniu wynik rabatu może ulec zmianie, na przykład zmienia się ilość zakupu.


Świetna i kompletna odpowiedź ... ALE mam tylko obawy i jest to, że sekcja z założeniami nie powinna tam być, a przynajmniej nie prowadzić odpowiedzi. Cała idea polega na tym, że wzorzec pomaga zapewnić komfort i zapomnieć o „założeniach”, ogólna reguła nie musi wiedzieć, w jaki sposób wykonywane są obliczenia i czego używa każda szczegółowa implementacja z kontekstu (klient, elementy koszyka , Czas, pora roku itd.). Naprawdę pomóżcie jednak
le0diaz

Wstępne pociski i sekcja „Założenia” to tylko moje uzasadnienie „pokaż swoją pracę” na temat samego problemu, który oczywiście ma negatywny wpływ na projekt modelu rabatowego. Na przykład moje założenia dotyczące zlecenia realizacji rabatu i współzależności doprowadziły mnie do rezygnacji z podkreślenia łańcucha odpowiedzialności. Zauważ, że myślę o intencji wzoru, a nie o złożoności, jak robi to @docbrown. Jestem entuzjastycznym zwolennikiem odzwierciedlania zamiarów w projektowaniu.
radarbob,

1

Logicznie rzecz biorąc, model rabatu może być dowolny , więc nie można zakładać, że można zaprogramować wszystkie sprawy z wyprzedzeniem. Nikt, kto odpowie na twoje pytanie, nie może być całkowicie pewien, czego naprawdę potrzebujesz. Zakładając jednak, że otrzymujesz zwykłe rabaty w prawdziwym świecie ...

Ważnym pytaniem jest, czy rabaty zostaną zaprogramowane, czy też chcesz, aby użytkownicy je wprowadzali. Jak wspomniano powyżej, nigdy nie można ich zaprogramować, ale zazwyczaj celem jest próba wprowadzenia większej ilości danych, tak jak w przypadku typowych przypadków, zamiast programowania ich wszystkich. Dotyczy to do pewnego stopnia, nawet jeśli programiści są wykorzystywani do tworzenia wszystkich rabatów.

Martin Fowler wspomina o „metodzie wystąpienia indywidualnego” w „Wzorcach analizy: modele obiektów wielokrotnego użytku” jako część sposobu wdrażania „Reguł księgowania” dla systemów księgowych, ale reguły wydają się dość podobne do twoich. Podałbym więcej szczegółów, ale jest to dzieło chronione prawem autorskim i

W przypadku interfejsu użytkownika musisz albo wymyślić dość proste przypadki użycia, albo zbudować interpreter i narzędzie do tworzenia zapytań. Możliwe, że jedno i drugie dla prostych przypadków i jedno bardziej zaawansowane. Jeśli piszesz interpreter, jest to prawdopodobnie dość dobry przypadek użycia wzorca interpretera, ponieważ kod jest stosunkowo prosty w porównaniu do generatora analizatora składni, a wolniejszy czas analizy prawdopodobnie nie będzie miał znaczenia. (Jeśli lubisz korzystać z generatorów analizatorów składni, nie pozwól mi Cię zatrzymać).

Nie próbuj jednak robić wszystkiego z tłumaczem - w pewnym momencie programujesz po prostu w swoim kiepskim języku, więc równie dobrze możesz użyć prawdziwego. Jeśli Twój interpretowany język obsługuje funkcje (prawdopodobnie powinien obsługiwać ich wywoływanie - definiowanie ich jest wątpliwe), można je zakodować w prawdziwym języku. Nie idź dalej, niż musisz.

Bez względu na to, co robisz, ostatecznie ktoś będzie chciał, aby rabat był oparty na tym, czy kupił w ciągu 30 dni roboczych od promocji - gdzie dni robocze liczą się tylko wtedy, gdy w regionie nie ma wakacji określonych przez kod pocztowy sklepu lub kod klienta kod pocztowy. Nie próbuj więc z góry projektować idealnego systemu - załóż, że czasami będziesz musiał napisać kod dla nowych rodzajów rabatów i odpowiednio zaprojektować.


0

Czy ma sens pytanie, czy istnieje przydatny wzorzec tego? Jaki typ wzoru jest wymagany - strukturalny czy behawioralny?

Idealnie, gdybym miał do tego napisać oprogramowanie, wystarczy algorytm . Prosta funkcja, która oblicza całkowity rabat w następujący sposób:

cart.calculateDiscount(productVector);

Nie potrzebujesz niczego więcej niż to!

Wyjaśniając: Rozumiem, że będzie wiele reguł - najbardziej podstawowa z takich reprezentacji powinna być w formie zbioru reguł (zestaw atrybutów danych i wynikająca z tego zniżka), a funkcja taka jak powyżej iterowałaby ją do obliczenia. Jeśli reguły zostaną dodane lub usunięte, nie powinieneś zmieniać kodu - po prostu zmień podstawę reguły.

Wzorzec będzie wymagany tylko wtedy, gdy potrzebujemy różnych obiektów, aby uzyskać dostęp do interfejsów API lub komunikować się w celu wykonania zadania.

PS: Pomyśl o tym - kiedy zapora przetwarza pakiety i przepuszcza lub odrzuca pakiety (lub je modyfikuje) - jakiego wzorca używa? Odpowiedź NIE jest z wyżej opisanych!


Oczywiście, że potrzebujesz czegoś więcej! Chodzi o to, że algorytm nie jest ściśle powiązany z implementacją kodu. Jeśli sprawdzisz scenariusze, jest wysoce prawdopodobne, że pojawi się więcej scenariuszy, to również jakoś o tym wspomniałeś, tak naprawdę nie jest on zależny od żadnej innej „Reguły”. Naiwnie jest myśleć, że reguła będzie zależeć tylko od listy produktów, ale w rzeczywistości zależy od klienta, czasu, sezonu i tak dalej. Nie wiem, którą implementację zapory sprawdziłeś, ale te, które sprawdzam,
Mają
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.