views:

171

answers:

1

I've been trying to implement a fluent interface for a set of rules in my system. What I am trying to accomplish is this

TicketRules
.RequireValidation()
.When(quartType => quartType == QuartType.Before).TotalMilageIs(64)
.When(quartType => quartType == QuartType.After).TotalMilageIs(128);

However, I have trouble implementing the When conditional how I intended to be. Currently, I need to call When() twice like in this snippet:

rules.When(param => param.Remarque == "Test").TotalMilageIs(100);
rules.When(param => param.Remarque == "Other").TotalMilageIs(50);

var params1 = new AddTicketParameters() { Remarque = "Test" };
var params2 = new AddTicketParameters() { Remarque = "Other" };

rules.ExecuteWith(params1);

Assert.That(ticket.TotalMilage, Is.EqualTo(100));

rules.ExecuteWith(params2);

Assert.That(ticket.TotalMilage, Is.EqualTo(50));

My TicketRules class looks this:

[EditorBrowsable(EditorBrowsableState.Never)]
public class TicketRules : ITicketRule, IHideObjectMembers
{
 private Ticket theTicket;

 public Ticket Ticket
 {
  set
  {
   theTicket = value;
  }
 }

 private List<ITicketRule> allRules = new List<ITicketRule>();

 public TicketRules()
 {
 }

 public TicketRules(Ticket ticket)
 {
  theTicket = ticket;
 }

 public void Execute()
 {
  ExecuteWith(null, null);
 }

 public void ExecuteWith(AddTicketParameters param)
 {
  ExecuteWith(param, null);
 }

 public virtual void ExecuteWith(AddTicketParameters param, Ticket outsideTicket)
 {
  foreach (ITicketRule rule in allRules)
  {
   rule.ExecuteWith(param, theTicket ?? outsideTicket);
  }
 }

 public TicketRules RequireValidation()
 {
  CreateModifierRule(ticket => ticket.NeedValidation = true);
  return this;
 }

 public TicketRules TotalMilageIs(int milage)
 {
  CreateModifierRule(ticket => ticket.TotalMilage = milage);
  return this;
 }

 private void CreateModifierRule(Action<Ticket> function)
 {
  AddRule(new ModifierTicketRule(function));
 }

 internal void AddRule(ITicketRule rule)
 {
  allRules.Add(rule);
 }

 public WhenClauseTicketRule When(Predicate<AddTicketParameters> predicate)
 {
  WhenClauseTicketRule whenClause = new WhenClauseTicketRule();
  whenClause.Predicate = predicate;

  AddRule(whenClause);

  return whenClause;
 }

 public TicketRules UseStandardFormulaForTotalMilageAndTime()
 {
  AddRule(new StandardFormulaTicketRule());
  return this;
 }

 public TicketRules EnsureMinimumMilageIs(int milage)
 {
  AddRule(new EnsureMinimumMilageTicketRule(milage));
  return this;
 }
}

the ITicketRules

internal interface ITicketRule : IHideObjectMembers
{
 void ExecuteWith(AddTicketParameters param, Ticket ticket);
}

I also need to support the subclasses of AddTicketParameters in the When clause (I've though maybe using generics for that part). I'm posting here because I'm all confused in my design and the Martin Fowler articles confuse me even more.

+1  A: 

This is known as the finishing problem when method chaining Try this

TicketRules
.RequireValidation()
.When(quartType => quartType == QuartType.Before,
      rule => rule.TotalMilageIs(64))
.When(quartType => quartType == QuartType.After,
      rule => rule.TotalMilageIs(128));

It looks a little odd at first, but it wraps your conditionals into a different scope so you can conditionally execute them. Think about it like creating your own if block. By closing it, you know when you can "finish" a sub statement.

Ball
The second argument is an expression tree right ? Because it need to create a new instance of a rule class. The problem is I can't use expression tree because it is a .NET 2.0 project. I use the C# 3.0 compiler thought
Michaël Larouche
It's just a lambda. If you can only use C# 2.0, try an anonymous delegate http://msdn.microsoft.com/en-us/library/0yw3tz5k(VS.80).aspxWhen(quartType => quartType==QuartType.After, delegate(Rule rule){ rule.TotalMillageIs(128))If that doesn't work, how about:When(quartType => quartType == QuartType.After, Rule.TotalMilageIs(128))And create a new rule to be applied later. The major downside to this is you may have two types of rules, instance rules and applied rules. You might be able to factor them together.
Ball
Thanks for the hints.
Michaël Larouche