折扣策略在购物车和订单 [英] Discount strategy in shopping cart and orders

查看:118
本文介绍了折扣策略在购物车和订单的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试实施可以处理我的购物车/已完成订单应用的多种折扣的系统。我已经应用策略类型模式来封装折扣内的折扣处理。

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.

将会对附加的代码感兴趣。各种受保护的构造函数和标记为虚拟的成员为nhibernate。

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
}

}

推荐答案

正如我在您的问题的评论中提到的在这种情况下,不要认为策略很适合。

As I mentioned in the comments to your question I don't think strategy is apt in this case.

对我来说,所有这些折扣BuyXGetYFree,SpendMoreThanXGetYDiscount等都是规则(并不一定都是关于获得折扣)可用于计算商品/购物车成本。我将使用您概述的规则和当您要求购物车根据RulesEngine计算其成本过程时,将构建一个RulesEngine。 RulesEngine将处理构成购物车的产品线和总体订单,并对成本等进行相关调整。

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.

RulesEngine甚至可以控制规则的顺序

The RulesEngine could even control the order in which the rules are applied.

规则可以是基于产品的(例如,购买一张免费)或订单(例如,购买X件免费送货),甚至可以到期内置的日期。这些规则将持续到数据存储。

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.

这篇关于折扣策略在购物车和订单的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆