2010-02-12 35 views
8

Tôi đang cố gắng triển khai hệ thống có thể xử lý nhiều chiết khấu được áp dụng cho giỏ hàng/đơn hàng đã hoàn thành của mình. Tôi đã áp dụng mẫu kiểu chiến lược để đóng gói việc xử lý giảm giá trong phạm vi giảm giá.Chiến lược giảm giá trong giỏ hàng và đơn đặt hàng

Tôi đã đưa ra những điều sau: lớp cơ sở giảm giá trừu tượng với các lớp con tạo nên mức giảm giá cụ thể. Sau đó, chúng được áp dụng cho đối tượng đặt hàng/giỏ hàng và sẽ xử lý nội dung của đơn đặt hàng/giỏ hàng khi được thêm vào giỏ hàng/đơn đặt hàng.

Rất thích một số nhận xét về mã được đính kèm. Nhiều nhà xây dựng được bảo vệ và các thành viên được đánh dấu là "ảo" cần thiết cho 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

Có lẽ bạn có thể lọc ra một số đoạn mã cụ thể mà bạn muốn nhập vào? –

+0

Cảm ơn bạn đã trả lời. Thành thật mà nói nó là nhiều hơn một câu hỏi kiến ​​trúc hơn là một vấn đề cụ thể với mã. – Chev

+0

Mô hình chiến lược dường như không phù hợp với tôi, đặc biệt nếu bạn có thể có nhiều chiết khấu được áp dụng cho giỏ hàng. Đối với tôi, bạn đang tìm cách triển khai một số loại công cụ quy tắc. – David

Trả lời

2

Đối với tôi những Decorator pattern dường như áp dụng thêm ở đây. Nó bắt đầu với hệ thống phân cấp lớp Giảm giá tương tự bạn có, nhưng giảm giá cũng sẽ thực hiện OrderBase. Sau đó, họ trang trí theo thứ tự thay vì chỉ gắn liền với nó. Khi được truy vấn, người trang trí lấy dữ liệu đơn đặt hàng từ thể hiện đơn hàng mà nó trang trí (có thể là một đơn hàng vani đơn giản hoặc một trang trí khác) và áp dụng cho nó mức chiết khấu thích hợp. IMO này khá dễ thực hiện nhưng cũng đủ linh hoạt; trong ngắn hạn, với tôi đây là giải pháp đơn giản nhất có thể hoạt động có thể hoạt động.

Thứ tự chiết khấu trong chuỗi trang trí có lẽ không tùy ý; lúc đầu tiên đoán bạn nên áp dụng giảm giá thay đổi giá đầu tiên, sau đó thay đổi số lượng những người thân. Nhưng tôi đoán đây không phải là một hạn chế rất mạnh.

+0

cảm ơn Peter đã trả lời. Có một trang trí nhìn qua để xem nó có phù hợp không ... – Chev

4

Như tôi đã đề cập trong các nhận xét cho câu hỏi của bạn, tôi không nghĩ rằng chiến lược là thích hợp trong trường hợp này.

Với tôi tất cả những giảm giá này BuyXGetYFree, SpendMoreThanXGetYDiscount vv là tất cả các quy tắc (và có thể không phải tất cả về việc giảm giá) có thể được áp dụng trong tính toán chi phí sản phẩm/giỏ hàng. Tôi sẽ xây dựng một RulesEngine sử dụng các quy tắc bạn vạch ra và khi bạn yêu cầu các giỏ hàng để tính toán quá trình chi phí của nó chống lại RulesEngine. RulesEngine sẽ xử lý các dòng sản phẩm tạo thành giỏ hàng và thứ tự tổng thể và áp dụng các điều chỉnh có liên quan đến chi phí, v.v.

Quy tắcEngine thậm chí có thể kiểm soát thứ tự áp dụng các quy tắc.

Quy tắc có thể là sản phẩm dựa trên (ví dụ: Mua một tặng miễn phí) hoặc đặt hàng (ví dụ: Mua mặt hàng X được giao hàng miễn phí) và thậm chí bạn có thể có ngày hết hạn được tích hợp. Các quy tắc này sẽ được lưu vào kho dữ liệu.

Các vấn đề liên quan