Search code examples
c#design-patternsfluentmethod-chainingbuilder-pattern

Builder pattern with nested objects


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.


Solution

  • 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