The goal is to build a metal database with UnitsNet with a defined pool of material properties.
These properties are to be represented in a DataGrid.
For example the PropertyDataGrid for copper:
Property
Value
Unit
Yield
40
MPa
Young modulus
70000
MPa
Density
2.6989
g/cm³
I'm not sure how to start.
First option: use an interface
using UnitsNet;
public interface IMetalProperty
{
public IQuantity Quantity { get; set; }
}
public class Density : IMetalProperty
{
public IQuantity Quantity { get; set; }
public override string ToString()
{
return Quantity.ToString();
}
}
public class Yield : IMetalProperty
{
public Pressure Quantity { get; set; }
public override string ToString()
{
return Quantity.ToString();
}
}
public class YoungModulus : IMetalProperty
{
public Pressure Value { get; set; }
public override string ToString()
{
return Quantity.ToString();
}
}
Than I can go ahead with a list or whatever:
var properties = new List<IMetalProperty>();
properties.Add(new Density { Quantity = UnitsNet.Density.FromGramsPerCubicCentimeter(2.6989) });
properties.Add(new Yield { Quantity = Pressure.FromMegapascals(40) });
Second option: use an abstract class
using UnitsNet;
public abstract class MetalProperty
{
public IQuantity Quantity { get; set; }
public override string ToString()
{
return Quantity.ToString();
}
}
public class Density : IMetalProperty
{
}
public class Yield : IMetalProperty
{
}
public class YoungModulus : IMetalProperty
{
}
The list works just as well:
var properties = new List<MetalProperty>();
properties.Add(new Density { Quantity = UnitsNet.Density.FromGramsPerCubicCentimeter(2.6989) });
properties.Add(new Yield { Quantity = Pressure.FromMegapascals(40) });
Questions:
Which is the better or the most typical way?
Is there a possibility to ensure the use of the right Units?
For example that it is restricted to add a metal property to the list: new Yield { Quantity = Pressure.FromGramsPerCubicCentimeter(40) }
How can I provide a list of all possible properties from which the user can choose one and define a new property for this metal?
Thanks for any help!
Related
I'd like to store LicenseInformations for multiple domains in my application.
The structure looks the following way:
public class LicenseData
{
// properties...
public List<LicenseDomain> Domains { get; set; }
// other properties...
}
public class LicenseDomain
{
// properties...
public object LicenseConfig { get; set; }
}
We have multiple domains with total different properties, but the license may contain multiple configurations..
For example:
{
"MaxValidUsers": 5
}
{
"Property": "xy"
"SubProperty": { "Foo" : "Bar"
}
}
The generation is no problem in any way..
But if I restore the informations from my signed json file I deserialize to object..
Which pattern / possiblity I have to work with Interfaces / Abstracts / that I can (RE)store generic informations here..
Right now I hack with:
JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(domain.LicenseConfig))
But I can't agree with myself.
So, based on the pieces of context I can grab, I would actually recommend having your LicenseConfig stored as a JSON string, which would give you the ability to do something like this:
public class LicenseDomain
{
// properties...
// Depending on how this is loaded,
// this property (or at least its setter) could be made private/protected/internal
public string LicenseConfigJson { get; set; }
public T LicenseConfig<T>() where T : BaseLicenseConfig
{
if (string.IsNullOrWhiteSpace(LicenseConfigJson))
{
return null;
}
return JsonConvert.DeserializeObject<T>(LicenseConfigJson);
}
public void SaveLicenseConfig<T>(T config) where T : BaseLicenseConfig
{
if (config == null)
{
LicenseConfigJson = null;
}
else
{
LicenseConfigJson = JsonConvert.SerializeObject(config);
}
}
}
Or if each LicenseDomain can only have one type of LicenseConfig, you could make it a generic parameter to the class:
public class LicenseData
{
// properties...
public List<LicenseDomain<BaseLicenseConfig>> Domains { get; set; }
// other properties...
}
public class LicenseDomain<T> where T : BaseLicenseConfig
{
// properties...
// Depending on where this value comes from, you could do this a variety of ways,
//but this is just one
public string LicenseConfigJson { get; set; }
public T LicenseConfig
{
get
{
if (string.IsNullOrWhiteSpace(LicenseConfigJson))
{
return null;
}
return JsonConvert.DeserializeObject<T>(LicenseConfigJson);
}
set
{
if (value == null)
{
LicenseConfigJson = null;
}
else
{
LicenseConfigJson = JsonConvert.SerializeObject(value);
}
}
}
}
public abstract class BaseLicenseConfig
{
}
public class LicConfig1 : BaseLicenseConfig
{
public int MaxValidUsers { get; set;}
}
public class LicConfig2 : BaseLicenseConfig
{
public string Property {get;set;}
public SubProp SubProperty {get;set;}
}
public class SubProp
{
public string Foo {get;set;}
}
In both cases, the BaseLicenseConfig class is strictly to enforce that everything in the domain list can come from a base class of some kind. If that's not important, you don't need the base class and can remove the where T : BaseLicenseConfig from LicenseDomain class.
there is a concept about inheritance which I do not quite understand.
I have a
protected DeveloperReport DeveloperReport; // Field
Wouldn't PersonalInfoBuilder be able to access that field ?
If yes,
public PersonalInfoBuilder MyPersonalInfo => new PersonalInfoBuilder(DeveloperReport);
Why do I still have to pass the DeveloperReport(field) into PersonalInfoBuilder constructor, when I can
just modify the protected DeveloperReport field by calling new PersonalInfoBuilder(), instead of
new PersonalInfoBuilder(DeveloperReport)?
And, how the concept of "return this" return the changes made to DeveloperReport(field) back to
DeveloperReportBuilder?
Thanks !
class DeveloperReport
{
// Properties
public int Id { get; set; }
public string Name { get; set; }
public DeveloperLevel Level { get; set; }
public int WorkingHours { get; set; }
public int HourlyRate { get; set; }
// Methods
public double CalculateSalary() => WorkingHours * HourlyRate;
}
class DeveloperReportBuilder
{
protected DeveloperReport DeveloperReport;
public PersonalInfoBuilder MyPersonalInfo => new PersonalInfoBuilder(DeveloperReport);
public DeveloperReportBuilder()
{
DeveloperReport = new DeveloperReport();
}
// return developer report.
public DeveloperReport Build() => DeveloperReport;
}
class PersonalInfoBuilder : DeveloperReportBuilder
{
public PersonalInfoBuilder(DeveloperReport report)
{
DeveloperReport = report;
}
public PersonalInfoBuilder()
{
}
public PersonalInfoBuilder NameIs(string name)
{
DeveloperReport.Name = name;
return this;
}
public PersonalInfoBuilder IDis(int id)
{
DeveloperReport.Id = id;
return this;
}
}
You only have to pass the report instance if you want to have both instances of DeveloperReportBuilder and PersonalInfoBuilder have acces to the same instance of DeveloperReport.
Inheritance will not copy the instance values.
I often end up writing classes like this:
public class Animal
{
public string Colour { get; set; }
public int Weight { get; set; }
public Animal(Dog data)
{
this.Colour = data.Colour;
this.Weight = data.Weight;
}
public Animal(Cat data)
{
this.Colour = data.Colour;
this.Weight = data.Weight;
}
}
When you have lots of properties and types then you quickly end up with a lot of boiler plate code. Ideally in this situation I would just create an IAnimal interface and reference that. I'm currently in a situation where the Dog and Cat classes exist in a third party assembly and I can't modify them. The only solution that I can come up with is:
public class Animal
{
public string Colour { get; set; }
public int Weight { get; set; }
public Animal(Cat data){Init(data);}
public Animal(Dog data){Init(data);}
private void Init(dynamic data)
{
this.Colour = data.Colour;
this.Weight = data.Weight;
}
}
This works but I lose all type safety, is there a better solution than constructor injection?
Thanks,
Joe
EDIT: Here is a real world example. I have a third party library which returns 3 objects called:
GetPageByIdResult
GetPagesByParentIdResult
GetPagesByDateResult
(These are all auto generated classes from a service reference and the properties are pretty much identical)
Instead of dealing with these three objects I want to deal with a single PageData object or a collection of them.
You can have the logic in one common constructor that all the other constructors call:
public class Animal
{
public string Colour { get; set; }
public int Weight { get; set; }
public Animal(Dog data) : this (data.Colour, data.Weight)
{
}
public Animal(Cat data) : this (data.Colour, data.Weight)
{
}
private Animal(string colour, int weight)
{
this.Colour = colour;
this.Weight = weight;
}
}
This is pretty similar to your second solution but it doesn't lose type safety.
I'm currently in a situation where the Dog and Cat classes exist in a
third party assembly and I can't modify them
I'd suggest Automapper-based solution:
public static class AnimalFactory
{
public static Animal Create<T>(T source)
where T : class
{
Mapper.CreateMap<T, Animal>();
return Mapper.Map<Animal>(source);
}
}
Usage:
var catAnimal = AnimalFactory.Create(cat);
var dogAnimal = AnimalFactory.Create(dog);
Of course, you can provide a way to custom mapping configuration, if needed.
If you do not want to have the class littered like that you can try Extension methods?
public static Animal ToAnimal(this Dog item)
{
return new Animal() {Weight = item.Weight, Colour = item.Colour};
}
public static Animal ToAnimal(this Cat item)
{
return new Animal() {Weight = item.Weight, Colour = item.Colour};
}
try using json serializer's, with that we can ensure type safety.
public class Animal
{
public string Colour { get; set; }
public long Weight { get; set; }
public string Name { get; set; }
public Animal Create<T>(T anyType)
{
return GetObject<T, Animal>(anyType);
}
public K GetObject<T, K>(T type1)
{
try
{
var serialized = JsonConvert.SerializeObject(type1);
return JsonConvert.DeserializeObject<K>(serialized);
}
catch (Exception ex)
{
return default(K);
}
}
}
class Program
{
public static void Main(string[] args)
{
Animal obj = new Animal();
var animal = obj.Create(new { Colour = "Red", Weight = 100 });
//here you can pass any object, only same name properties will be initialized..
Console.WriteLine(animal.Colour + " : " + animal.Weight);
Console.ReadKey();
}
}
how do i handle Enums without using switch or if statements in C#?
For Example
enum Pricemethod
{
Max,
Min,
Average
}
... and i have a class Article
public class Article
{
private List<Double> _pricehistorie;
public List<Double> Pricehistorie
{
get { return _pricehistorie; }
set { _pricehistorie = value; }
}
public Pricemethod Pricemethod { get; set; }
public double Price
{
get {
switch (Pricemethod)
{
case Pricemethod.Average: return Average();
case Pricemethod.Max: return Max();
case Pricemethod.Min: return Min();
}
}
}
}
i want to avoid the switch statement and make it generic.
For a specific Pricemethod call a specific Calculation and return it.
get { return CalculatedPrice(Pricemethod); }
Wich pattern is to use here and maybe someone have a good implementation idea.
Searched already for state pattern, but i dont think this is the right one.
how do I handle enums without using switch or if statements in C#?
You don't. enums are just a pleasant syntax for writing const int.
Consider this pattern:
public abstract class PriceMethod
{
// Prevent inheritance from outside.
private PriceMethod() {}
public abstract decimal Invoke(IEnumerable<decimal> sequence);
public static PriceMethod Max = new MaxMethod();
private sealed class MaxMethod : PriceMethod
{
public override decimal Invoke(IEnumerable<decimal> sequence)
{
return sequence.Max();
}
}
// etc,
}
And now you can say
public decimal Price
{
get { return PriceMethod.Invoke(this.PriceHistory); }
}
And the user can say
myArticle.PriceMethod = PriceMethod.Max;
decimal price = myArticle.Price;
You could create an interface, and classes that implement it:
public interface IPriceMethod
{
double Calculate(IList<double> priceHistorie);
}
public class AveragePrice : IPriceMethod
{
public double Calculate(IList<double> priceHistorie)
{
return priceHistorie.Average();
}
}
// other classes
public class Article
{
private List<Double> _pricehistorie;
public List<Double> Pricehistorie
{
get { return _pricehistorie; }
set { _pricehistorie = value; }
}
public IPriceMethod Pricemethod { get; set; }
public double Price
{
get {
return Pricemethod.Calculate(Pricehistorie);
}
}
}
Edit: another way is using a Dictionary to map Funcs, so you don't have to create classes just for this (this code is based on code by Servy, who since deleted his answer):
public class Article
{
private static readonly Dictionary<Pricemethod, Func<IEnumerable<double>, double>>
priceMethods = new Dictionary<Pricemethod, Func<IEnumerable<double>, double>>
{
{Pricemethod.Max,ph => ph.Max()},
{Pricemethod.Min,ph => ph.Min()},
{Pricemethod.Average,ph => ph.Average()},
};
public Pricemethod Pricemethod { get; set; }
public List<Double> Pricehistory { get; set; }
public double Price
{
get
{
return priceMethods[Pricemethod](Pricehistory);
}
}
}
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
}
}
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.
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.