views:

365

answers:

3

I'm taking a crack at writing my first DSL for a simple tool at work. I'm using the builder pattern to setup the complex parent object but am running into brick walls for building out the child collections of the parent object. Here's a sample:

Use:

var myMorningCoffee = Coffee.Make.WithCream().WithOuncesToServe(16);

Sample with closure (I think that's what they're called):

var myMorningCoffee = Coffee.Make.WithCream().PourIn( 
                        x => {
                                x.ShotOfExpresso.AtTemperature(100);
                                x.ShotOfExpresso.AtTemperature(100).OfPremiumType();
                             }
                        ).WithOuncesToServe(16);

Sample class (without the child PourIn() method as this is what I'm trying to figure out.)

 public class Coffee
 {
   private bool _cream;

   public Coffee Make { get new Coffee(); }
   public Coffee WithCream()
   {
     _cream = true;
     return this;
   }
   public Coffee WithOuncesToServe(int ounces)
   {
     _ounces = ounces;
     return this;
   }
 }

So in my app for work I have the complex object building just fine, but I can't for the life of me figure out how to get the lambda coded for the sub collection on the parent object. (in this example it's the shots (child collection) of expresso).

Perhaps I'm confusing concepts here and I don't mind being set straight; however, I really like how this reads and would like to figure out how to get this working.

Thanks, Sam

+1  A: 

What if .IncludeApps accepted an array of AppRegistrations

IncludeApps(params IAppRegistration[] apps)

then

public static class App
{
  public static IAppRegistration IncludeAppFor(AppType type)
  {
    return new AppRegistration(type);
  }
}

public class AppRegistration
{
  private AppType _type;
  private bool _cost;

  public AppRegistration(AppType type)
  {
    _type = type;
  }

  public AppRegistration AtNoCost()
  { 
    _cost = 0;
    return this;
  }
}

so eventually it would look like this...

.IncludeApps
(
  App.IncludeAppFor(AppType.Any), 
  App.IncludeAppFor(AppType.Any).AtNoCost()
)

Inside your IncludeApps method you would inspect the registrations and create the objects as required.

Rohan West
I definitely think you're going in the right direction, but I'd like to solve this problem using c#'s System.Action or System.Func objects in a closure. This is the concept that I'm trying to grasp my head around. Thanks.
sam
This is not a useful place to use closure. Lambda expressions are useful when you want to define an `action` which will occur according to some unknown parameter. Here, everything is known, and you aren't really defining an action. You just want to register an app on the phone.
tster
Good call on the params. I was thinking the same thing. You beat me to it.
David Silva Smith
you're definitely not dealing with closures. see this post for info on how closures behave in c# http://bit.ly/81BfpW
Derick Bailey
A: 

To go the delegate route maybe something like this would work?

var aPhone = MyPhone.Create;
  MyPhone.Create.IncludeApps
  (
    x =>
      {
        x.IncludeAppFor(new object());
      }
  );

class MyPhone
  {
    public MyPhone IncludeApps(Action<MyPhone> includeCommand)
    {
        includeCommand.Invoke(this);
        return this;
    }
  }

If you aren't set on the delegate route maybe params would work?

var anotherPhone = MyPhone.Create.IncludeApps(
    new IncludeAppClass(AppType.Math),
    new IncludeAppClass(AppType.Entertainment).AtNoCost());


class MyPhone
{
    internal MyPhone IncludeApps(params IncludeAppClass[] includeThese)
    {
        if (includeThese == null)
        {
            return this;
        }
        foreach (var item in includeThese)
        {
            this.Apps.Add(Item);
        }
        return this;
    }
}
David Silva Smith
A: 

Ok, so I figured out how to write my DSL using an additional expression builder. This is how I wanted my DSL to read:

var myPreferredCoffeeFromStarbucks =
            Coffee.Make.WithCream().PourIn(
                x =>
                    {
                        x.ShotOfExpresso().AtTemperature(100);
                        x.ShotOfExpresso().AtTemperature(100).OfPremiumType();
                    }
                ).ACupSizeInOunces(16);

Here's my passing test:

[TestFixture]
public class CoffeeTests
{
    [Test]
    public void Can_Create_A_Caramel_Macchiato()
    {
        var myPreferredCoffeeFromStarbucks =
            Coffee.Make.WithCream().PourIn(
                x =>
                    {
                        x.ShotOfExpresso().AtTemperature(100);
                        x.ShotOfExpresso().AtTemperature(100).OfPremiumType();
                    }
                ).ACupSizeInOunces(16);

        Assert.IsTrue(myPreferredCoffeeFromStarbucks.expressoExpressions[0].ExpressoShots.Count == 2);
        Assert.IsTrue(myPreferredCoffeeFromStarbucks.expressoExpressions[0].ExpressoShots.Dequeue().IsOfPremiumType == true);
        Assert.IsTrue(myPreferredCoffeeFromStarbucks.expressoExpressions[0].ExpressoShots.Dequeue().IsOfPremiumType == false);
        Assert.IsTrue(myPreferredCoffeeFromStarbucks.CupSizeInOunces.Equals(16));
    }
}

And here's my CoffeeExpressionBuilder DSL class(s):

public class Coffee
{
    public List<ExpressoExpressionBuilder> expressoExpressions { get; private set; }

    public bool HasCream { get; private set; }
    public int CupSizeInOunces { get; private set; }

    public static Coffee Make
    {
        get
        {
            var coffee = new Coffee
                             {
                                 expressoExpressions = new List<ExpressoExpressionBuilder>()
                             };

            return coffee;
        }
    }

    public Coffee WithCream()
    {
        HasCream = true;
        return this;
    }

    public Coffee ACupSizeInOunces(int ounces)
    {
        CupSizeInOunces = ounces;

        return this;
    }

    public Coffee PourIn(Action<ExpressoExpressionBuilder> action)
    {
        var expression = new ExpressoExpressionBuilder();
        action.Invoke(expression);
        expressoExpressions.Add(expression);

        return this;
    }

    }

public class ExpressoExpressionBuilder
{
    public readonly Queue<ExpressoExpression> ExpressoShots = 
        new Queue<ExpressoExpression>();

    public ExpressoExpressionBuilder ShotOfExpresso()
    {
        var shot = new ExpressoExpression();
        ExpressoShots.Enqueue(shot);

        return this;
    }

    public ExpressoExpressionBuilder AtTemperature(int temp)
    {
        var recentlyAddedShot = ExpressoShots.Peek();
        recentlyAddedShot.Temperature = temp;

        return this;
    }

    public ExpressoExpressionBuilder OfPremiumType()
    {
        var recentlyAddedShot = ExpressoShots.Peek();
        recentlyAddedShot.IsOfPremiumType = true;

        return this;
    }
}

public class ExpressoExpression
{
    public int Temperature { get; set; }
    public bool IsOfPremiumType { get; set; }

    public ExpressoExpression()
    {
        Temperature = 0;
        IsOfPremiumType = false;
    }
}

Any and all suggestions are welcome.

sam