Strategies and discriminators in NHibernate


I recently posted about enumeration classes, and how I like to use them as a sort of “Enumerations with behavior”.  Not every enumeration should be replaced with a class, but that pattern helps quite a bit when I find a lot of switch statements concerning my enumeration.  Often, these strategies come from data.  For example, I recently had a situation where individual Product instances had different PricingStrategies.  Each PricingStrategy depended on specific rules around Product data, so the Product data owners would decide what PricingStrategy each Product was eligible for.

This was nice, as it seemed like they pretty much flipped a coin on how they wanted to do pricing.  In any case, when the PricingStrategy is data-driven, it leaves a lot of flexibility on the business side to change pricing as they need to, with full confidence that each PricingStrategy type could handle its own pricing rules.

To handle this, we went with something very close to the enumeration classes I described earlier.  First, here’s our Product class:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal UnitPrice { get; set; }
    public int UnitsInStock { get; set; }

    public Category Category { get; set; }
    public PricingStrategy PricingStrategy { get; set; }

    public decimal GetPrice()
    {
        decimal discountAmount = (UnitPrice * PricingStrategy.GetDiscountPercentage(this) / 100m);

        return UnitPrice - discountAmount;
    }
}

Note that the GetPrice uses the PricingStrategy to calculate the final price.  The PricingStrategy (really, a discount strategy in this example) had various means of determining what he discount percentage would be.  It used something of a double-dispatch to calculate this, passing the Product into the PricingStrategy.  Our PricingStrategy class is our old enumeration class:

public abstract class PricingStrategy
{
    private int _id;
    private string _name;
    private string _displayName;

    public static readonly PricingStrategy FullPrice = new FullPriceStrategy(1);
    public static readonly PricingStrategy LowStock = new LowStockDiscountPriceStrategy(2);

    private PricingStrategy() {}

    protected PricingStrategy(int id)
    {
        _id = id;
    }

    public int Id
    {
        get { return _id; }
    }

    public string Name
    {
        get { return _name; }
    }

    public string DisplayName
    {
        get { return _displayName; }
    }

    public override bool Equals(object obj)
    {
        var otherValue = obj as PricingStrategy;

        if (otherValue == null)
        {
            return false;
        }

        return otherValue.Id.Equals(Id);
    }

    public override int GetHashCode()
    {
        return _id.GetHashCode();
    }

    public abstract decimal GetDiscountPercentage(Product product);

    private class FullPriceStrategy : PricingStrategy
    {
        private FullPriceStrategy() { }

        public FullPriceStrategy(int id)
            : base(id)
        {
        }

        public override decimal GetDiscountPercentage(Product product)
        {
            return 0.0m;
        }
    }

    private class LowStockDiscountPriceStrategy : PricingStrategy
    {
        private LowStockDiscountPriceStrategy() { }

        public LowStockDiscountPriceStrategy(int id)
            : base(id)
        {
        }

        public override decimal GetDiscountPercentage(Product product)
        {
            if (product.UnitsInStock < 10)
                return 10m;

            return 0m;
        }
    }
}

It’s rather long, but the basic idea is that the only public type is PricingStrategy.  The implementation types are completely private to anyone outside the abstract PricingStrategy.  Anyone wanting to use a specific PricingStrategy can use the static readonly fields, which again hide the private types.  No one refers to anything but the base PricingStrategy class.

The PricingStrategy values are stored in the database, here is what the tables look like:

In the database, each Product has a foreign key reference to the PricingStrategies table, which has only two entries right now.  Each PricingStrategyID corresponds to the ID used in the static fields defined in the PricingStrategy class.  That way, no matter if a user choose to pull from the database or from the static fields, each Value Object is equal to the other.  The corresponding NHibernate mapping for PricingStrategy would be:

<class name="PricingStrategy" table="PricingStrategies">
    <id name="Id" column="PricingStrategyID" type="int">
        <generator class="assigned" />
    </id>
    <discriminator column="PricingStrategyID" />
    <property name="Name" />

    <subclass discriminator-value="1" extends="PricingStrategy" name="PricingStrategy+FullPriceStrategy" />
    <subclass discriminator-value="2" extends="PricingStrategy" name="PricingStrategy+LowStockDiscountPriceStrategy" />
</class>

Note that each subclass is called out with the element, with the corresponding discriminator value.  The specific strategy is specified with the “name” attribute.  At load time, NHibernate will check the discriminator column value and pull up the corresponding subclass that matches that found value.  When the Product table has a “1” for the PricingStrategyID, NHibernate pulls up the FullPriceStrategy, and so on.

So how might this look in actual client code?  Here are a couple of tests that pull out two different Product entries from the database, each with a different PricingStrategy:

[Test]
public void Products_with_full_price_strategy_should_charge_full_price()
{
    ISession session = GetSession();
    var product = session.Get<Product>(22);

    product.PricingStrategy.ShouldEqual(PricingStrategy.FullPrice);
    product.GetPrice().ShouldEqual(product.UnitPrice);
}

[Test]
public void Products_with_low_stock_price_strategy_should_charge_discount_when_stock_is_low()
{
    ISession session = GetSession();
    var product = session.Get<Product>(8);

    product.UnitsInStock.ShouldBeLessThan(10);
    product.PricingStrategy.ShouldEqual(PricingStrategy.LowStock);
    product.GetPrice().ShouldBeLessThan(product.UnitPrice);
}

The first Product uses the FullPrice strategy (proven by the first assertion).  Its price should simply equal the UnitPrice.  In the second test, that Product uses the LowStock pricing strategy.  That strategy charges a discount when the stock gets low (maybe it’s a blue-light special?).  Its final price should be lower than the original UnitPrice, and this is verified by our unit test above.

When strategies or enumeration classes aren’t completely code-driven, and are data driven as was in this case, NHibernate discriminators provide a nice means of a “strategy factory”.  Each individual strategy instance is supplied with a distinct discriminator value, allowing me to add new strategy/enumeration class implementations as needed.  The specific implementation is data-driven, which makes our product data owners happy.

Parameter lists in NHibernate