Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

create fluent interface for adding elements to a list

this is what I'm trying to achieve:

config.Name("Foo")
      .Elements(() => {
                         Element.Name("element1").Height(23);
                         Element.Name("element2").Height(31);
                      })
     .Foo(23);

or like this:

  .Elements(e => {
                     e.Name("element1").Height(23);
                     e.Name("element2").Height(31);
                  })
  .Foo(3232);

this is what I have for the moment:

public class Config
{
   private string name;
   private int foo;
   private IList<Element> elements = new List<Element>();

   public Config Name(string name)
   {
      this.name = name;
      return this;
   }

   public Config Foo(int x)
   {
       this.foo = x;
   }

   ... //add method for adding elements 

   class Element
   {
      public string Name { get; set; }
      public int Height { get; set; }
   }
}

anybody knows how to do this ?

like image 937
Omu Avatar asked Apr 27 '12 08:04

Omu


4 Answers

public class Config
{
   private string name;
   private IList<Element> elements = new List<Element>();
   public IList<Element> GetElements {get {return this.elements;}}
   public Config Name(string name)
   {
      this.name = name;
      return this;
   }

   public Config Elements(IEnumerable<Element> list)
   {
        foreach ( var element in list)
            elements.Add(element);
        return this;
   }

   public Config Elements(params Element[] list)
   {
        foreach ( var element in list)
            elements.Add(element);
        return this;
   }

   public Config Elements(params Expression<Func<Element>>[] funcs)
   {
        foreach (var func in funcs )
            elements.Add(func.Compile()());
        return this;
   }

   public Config Elements(params Expression<Func<IEnumerable<Element>>>[] funcs)
   {
        foreach (var func in funcs )
            foreach ( var element in func.Compile()())
                elements.Add(element);
        return this;
   }

   public class Element
   {
      public string Name { get; set; }
      public int Height { get; set; }     
      public Element() {}
      public Element(string name)
      {
         this.Name = name;
      }  
      public Element AddHeight(int height)
      {
          this.Height = height;
          return this;
      }
      public static Element AddName(string name)
      {
        return new Element(name);
      }
   }
}

usage

var cfg = new Config()
    .Name("X")
    .Elements(new [] { new Config.Element { Name = "", Height = 0} })
    .Elements(
            Config.Element.AddName("1").AddHeight(1), 
            Config.Element.AddName("2").AddHeight(2) 
            )
    .Elements(
        () => Config.Element.AddName("1").AddHeight(1)
    )
    .Elements(
        () => new[] {
                Config.Element.AddName("1").AddHeight(1),
                Config.Element.AddName("1").AddHeight(1)
               }
    )
like image 55
Adrian Iftode Avatar answered Nov 03 '22 18:11

Adrian Iftode


Any reason you don't want to use object and collection initializers?

public class Config
{
   public string Name { get; set; }
   public int Foo { get; set; }
   public IList<Element> Elements { get; private set; }

   public Config()
   {
       Elements = new List<Element>();
   }
}

// I'm assuming an element *always* needs a name and a height
class Element
{
   public string Name { get; private set; }
   public int Height { get; private set; }

   public Element(string name, int height)
   {
       this.Name = name;
       this.Height = height;
   }
}

Then:

var config = new Config
{
    Name = "Foo",
    Elements = { 
        new Element("element1", 23),
        new Element("element2", 31)
    },
    Foo = 23
};

If you don't want to expose the list of elements directly, you could always turn that into a builder, and copy it into a more private data structure on Build:

var config = new Config.Builder
{
    Name = "Foo",
    Elements = { 
        new Element("element1", 23),
        new Element("element2", 31)
    },
    Foo = 23
}.Build();

This has the additional advantage that you can make Config itself immutable.

If you always need Name to be present, just take that as a constructor parameter instead.

While there are times where it's good to have a fluent interface with mutating (or copy-and-change) method calls, in this case I think collection/object initializers are more idiomatic C#.

Note that if you're using C# 4 and you want to make your Element constructor calls, you can always use named arguments:

new Element(name: "element2", height: 31)
like image 26
Jon Skeet Avatar answered Nov 03 '22 18:11

Jon Skeet


I'd rather go with following fluent interface:

Config config = new Config("Foo")
                        .WithElement("element1", 23)
                        .WithElement("element2");

I think it's more readable and compact. Implementation:

public class Config
{
    private string name;
    private IList<Element> elements = new List<Element>();

    public Config(string name)
    {
        this.name = name;
    }

    public Config WithElement(string name, int height = 0)
    {
        elements.Add(new Element() { Name = name, Height = height });
        return this;
    }

    class Element
    {
        public string Name { get; set; }
        public int Height { get; set; }
    }
}

If name is optional, then add Config constructor without parameters. Also consider optional parameters for WithElemnt method, if you don't need both height and name.

UPDATE: I changed height to be optional parameter to show how you can add elements with only name specified.

UPDATE (if you want to allow only one group of elements)

Config config = new List<Element>()
                    .AddElement(new Element {Name = "element1", Height = 23 })
                    .AddElement(new Element {Name = "element2" })
                    .WrapToConfig()
                    .Name("config1");

Implementation:

public static class ConfigurationHelper
{
    public static IList<Element> AddElement(this IList<Element> elements, Element element)
    {
        elements.Add(element);
        return elements;
    }

    public static Config WrapToConfig(this IList<Element> elements)
    {
        return Config(elements);
    }
}

But this is not very obvious for users, so I'd go with first simple fluent interface.

like image 3
Sergey Berezovskiy Avatar answered Nov 03 '22 17:11

Sergey Berezovskiy


Use data builder pattern. Nice thing about that is that it separates the fluent build api from the data objects. Of course you can omit "with" in your convention.

Usage:

var aConfig = new ConfigBuilder();

// create config fluently with lambdas
Config config = aConfig.WithName("Foo")
        .WithElement(e => e.WithName("element1").WithHeight(23))
        .WithElement(e => e.WithName("element2").WithHeight(31))
    .WithFoo(3232)
    .Build();

// create elements in one go
config = aConfig.WithName("Foo")
         .WithElements(
             e => e.WithName("element1").WithHeight(23), 
             e => e.WithName("element2").WithHeight(31))
     .WithFoo(3232)
     .Build();


var anElement = new ElementBuilder();

// or with builders 
config = aConfig.WithName("Foo")
        .WithElement(anElement.WithName("element1").WithHeight(23))
        .WithElement(anElement.WithName("element2").WithHeight(31))
    .WithFoo(3232)
    .Build();

// use builders to reuse configuration code
anElement.WithHeigh(100);

config = aConfig.WithName("Bar")
        .WithElement(anElement.WithName("sameheight1"))
        .WithElement(anElement.WithName("sameheight2"))
    .WithFoo(5544)
    .Build();

Implementation:

public class ConfigBuilder
{
    private string name;
    private int foo;
    private List<Element> elements = new List<Element>();

    public ConfigBuilder WithName(string name)
    {
         this.name = name;
         return this;
    }

    public ConfigBuilder WithFoo(int foo)
    {
        this.foo = foo;
        return this;
    }

    public ConfigBuilder WithElement(Element element)
    {
        elements.Add(element);
        return this;
    }

    public ConfigBuilder WithElement(ElementBuilder element)
    {
        return WithElement(element.Build());
    }

    public ConfigBuilder WithElement(Action<ElementBuilder> builderConfig)
    {
         var elementBuilder = new ElementBuilder();
         builderConfig(elementBuilder);
         return this.WithElement(elementBuilder);
    }

    public ConfigBuilder WithElements(params Action<ElementBuilder>[] builderConfigs)
    {
         foreach(var config in builderConfigs)
         {
              this.WithElement(config);
         }

         return this;
    }

    public Config Build()
    {
         return new Config() 
         { 
             Name = this.name,
             Foo = this.foo,
             Elements = this.elements
         };
    }
}

public class ElementBuilder
{
    private string name;
    private int height;

    public ElementBuilder WithName(string name)
    {
        this.name = name;
        return this;
    }

    public ElementBuilder WithHeight(int height)
    {
        this.height = height;
        return this;
    }

    public Element Build()
    {
        return new Element() 
        { 
            Name = this.name,
            Height = this.height
        };
    }
}

public class Config
{
    public string Name { get; set; }
    public int Foo { get; set; }
    public IList<Element> Elements { get; set; }
}

public class Element
{
    public string Name { get; set; }
    public int Height { get; set; }
}
like image 1
Peter Zajic Avatar answered Nov 03 '22 16:11

Peter Zajic