17

Hi I'm stuck with a problem.

I want to implement the builder pattern to make creating my objects easier. The problem I face has to do with nested object. The object I would like to create has a list of other objects in it, and I don't really have an idea on how to tackle it.

I want to be able to do the following (Simpler objects for example):

Receipt RestaurantReceipt = new ReceiptBuilder()
.withDate("value")
.withName("value")
.AddItem("value")
    .WithIngredients("value")
    .WithType("value")
.AddItem("value")
    .WithIngredients("value")
    .WithType("value")
.build();

Or something like:

Receipt RestaurantReceipt = new ReceiptBuilder()
.withDate("value")
.withName("value")
.AddItem("value", item => {
  .WithIngredients("value")
  .WithType("value")
})
.AddItem("value", item => {
  .WithIngredients("value")
  .WithType("value")
})
.build();

Example should be representative for my situation, although if got more than one type of nested object.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
MrWaldo
  • 175
  • 1
  • 6
  • 1
    What does your current code for `ReceiptBuilder` look like? What is the structure of the items you;re trying to add with `AddItem`? – Jamiec Jul 20 '16 at 11:50
  • Is the general trick not to return the instance itself as return value from those operations? So why not `` ... .AddFoo(new Foo().WithIngredient("value").WithType("value")). ... `` ? Just repeat the pattern for those sub-types and use sub-collection specific Add-functions/properties. – BitTickler Jul 20 '16 at 11:50
  • Would be helpful to see `Receipt` class – konkked Jul 20 '16 at 11:52
  • Is there a benefit in having an extra Builder class? Or is this usually only done if the ``Receipt`` object does not implement the pattern itself? Instead of the builder, would extension methods not be preferable? – BitTickler Jul 20 '16 at 11:54

1 Answers1

25

Given code like this

var rb = new ReceiptBuilder();
var receipt = rb.WithName("Name")
            .WithDate(DateTime.Now)
            .WithItem("Item1", i => i.WithIngredients("Ingredients1"))
            .WithItem("Item2", i => i.WithIngredients("Ingredients1"))
            .Build();
Console.WriteLine(receipt);

Your builder is pretty simple, making use of some simple predicates inside the WithItem builder method to allow the consumer to configure each item in a similar "builder" pattern to the top level ReceiptBuilder:

public class ReceiptBuilder
{
    private Receipt r;

    public ReceiptBuilder()
    {
        r = new Receipt();
    }

    public ReceiptBuilder WithName(string name)
    {
        r.Name = name;
        return this;
    }

    public ReceiptBuilder WithDate(DateTime dt)
    {
        r.Date = dt;
        return this;
    }

    public ReceiptBuilder WithItem(string text, Action<ReceiptItemBuilder> itemBuilder)
    {
        var rib = new ReceiptItemBuilder(text);
        itemBuilder(rib);
        r.AddItem(rib.Build());
        return this;
    }

    public Receipt Build()
    {
        return r;
    }
}

public class ReceiptItemBuilder
{
    private ReceiptItem ri;

    public ReceiptItemBuilder(string text)
    {
        ri = new ReceiptItem(text);
    }

    public ReceiptItemBuilder WithIngredients(string ings)
    {
        ri.Ingredients = ings;
        return this;
    }

    // WithType omitted for brevity. 

    internal ReceiptItem Build()
    {
        return ri;
    }
}

Working example: http://rextester.com/IRR50897

Saulo Silva
  • 1,219
  • 1
  • 20
  • 37
Jamiec
  • 133,658
  • 13
  • 134
  • 193