Builder pattern with nested objects
Asked Answered
J

1

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.

Jhelum answered 20/7, 2016 at 11:45 Comment(4)
What does your current code for ReceiptBuilder look like? What is the structure of the items you;re trying to add with AddItem?Wonky
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.Cadaverine
Would be helpful to see Receipt classMcdougall
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?Cadaverine
W
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

Wonky answered 20/7, 2016 at 12:5 Comment(1)
could you explain what happens here please? itemBuilder(rib);Collation

© 2022 - 2024 — McMap. All rights reserved.