I need some suggestions on how to build a fluent interface acting as a Builder, responsible for returning different concrete types depending on the methods called.
Imagine that I need to create one of the following types using my ProductBuilder (fluently): Product, ServiceProduct, PackagedProduct (both derived from Product).
I'm thinking of using a fluent syntax like this (other suggestions are more than welcome):
To create a Product:
var product = new ProductBuilder()
.Create("Simple product")
.WithPrice(12.5)
To create a ServiceProduct
var product = new ProductBuilder()
.Create("Service product")
.WithPrice(12.5)
.AsServiceProduct()
.ServiceProductSpecificMethods...()
And PackagedProduct with a call to AsPackagedProduct() instead of AsServiceProduct() etc. You get the idea.
I haven't found a sample that shows best practices on this. Only samples where the final build returns the same type.
Any suggestions?
I see two options here.
If there are a finite number of products that are fixed, and not designed to be extended, then just create a Create
method for each product:
var product = new ProductBuilder()
.CreateSimple()
.WithPrice(12.5);
var product = new ProductBuilder()
.CreateService()
.WithPrice(12.5)
.ServiceProductSpecificMethods...();
If you don't want (or can't have) ProductBuilder
to know all of the types of products, then I would use generics:
public class Product {}
public class SimpleProduct : Product {}
public class ServiceProduct : Product {}
var product = new ProductBuilder<SimpleProduct>()
.WithPrice(12.5);
Here's a starting place for the design to follow:
public class Product
{
public decimal Price { get; set; }
}
public class SimpleProduct : Product { }
public class ServiceProduct : Product
{
public string Service { get; set; }
}
public class ProductBuilder<T> where T : Product, new()
{
private List<Action<T>> actions = new List<Action<T>>();
public T Build()
{
T product = new T();
foreach (var action in actions)
{
action(product);
}
return product;
}
public void Configure(Action<T> action)
{
actions.Add(action);
}
}
public static class ProductExtensions
{
public static ProductBuilder<T> WithPrice<T>(this ProductBuilder<T> builder, decimal price)
where T : Product
{
builder.Configure(product => product.Price = price);
return builder;
}
public static ProductBuilder<T> WithService<T>(this ProductBuilder<T> builder, string service)
where T : ServiceProduct
{
builder.Configure(product => product.Service = service);
return builder;
}
}