Introduction
To handle complex business rules, Eric Evans describes in his book Domain-Driven Design the Policy pattern (page 18), also known as Strategy pattern (Gamma). Based on his example, "Extracting a Hidden Concept" (page 17); this tip demonstrates a possible implementation.
Using the Code
First, we need the domain foundation classes for rules and policies:
public interface IRule
{
bool IsValid { get; }
}
public class Rule : IRule
{
public Rule()
{
}
public Rule(bool isValid)
{
IsValid = isValid;
}
public bool IsValid { get; private set; }
}
public interface IPolicy
{
}
public interface IPolicy<T1> : IPolicy
{
IRule Validate(T1 arg1);
}
public interface IPolicy<T1, T2> : IPolicy
{
IRule Validate(T1 arg1, T2 arg2);
}
public interface IPolicy<T1, T2, T3> : IPolicy
{
IRule Validate(T1 arg1, T2 arg2, T3 arg3);
}
public abstract class Policy : IPolicy
{
}
public abstract class Policy<T1> : IPolicy<T1>
{
public abstract IRule Validate(T1 arg1);
}
public abstract class Policy<T1, T2> : IPolicy<T1, T2>
{
public abstract IRule Validate(T1 arg1, T2 arg2);
}
public abstract class Policy<T1, T2, T3> : IPolicy<T1, T2, T3>
{
public abstract IRule Validate(T1 arg1, T2 arg2, T3 arg3);
}
public abstract class DomainObject
{
public TResult Validate<TResult, TPolicy, T1>(T1 arg1)
where TResult : IRule
where TPolicy : IPolicy<T1>
{
return (TResult)((IPolicy<T1>)_policies[typeof(TPolicy)]).Validate(arg1);
}
public TResult Validate<TResult, TPolicy, T1, T2>(T1 arg1, T2 arg2)
where TResult : IRule
where TPolicy : IPolicy<T1, T2>
{
return (TResult)((IPolicy<T1, T2>)_policies[typeof(TPolicy)]).Validate(arg1, arg2);
}
public TResult Validate<TResult, TPolicy, T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
where TResult : IRule
where TPolicy : IPolicy<T1, T2, T3>
{
return (TResult)((IPolicy<T1, T2, T3>)_policies[typeof(TPolicy)]).Validate(arg1, arg2, arg3);
}
protected void RegisterPolicy(IPolicy policy)
{
_policies.Add(policy.GetType(), policy);
}
private readonly Dictionary<Type, IPolicy> _policies = new Dictionary<Type, IPolicy>();
}
Based on the foundation, we have the following business classes:
public class Cargo
{
public int Size { get; set; }
}
public class Voyage
{
public int Capacity { get; set; }
public int BookedCargoSize { get { return cargos.Values.Sum(x => x.Size); } }
public void AddCargo(Cargo cargo, int confirmation)
{
cargos.Add(confirmation, cargo);
}
private readonly Dictionary<int, Cargo> cargos = new Dictionary<int, Cargo>();
}
public class OverbookingRule : Rule
{
public OverbookingRule(int cargoSize, int bookedCargoSize, double maxCapacity, bool isValid) :
base(isValid)
{
CargoSize = cargoSize;
BookedCargoSize = bookedCargoSize;
MaxCapacity = maxCapacity;
}
public int CargoSize { get; private set; }
public int BookedCargoSize { get; private set; }
public double MaxCapacity { get; private set; }
}
public class OverbookingPolicy : Policy<Cargo, Voyage>
{
public override IRule Validate(Cargo cargo, Voyage voyage)
{
double voyageMaxCapacity = voyage.Capacity * 1.1;
return new OverbookingRule(
cargo.Size, voyage.BookedCargoSize, voyageMaxCapacity,
(cargo.Size + voyage.BookedCargoSize) <= voyageMaxCapacity);
}
}
public class VoyageController : DomainObject
{
public VoyageController()
{
RegisterPolicy(new OverbookingPolicy());
}
public OverbookingRule MakeBooking(Cargo cargo, Voyage voyage)
{
var result = Validate<OverbookingRule, OverbookingPolicy, Cargo, Voyage>(cargo, voyage);
if (result.IsValid)
{
int confirmation = GetNextOrderConfirmation();
voyage.AddCargo(cargo, confirmation);
}
return result;
}
private int GetNextOrderConfirmation()
{
int confirmation = nextConfirmation;
nextConfirmation++;
return confirmation;
}
private int nextConfirmation = 1;
}
And finally, the sample application:
class Program
{
static void Main(string[] args)
{
Voyage voyage = new Voyage { Capacity = 100 };
VoyageController voyageController = new VoyageController();
Console.WriteLine("Voyage, capacity={0}", voyage.Capacity);
Console.WriteLine();
for (int i = 0; i < 10; i++)
{
Cargo cargo = new Cargo { Size = 15 };
var rule = voyageController.MakeBooking(cargo, voyage);
if (rule.IsValid)
{
Console.WriteLine("added cargo with size={0}, voyage cargo size={1}",
cargo.Size, voyage.BookedCargoSize);
}
else
{
Console.WriteLine("out of capacity! cargo size={0}, booked size={1}, max capacity={2}",
rule.CargoSize, rule.BookedCargoSize, rule.MaxCapacity);
break;
}
}
Console.WriteLine();
Console.Write("Press any key...");
Console.ReadKey();
}
}