2010-02-12 40 views
8

我想實現一個系統,可以處理應用於我的購物車/完成訂單的多種折扣。我已應用策略類型模式來封裝摺扣內的折扣處理。購物車和訂單中的折扣策略

我已經想出了以下內容:一個抽象折扣基類和子類組成的具體折扣。然後這些應用於訂單/購物車對象,並在添加到購物車/訂單時處理訂單/購物車的內容。

會愛上附加的代碼的一些意見。 nhibernate需要各種受保護的構造函數和標記爲「虛擬」的成員。

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

感謝Peter的回覆。通過裝飾看看它是否適合... – Chev

4

正如我在你的問題的評論中提到的,我不認爲在這種情況下策略是恰當的。

對我來說,所有這些折扣BuyXGetYFree,SpendMoreThanXGetYDiscount等都是可以應用於計算產品/購物車成本的規則(並不一定都是獲得折扣)。我會利用您概述的規則構建一個RulesEngine,並且當您要求購物車根據RulesEngine計算其成本處理時。 RulesEngine將處理組成購物車的產品線和總體訂單,並將相關調整應用於成本等。

RulesEngine甚至可以控制應用規則的順序。

規則可以基於產品(例如買一送一)或基於訂單(例如購買X件物品獲得免費送貨),甚至可以有內置的過期日期。這些規則將持續到數據存儲。

相關問題