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

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

问题描述

我想实现,可以处理适用于我的购物车/已完成订单多折扣的系统。我已经申请一个策略类型图案来封装折扣内的折扣的处理。

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.

很想上附着的code一些意见。各种受保护的构造函数和成员标有虚的需要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
}

}

推荐答案

要我 Decorator模式在这里似乎更为适用。它始于你有一个类似的折扣类层次结构,但折扣也将执行 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.

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

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