购物车和订单中的折扣策略
我正在尝试实现一个可以处理应用于我的购物车/已完成订单的多个折扣的系统。我应用了策略类型模式来将折扣处理封装在折扣中。
我提出了以下方案:一个抽象折扣基类,其子类构成具体折扣。然后将它们应用于订单/购物车对象,并在添加到购物车/订单时处理订单/购物车的内容。
希望对所附代码有一些评论。 nhibernate 所需的各种受保护的构造函数和标记为“虚拟”的成员。
雪佛兰
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
}
}
I am trying to implement a system that can handle multiple discounts applied to my cart/completed orders. I have applied a strategy type pattern to encapsulate the processing of the discounts within the discounts.
I have come up with the following: an abstract discount base class with subclasses making up the concrete discounts. These are then applied to either an order/cart object and will process the contents of the order/cart when added to the cart/order.
Would love some comments on the code attached. Various protected constructors and members marked "virtual" needed for 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
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
正如我在对你的问题的评论中提到的,我认为在这种情况下策略不合适。
对我来说,所有这些折扣 BuyXGetYFree、SpendMoreThanXGetYDiscount 等都是可以应用于计算产品/购物车成本的规则(并且可能不一定都与获得折扣有关)。我将利用您概述的规则构建一个规则引擎,当您要求购物车计算其成本时,它会根据规则引擎进行处理。规则引擎将处理组成购物车的产品线和整体订单,并对成本等应用相关调整。
规则引擎甚至可以控制应用规则的顺序。
规则可以是基于产品的(例如买一送一)或基于订单的(例如买 X 件商品免运费),甚至可以内置到期日期。这些规则将保存到数据存储中。
As I mentioned in the comments to your question I don't think strategy is apt in this case.
To me all these discounts BuyXGetYFree, SpendMoreThanXGetYDiscount etc are all rules (and may not all neccesarily be about getting discount) that can be applied in calculating product/cart cost. I would build a RulesEngine utilising the rules you outlined and when you ask the cart to calculate its cost process it against the RulesEngine. The RulesEngine would process the product lines making up the cart and the overall order and apply the relevant adjustments to costs etc.
The RulesEngine could even control the order in which the rules are applied.
Rules could be product based (e.g. Buy one get one free) or order based (eg. Buy X items get free shipping) and you could even have expiry dates built in. These rules would be persisted to a data store.
对我来说,装饰器模式在这里似乎更适用。它以类似的 Discount 类层次结构开始,但折扣也会实现
OrderBase
。然后他们装饰订单而不是仅仅附加到订单上。当查询时,装饰器从它装饰的订单实例(可能是普通订单或其他装饰器)获取订单数据,并对其应用适当的折扣。 IMO 这相当容易实现,但也足够灵活;简而言之,对我来说,这是可能有效的最简单的解决方案。不过,装饰器链中的折扣顺序可能不是任意的;乍一看,您应该首先应用改变价格的折扣,然后应用改变数量的折扣。但我想这并不是一个很强的约束。
To me the Decorator pattern seems more applicable here. It starts with the a similar Discount class hierarchy you have, but the discounts would also implement
OrderBase
. Then they decorate the order instead of just being attached to it. When queried, the decorator gets the order data from the order instance it decorates (which may be a plain vanilla order, or another decorator), and applies to it the appropriate discount. IMO this is fairly easy to implement but also flexible enough; in short, to me this is the simplest solution that may work.The order of discounts in the decorator chain is probably not arbitrary though; at first guess you should apply price altering discounts first, then quantity altering ones. But I guess this is not a very strong constraint.