2010-02-12 14 views
8

में छूट रणनीति मैं एक ऐसे सिस्टम को लागू करने की कोशिश कर रहा हूं जो मेरे कार्ट/पूर्ण ऑर्डर पर लागू कई छूट को संभाल सके। मैंने छूट के भीतर छूट की प्रसंस्करण को समाहित करने के लिए एक रणनीति प्रकार पैटर्न लागू किया है।शॉपिंग कार्ट और ऑर्डर

मैं निम्नलिखित के साथ आया हूं: कंक्रीट छूट बनाने वाले उप-वर्गों के साथ एक अमूर्त छूट आधार वर्ग। ये तब ऑर्डर/कार्ट ऑब्जेक्ट पर लागू होते हैं और कार्ट/ऑर्डर में जोड़े जाने पर ऑर्डर/कार्ट की सामग्री को संसाधित करेंगे।

संलग्न कोड पर कुछ टिप्पणियां पसंद करेंगे। विभिन्न संरक्षित रचनाकारों और सदस्यों को निचले स्तर के लिए आवश्यक "वर्चुअल" चिह्नित किया गया है।

Chev

using System; 
using System.Collections.Generic; 
using System.Linq; 
using NUnit.Framework; 

namespace CodeCollective.RaceFace.DiscountEngine 
{ 
[TestFixture] 
public class TestAll 
{ 
    #region Tests 

    [Test] 
    public void Can_Add_Items_To_Cart() 
    { 
     Cart cart = LoadCart(); 

     // display the cart contents 
     foreach (LineItem lineItem in cart.LineItems) 
     { 
      Console.WriteLine("Product: {0}\t Price: {1:c}\t Quantity: {2} \t Subtotal: {4:c} \t Discount: {3:c} \t| Discounts Applied: {5}", lineItem.Product.Name, lineItem.Product.Price, lineItem.Quantity, lineItem.DiscountAmount, lineItem.Subtotal, lineItem.Discounts.Count); 
     } 
    } 

    [Test] 
    public void Can_Add_Items_To_An_Order() 
    { 
     // create the cart 
     Order order = new Order(new Member("Chev")); 

     // add items to the cart 
     GenericProduct hat = new GenericProduct("Cap", 110m); 
     order.AddLineItem(hat, 5); 

     EventItem race = new EventItem("Ticket", 90m); 
     order.AddLineItem(race, 1); 

     // add discounts 
     Discount percentageOff = new PercentageOffDiscount("10% off all items", 0.10m); 
     percentageOff.CanBeUsedInJuntionWithOtherDiscounts = false; 
     order.AddDiscount(percentageOff); 

     Discount spendXgetY = new SpendMoreThanXGetYDiscount("Spend more than R100 get 10% off", 100m, 0.1m); 
     spendXgetY.SupercedesOtherDiscounts = true; 
     order.AddDiscount(spendXgetY); 

     Discount buyXGetY = new BuyXGetYFree("Buy 4 hats get 2 hat free", new List<Product> { hat }, 4, 2); 
     buyXGetY.CanBeUsedInJuntionWithOtherDiscounts = false; 
     buyXGetY.SupercedesOtherDiscounts = true; 
     order.AddDiscount(buyXGetY); 

     // display the cart contents 
     foreach (LineItem lineItem in order.LineItems) 
     { 
      Console.WriteLine("Product: {0}\t Price: {1:c}\t Quantity: {2} \t Subtotal: {4:c} \t Discount: {3:c} \t| Discounts Applied: {5}", lineItem.Product.Name, lineItem.Product.Price, lineItem.Quantity, lineItem.DiscountAmount, lineItem.Subtotal, lineItem.Discounts.Count); 
     } 
    } 

    [Test] 
    public void Can_Process_A_Cart_Into_An_Order() 
    { 
     Cart cart = LoadCart(); 

     Order order = ProcessCartToOrder(cart); 

     // display the cart contents 
     foreach (LineItem lineItem in order.LineItems) 
     { 
      Console.WriteLine("Product: {0}\t Price: {1:c}\t Quantity: {2} \t Subtotal: {4:c} \t Discount: {3:c} \t| Discounts Applied: {5}", lineItem.Product.Name, lineItem.Product.Price, lineItem.Quantity, lineItem.DiscountAmount, lineItem.Subtotal, lineItem.Discounts.Count); 
     } 
    } 

    private static Cart LoadCart() 
    { 
     // create the cart 
     Cart cart = new Cart(new Member("Chev")); 

     // add items to the cart 
     GenericProduct hat = new GenericProduct("Cap", 110m); 
     cart.AddLineItem(hat, 5); 

     EventItem race = new EventItem("Ticket", 90m); 
     cart.AddLineItem(race, 1); 

     // add discounts 
     Discount percentageOff = new PercentageOffDiscount("10% off all items", 0.10m); 
     percentageOff.CanBeUsedInJuntionWithOtherDiscounts = false; 
     cart.AddDiscount(percentageOff); 

     Discount spendXgetY = new SpendMoreThanXGetYDiscount("Spend more than R100 get 10% off", 100m, 0.1m); 
     spendXgetY.SupercedesOtherDiscounts = true; 
     cart.AddDiscount(spendXgetY); 

     Discount buyXGetY = new BuyXGetYFree("Buy 4 hats get 2 hat free", new List<Product> { hat }, 4, 2); 
     buyXGetY.CanBeUsedInJuntionWithOtherDiscounts = false; 
     buyXGetY.SupercedesOtherDiscounts = true; 
     cart.AddDiscount(buyXGetY); 

     return cart; 
    } 

    private static Order ProcessCartToOrder(Cart cart) 
    { 
     Order order = new Order(cart.Member); 
     foreach(LineItem lineItem in cart.LineItems) 
     { 
      order.AddLineItem(lineItem.Product, lineItem.Quantity); 
      foreach(Discount discount in lineItem.Discounts) 
      { 
       order.AddDiscount(discount);  
      } 
     } 
     return order; 
    } 

    #endregion 
} 

#region Discounts 

[Serializable] 
public abstract class Discount : EntityBase 
{ 
    protected internal Discount() 
    { 
    } 

    public Discount(string name) 
    { 
     Name = name; 
    } 

    public virtual bool CanBeUsedInJuntionWithOtherDiscounts { get; set; } 
    public virtual bool SupercedesOtherDiscounts { get; set; } 
    public abstract OrderBase ApplyDiscount(); 
    public virtual OrderBase OrderBase { get; set; } 
    public virtual string Name { get; private set; } 
} 

[Serializable] 
public class PercentageOffDiscount : Discount 
{ 
    protected internal PercentageOffDiscount() 
    { 
    } 

    public PercentageOffDiscount(string name, decimal discountPercentage) 
     : base(name) 
    { 
     DiscountPercentage = discountPercentage; 
    } 

    public override OrderBase ApplyDiscount() 
    { 
     // custom processing 
     foreach (LineItem lineItem in OrderBase.LineItems) 
     { 
      lineItem.DiscountAmount = lineItem.Product.Price * DiscountPercentage; 
      lineItem.AddDiscount(this); 
     } 
     return OrderBase; 
    } 

    public virtual decimal DiscountPercentage { get; set; } 
} 

[Serializable] 
public class BuyXGetYFree : Discount 
{ 
    protected internal BuyXGetYFree() 
    { 
    } 

    public BuyXGetYFree(string name, IList<Product> applicableProducts, int x, int y) 
     : base(name) 
    { 
     ApplicableProducts = applicableProducts; 
     X = x; 
     Y = y; 
    } 

    public override OrderBase ApplyDiscount() 
    { 
     // custom processing 
     foreach (LineItem lineItem in OrderBase.LineItems) 
     { 
      if(ApplicableProducts.Contains(lineItem.Product) && lineItem.Quantity > X) 
      { 
       lineItem.DiscountAmount += ((lineItem.Quantity/X) * Y) * lineItem.Product.Price; 
       lineItem.AddDiscount(this);  
      } 
     } 
     return OrderBase; 
    } 

    public virtual IList<Product> ApplicableProducts { get; set; } 
    public virtual int X { get; set; } 
    public virtual int Y { get; set; } 
} 

[Serializable] 
public class SpendMoreThanXGetYDiscount : Discount 
{ 
    protected internal SpendMoreThanXGetYDiscount() 
    { 
    } 

    public SpendMoreThanXGetYDiscount(string name, decimal threshold, decimal discountPercentage) 
     : base(name) 
    { 
     Threshold = threshold; 
     DiscountPercentage = discountPercentage; 
    } 

    public override OrderBase ApplyDiscount() 
    { 
     // if the total for the cart/order is more than x apply discount 
     if(OrderBase.GrossTotal > Threshold) 
     { 
      // custom processing 
      foreach (LineItem lineItem in OrderBase.LineItems) 
      { 
       lineItem.DiscountAmount += lineItem.Product.Price * DiscountPercentage; 
       lineItem.AddDiscount(this); 
      } 
     } 
     return OrderBase; 
    } 

    public virtual decimal Threshold { get; set; } 
    public virtual decimal DiscountPercentage { get; set; } 
} 

#endregion 

#region Order 

[Serializable] 
public abstract class OrderBase : EntityBase 
{ 
    private IList<LineItem> _LineItems = new List<LineItem>(); 
    private IList<Discount> _Discounts = new List<Discount>(); 

    protected internal OrderBase() { } 

    protected OrderBase(Member member) 
    { 
     Member = member; 
     DateCreated = DateTime.Now; 
    } 

    public virtual Member Member { get; set; } 

    public LineItem AddLineItem(Product product, int quantity) 
    { 
     LineItem lineItem = new LineItem(this, product, quantity); 
     _LineItems.Add(lineItem); 
     return lineItem; 
    } 

    public void AddDiscount(Discount discount) 
    { 
     discount.OrderBase = this; 
     discount.ApplyDiscount(); 
     _Discounts.Add(discount); 
    } 

    public virtual decimal GrossTotal 
    { 
     get 
     { 
      return LineItems 
       .Sum(x => x.Product.Price * x.Quantity); 
     } 
    } 
    public virtual DateTime DateCreated { get; private set; } 
    public IList<LineItem> LineItems 
    { 
     get 
     { 
      return _LineItems; 
     } 
    } 
} 

[Serializable] 
public class Order : OrderBase 
{ 
    protected internal Order() { } 

    public Order(Member member) 
     : base(member) 
    { 
    } 
} 

#endregion 

#region LineItems 

[Serializable] 
public class LineItem : EntityBase 
{ 
    private IList<Discount> _Discounts = new List<Discount>(); 

    protected internal LineItem() { } 

    public LineItem(OrderBase order, Product product, int quantity) 
    { 
     Order = order; 
     Product = product; 
     Quantity = quantity; 
    } 

    public virtual void AddDiscount(Discount discount) 
    { 
     _Discounts.Add(discount); 
    } 

    public virtual OrderBase Order { get; private set; } 
    public virtual Product Product { get; private set; } 
    public virtual int Quantity { get; private set; } 
    public virtual decimal DiscountAmount { get; set; } 
    public virtual decimal Subtotal 
    { 
     get { return (Product.Price*Quantity) - DiscountAmount; } 
    } 
    public virtual IList<Discount> Discounts 
    { 
     get { return _Discounts.ToList().AsReadOnly(); } 
    } 
} 
#endregion 

#region Member 

[Serializable] 
public class Member : EntityBase 
{ 
    protected internal Member() { } 

    public Member(string name) 
    { 
     Name = name; 
    } 

    public virtual string Name { get; set; } 
} 

#endregion 

#region Cart 

[Serializable] 
public class Cart : OrderBase 
{ 
    protected internal Cart() 
    { 
    } 

    public Cart(Member member) 
     : base(member) 
    { 
    } 
} 

#endregion 

#region Products 

[Serializable] 
public abstract class Product : EntityBase 
{ 
    protected internal Product() 
    { 
    } 

    public Product(string name, decimal price) 
    { 
     Name = name; 
     Price = price; 
    } 

    public virtual string Name { get; set; } 
    public virtual decimal Price { get; set; } 
} 

// generic product used in most situations for simple products 
[Serializable] 
public class GenericProduct : Product 
{ 
    protected internal GenericProduct() 
    { 
    } 

    public GenericProduct(String name, Decimal price) : base(name, price) 
    { 
    } 
} 

// custom product with additional properties and methods 
[Serializable] 
public class EventItem : Product 
{ 
    protected internal EventItem() 
    { 
    } 

    public EventItem(string name, decimal price) : base(name, price) 
    { 
    } 
} 

#endregion 

#region EntityBase 

[Serializable] 
public abstract class EntityBase 
{ 
    private readonly Guid _id; 

    protected EntityBase() : this(GenerateGuidComb()) 
    { 
    } 

    protected EntityBase(Guid id) 
    { 
     _id = id; 
    } 

    public virtual Guid Id 
    { 
     get { return _id; } 
    } 

    private static Guid GenerateGuidComb() 
    { 
     var destinationArray = Guid.NewGuid().ToByteArray(); 
     var time = new DateTime(0x76c, 1, 1); 
     var now = DateTime.Now; 
     var span = new TimeSpan(now.Ticks - time.Ticks); 
     var timeOfDay = now.TimeOfDay; 
     var bytes = BitConverter.GetBytes(span.Days); 
     var array = BitConverter.GetBytes((long)(timeOfDay.TotalMilliseconds/3.333333)); 
     Array.Reverse(bytes); 
     Array.Reverse(array); 
     Array.Copy(bytes, bytes.Length - 2, destinationArray, destinationArray.Length - 6, 2); 
     Array.Copy(array, array.Length - 4, destinationArray, destinationArray.Length - 4, 4); 
     return new Guid(destinationArray); 
    } 

    public virtual int Version { get; protected set; } 

    #region Equality Tests 

    public override bool Equals(object entity) 
    { 
     return entity != null 
      && entity is EntityBase 
      && this == (EntityBase)entity; 
    } 

    public static bool operator ==(EntityBase base1, 
     EntityBase base2) 
    { 
     // check for both null (cast to object or recursive loop) 
     if ((object)base1 == null && (object)base2 == null) 
     { 
      return true; 
     } 

     // check for either of them == to null 
     if ((object)base1 == null || (object)base2 == null) 
     { 
      return false; 
     } 

     if (base1.Id != base2.Id) 
     { 
      return false; 
     } 

     return true; 
    } 

    public static bool operator !=(EntityBase base1, EntityBase base2) 
    { 
     return (!(base1 == base2)); 
    } 

    public override int GetHashCode() 
    { 
     { 
      return Id.GetHashCode(); 
     } 
    } 

    #endregion 

#endregion 
} 

}

+0

शायद आप उस कोड के कुछ विशिष्ट टुकड़े फ़िल्टर कर सकते हैं जिन्हें आप इनपुट चाहते हैं? –

+0

उत्तर के लिए धन्यवाद। ईमानदार होने के लिए कोड के साथ एक विशिष्ट समस्या के बजाय यह एक वास्तुकला प्रश्न है। – Chev

+0

रणनीति पैटर्न मेरे लिए सही प्रतीत नहीं होता है, खासकर यदि आप कार्ट में कई छूट लागू कर सकते हैं। मेरे लिए आप कुछ प्रकार के नियम इंजन को लागू करने की तलाश में हैं। – David

उत्तर

2

मेरे लिए Decorator pattern यहाँ और अधिक लागू लगता है। यह आपके पास समान डिस्काउंट क्लास पदानुक्रम के साथ शुरू होता है, लेकिन छूट OrderBase को भी कार्यान्वित करेगी। फिर वे इसे संलग्न करने के बजाय आदेश को सजाने के लिए। पूछे जाने पर, सजावटी को ऑर्डर उदाहरण से ऑर्डर डेटा मिलता है जो इसे सजाने (जो एक सादा वेनिला ऑर्डर या अन्य सजावट हो सकता है), और उचित छूट पर लागू होता है। आईएमओ यह लागू करने के लिए काफी आसान है लेकिन पर्याप्त लचीला भी है; संक्षेप में, मेरे लिए यह सरलतम समाधान है जो काम कर सकता है।

सजावटी श्रृंखला में छूट का क्रम शायद मनमाने ढंग से नहीं है; पहले अनुमान लगाते हैं कि आपको पहले कीमतों में बदलाव को लागू करना चाहिए, फिर मात्रा में बदलाव करना चाहिए। लेकिन मुझे लगता है कि यह एक बहुत मजबूत बाधा नहीं है।

+0

धन्यवाद। सजावट के माध्यम से देखने के लिए यह देखने के लिए कि यह फिट बैठता है ... – Chev

4

जैसा कि मैंने आपके प्रश्न पर टिप्पणियों में उल्लेख किया है, मुझे नहीं लगता कि रणनीति इस मामले में उपयुक्त है।

मेरे लिए ये सभी छूट BuyXGetYFree, SpendMoreThanXGetYDiscount आदि सभी नियम हैं (और सभी सफलतापूर्वक छूट प्राप्त करने के बारे में नहीं हो सकते हैं) जिन्हें उत्पाद/कार्ट लागत की गणना करने में लागू किया जा सकता है। मैं आपके द्वारा उल्लिखित नियमों का उपयोग करने वाले नियमों का निर्माण करूँगा और जब आप गाड़ी से इसकी लागत प्रक्रिया की गणना करने के लिए नियमों की गणना करेंगे। नियम Engine कार्ट और समग्र आदेश बनाने वाली उत्पाद लाइनों को संसाधित करेगा और लागत आदि के लिए प्रासंगिक समायोजन लागू करेगा

नियम Engine नियमों को लागू करने के क्रम को भी नियंत्रित कर सकता है।

नियम उत्पाद आधारित हो सकते हैं (उदा। एक खरीदें एक मुफ्त प्राप्त करें) या ऑर्डर आधारित (उदाहरण के लिए एक्स एक्स आइटम मुफ्त शिपिंग प्राप्त करें) और आप में समाप्ति तिथियां भी हो सकती हैं। ये नियम डेटा स्टोर पर बने रहेंगे।