Skip to content

Instantly share code, notes, and snippets.

@boca
Created September 29, 2016 19:48
Show Gist options
  • Save boca/7ad4ae836a5515afbc87e0e585024222 to your computer and use it in GitHub Desktop.
Save boca/7ad4ae836a5515afbc87e0e585024222 to your computer and use it in GitHub Desktop.
The Curiosly Recursive Generic Pattern
void Main()
{}
public class Manufacturer
{
public Manufacturer(string id, string name) {Id = id;Name = name;}
public string Id { get; set; }
public string Name { get; set; }
}
public class Product
{
public string Sku { get; set; }
public Manufacturer Manufacturer { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
void Main()
{
}
public class ProductBuilder
{
protected Manufacturer manufacturer;
protected string sku;
protected string name;
protected double price;
public ProductBuilder ManufacturedBy(Manufacturer aManufacturer) => Fluent(() => manufacturer = aManufacturer);
public ProductBuilder WithSku(string aSku) => Fluent(() => sku = aSku);
public ProductBuilder Named(string aName) => Fluent(() => name = aName);
public ProductBuilder PricedAt(double aPrice) => Fluent(() => price = aPrice);
public Product Build() => new Product { Manufacturer = manufacturer, Sku = sku, Name = name, Price = price };
public void Save()
{
if (manufacturer == null) throw new InvalidOperationException("Manufacturer should be set because foreign keys");
DoSave(Build());
}
protected ProductBuilder Fluent(Action a) => this.Fluently(a);
protected void DoSave(Product product) { product.Dump(); }
}
public static class FluentExtensions
{
public static T Fluently<T>(this T t, Action a) { a(); return t; }
}
public class Manufacturer
{
public Manufacturer(string id, string name) { Id = id; Name = name; }
public string Id { get; set; }
public string Name { get; set; }
}
public class Product
{
public string Sku { get; set; }
public Manufacturer Manufacturer { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
void Main()
{
var dell = new Manufacturer("m001", "DELL");
new ProductBuilder()
.Build()
.Dump();
new ProductBuilder()
.WithSku("001")
.Build()
.Dump();
new ProductBuilder()
.ManufacturedBy(dell)
.Save();
new ProductBuilder()
.ManufacturedBy(dell)
.WithSku("001")
.Named("Xps laptop")
.PricedAt(499.99)
.Save();
//we want the compiler to tell us that the next line is invalid
new ProductBuilder().Save();
}
public class ProductBuilder
{
protected Manufacturer manufacturer;
protected string sku;
protected string name;
protected double price;
public ProductBuilder ManufacturedBy(Manufacturer aManufacturer) => Fluent(() => manufacturer = aManufacturer);
public ProductBuilder WithSku(string aSku) => Fluent(() => sku = aSku);
public ProductBuilder Named(string aName) => Fluent(() => name = aName);
public ProductBuilder PricedAt(double aPrice) => Fluent(() => price = aPrice);
public Product Build() => new Product { Manufacturer = manufacturer, Sku = sku, Name = name, Price = price };
public void Save()
{
if (manufacturer == null) throw new InvalidOperationException("Manufacturer should be set because foreign keys");
DoSave(Build());
}
protected ProductBuilder Fluent(Action a) => this.Fluently(a);
protected void DoSave(Product product) { product.Dump(); }
}
public static class FluentExtensions
{
public static T Fluently<T>(this T t, Action a) { a(); return t; }
}
public class Manufacturer
{
public Manufacturer(string id, string name) { Id = id; Name = name; }
public string Id { get; set; }
public string Name { get; set; }
}
public class Product
{
public string Sku { get; set; }
public Manufacturer Manufacturer { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
void Main()
{
var dell = new Manufacturer("m001", "DELL");
new ProductBuilder()
.Build()
.Dump();
new ProductBuilder()
.WithSku("001")
.Build()
.Dump();
//This doesn't work anymore since only ProductSavers can save
new ProductBuilder()
.ManufacturedBy(dell)
.Save();
//This doesn't work anymore since only ProductSavers can save
new ProductBuilder()
.ManufacturedBy(dell)
.WithSku("001")
.Named("Xps laptop")
.PricedAt(499.99)
.Save();
//This doesn't work anymore since only ProductSavers can save
new ProductBuilder().Save();
}
public class ProductBuilder
{
protected Manufacturer manufacturer;
protected string sku;
protected string name;
protected double price;
public ProductBuilder ManufacturedBy(Manufacturer aManufacturer) => Fluent(() => manufacturer = aManufacturer);
public ProductBuilder WithSku(string aSku) => Fluent(() => sku = aSku);
public ProductBuilder Named(string aName) => Fluent(() => name = aName);
public ProductBuilder PricedAt(double aPrice) => Fluent(() => price = aPrice);
public Product Build() => new Product { Manufacturer = manufacturer, Sku = sku, Name = name, Price = price };
protected ProductBuilder Fluent(Action a) => this.Fluent(a);
protected void DoSave(Product product) { product.Dump(); }
}
public class ProductSaver : ProductBuilder
{
public ProductSaver(Manufacturer aManufacturer) { manufacturer = aManufacturer; }
public void Save() => DoSave(Build());
}
public static class FluentExtensions
{
public static T Fluently<T>(this T t, Action a) { a(); return t; }
}
public class Manufacturer
{
public Manufacturer(string id, string name) { Id = id; Name = name; }
public string Id { get; set; }
public string Name { get; set; }
}
public class Product
{
public string Sku { get; set; }
public Manufacturer Manufacturer { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
void Main()
{
var dell = new Manufacturer("m001", "DELL");
new ProductBuilder()
.Build()
.Dump();
new ProductBuilder()
.WithSku("001")
.Build()
.Dump();
new ProductSaver(dell)
.Save();
//This is invalid now, which is what we wanted
new ProductSaver().Save();
//but we broke this one too
new ProductSaver(dell)
.WithSku("001")
.Named("Xps laptop")
.PricedAt(499.99)
.Save();
}
public class ProductBuilder
{
protected Manufacturer manufacturer;
protected string sku;
protected string name;
protected double price;
public ProductBuilder ManufacturedBy(Manufacturer aManufacturer) => Fluent(() => manufacturer = aManufacturer);
public ProductBuilder WithSku(string aSku) => Fluent(() => sku = aSku);
public ProductBuilder Named(string aName) => Fluent(() => name = aName);
public ProductBuilder PricedAt(double aPrice) => Fluent(() => price = aPrice);
public Product Build() => new Product { Manufacturer = manufacturer, Sku = sku, Name = name, Price = price };
protected ProductBuilder Fluent(Action a) => this.Fluent(a);
protected void DoSave(Product product) { product.Dump(); }
}
public class ProductSaver : ProductBuilder
{
public ProductSaver(Manufacturer aManufacturer) { manufacturer = aManufacturer; }
public void Save() => DoSave(Build());
}
public static class FluentExtensions
{
public static T Fluently<T>(this T t, Action a) { a(); return t; }
}
public class Manufacturer
{
public Manufacturer(string id, string name) { Id = id; Name = name; }
public string Id { get; set; }
public string Name { get; set; }
}
public class Product
{
public string Sku { get; set; }
public Manufacturer Manufacturer { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
void Main()
{
var dell = new Manufacturer("m001", "DELL");
//product builders are broken since it's now a generic class
new ProductBuilder()
.Build()
.Dump();
//product builders are broken since it's now a generic class
new ProductBuilder()
.WithSku("001")
.Build()
.Dump();
new ProductSaver(dell)
.Save();
new ProductSaver(dell)
.WithSku("001")
.Named("Xps laptop")
.PricedAt(499.99)
.Save();
//still not valid. good
new ProductSaver().Save();
}
public class ProductBuilder<TSelf>
{
protected Manufacturer manufacturer;
protected string sku;
protected string name;
protected double price;
public TSelf ManufacturedBy(Manufacturer aManufacturer) => Fluent(() => manufacturer = aManufacturer);
public TSelf WithSku(string aSku) => Fluent(() => sku = aSku);
public TSelf Named(string aName) => Fluent(() => name = aName);
public TSelf PricedAt(double aPrice) => Fluent(() => price = aPrice);
public Product Build() => new Product { Manufacturer = manufacturer, Sku = sku, Name = name, Price = price };
protected TSelf Fluent(Action a) => this.Fluently(a);
protected void DoSave(Product product) { product.Dump(); }
}
public class ProductSaver : ProductBuilder<ProductSaver>
{
public ProductSaver(Manufacturer aManufacturer) { manufacturer = aManufacturer; }
public void Save() => DoSave(Build());
}
public static class FluentExtensions
{
public static T Fluently<T>(this T t, Action a) { a(); return t; }
}
public class Manufacturer
{
public Manufacturer(string id, string name) { Id = id; Name = name; }
public string Id { get; set; }
public string Name { get; set; }
}
public class Product
{
public string Sku { get; set; }
public Manufacturer Manufacturer { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
void Main()
{
var dell = new Manufacturer("m001", "DELL");
new ProductBuilder()
.Build()
.Dump();
new ProductBuilder()
.WithSku("001")
.Build()
.Dump();
new ProductSaver(dell)
.Save();
new ProductSaver(dell)
.WithSku("001")
.Named("Xps laptop")
.PricedAt(499.99)
.Save();
//still not valid. good
//new ProductSaver().Save();
}
public abstract class ProductBuilder<TSelf>
{
protected Manufacturer manufacturer;
protected string sku;
protected string name;
protected double price;
public TSelf ManufacturedBy(Manufacturer aManufacturer) => Fluent(() => manufacturer = aManufacturer);
public TSelf WithSku(string aSku) => Fluent(() => sku = aSku);
public TSelf Named(string aName) => Fluent(() => name = aName);
public TSelf PricedAt(double aPrice) => Fluent(() => price = aPrice);
public Product Build() => new Product { Manufacturer = manufacturer, Sku = sku, Name = name, Price = price };
protected abstract TSelf This { get; }
protected TSelf Fluent(Action a) => This.Fluently(a);
protected void DoSave(Product product) { product.Dump(); }
}
public class ProductBuilder : ProductBuilder<ProductBuilder> { protected override ProductBuilder This => this; }
public class ProductSaver : ProductBuilder<ProductSaver>
{
public ProductSaver(Manufacturer aManufacturer) { manufacturer = aManufacturer; }
public void Save() => DoSave(Build());
protected override ProductSaver This => this;
}
public static class FluentExtensions
{
public static T Fluently<T>(this T t, Action a) { a(); return t; }
}
public class Manufacturer
{
public Manufacturer(string id, string name) { Id = id; Name = name; }
public string Id { get; set; }
public string Name { get; set; }
}
public class Product
{
public string Sku { get; set; }
public Manufacturer Manufacturer { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
void Main()
{
var dell = new Manufacturer("m001", "DELL");
new ProductBuilder()
.Build()
.Dump();
new ProductBuilder()
.WithSku("001")
.Build()
.Dump();
new ProductSaver(dell)
.Save();
new ProductSaver(dell)
.WithSku("001")
.Named("Xps laptop")
.PricedAt(499.99)
.Save();
new StupidProductBuilder()
.Build();
//broken because This returns a string so it breaks our chaining
new StupidProductBuilder()
.WithSku("0")
.Build();
//still invaild. good
//new ProductSaver().Save();
}
public abstract class ProductBuilder<TSelf>
{
protected Manufacturer manufacturer;
protected string sku;
protected string name;
protected double price;
public TSelf ManufacturedBy(Manufacturer aManufacturer) => Fluent(() => manufacturer = aManufacturer);
public TSelf WithSku(string aSku) => Fluent(() => sku = aSku);
public TSelf Named(string aName) => Fluent(() => name = aName);
public TSelf PricedAt(double aPrice) => Fluent(() => price = aPrice);
public Product Build() => new Product { Manufacturer = manufacturer, Sku = sku, Name = name, Price = price };
protected abstract TSelf This { get; }
protected TSelf Fluent(Action a) => This.Fluently(a);
protected void DoSave(Product product) { product.Dump(); }
}
//This is nonsense. It's valid, but string is not what we meant by TSelf
public class StupidProductBuilder : ProductBuilder<string> { protected override string This => "stupid"; }
public class ProductBuilder : ProductBuilder<ProductBuilder> { protected override ProductBuilder This => this; }
public class ProductSaver : ProductBuilder<ProductSaver>
{
public ProductSaver(Manufacturer aManufacturer) { manufacturer = aManufacturer; }
public void Save() => DoSave(Build());
protected override ProductSaver This => this;
}
public static class FluentExtensions
{
public static T Fluently<T>(this T t, Action a) { a(); return t; }
}
public class Manufacturer
{
public Manufacturer(string id, string name) { Id = id; Name = name; }
public string Id { get; set; }
public string Name { get; set; }
}
public class Product
{
public string Sku { get; set; }
public Manufacturer Manufacturer { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
void Main()
{
var dell = new Manufacturer("m001", "DELL");
new ProductBuilder()
.Build()
.Dump();
new ProductBuilder()
.WithSku("001")
.Build()
.Dump();
new ProductSaver(dell)
.Save();
new ProductSaver(dell)
.WithSku("001")
.Named("Xps laptop")
.PricedAt(499.99)
.Save();
//still invalid. good
//new ProductSaver().Save();
}
//Curiously Recursive Template Pattern CRTP
//Curiously Recursive Generic Pattern CRGP
// this where clause is an f-bound
public abstract class ProductBuilder<TSelf> where TSelf : ProductBuilder<TSelf>
{
protected Manufacturer manufacturer;
protected string sku;
protected string name;
protected double price;
public TSelf ManufacturedBy(Manufacturer aManufacturer) => Fluent(() => manufacturer = aManufacturer);
public TSelf WithSku(string aSku) => Fluent(() => sku = aSku);
public TSelf Named(string aName) => Fluent(() => name = aName);
public TSelf PricedAt(double aPrice) => Fluent(() => price = aPrice);
public Product Build() => new Product { Manufacturer = manufacturer, Sku = sku, Name = name, Price = price };
protected abstract TSelf This { get; }
protected TSelf Fluent(Action a) => This.Fluently(a);
protected void DoSave(Product product) { product.Dump(); }
}
//No longer valid. The f-bound prevents this nonsense
//public class StupidProductBuilder: ProductBuilder<string> { protected override string This => "stupid";}
//This is what we meant to happen
public class StupidProductBuilder: ProductBuilder<StupidProductBuilder> { protected override StupidProductBuilder This => this;}
//but I still can create stupid things like this. F-bound will only carry me until here.
public class StupidProductBuilder: ProductBuilder<ProductSaver> { protected override ProductSaver This => null;}
public class ProductBuilder : ProductBuilder<ProductBuilder> { protected override ProductBuilder This => this; }
public class ProductSaver : ProductBuilder<ProductSaver>
{
public ProductSaver(Manufacturer aManufacturer) { manufacturer = aManufacturer; }
public void Save() => DoSave(Build());
protected override ProductSaver This => this;
}
public static class FluentExtensions
{
public static T Fluently<T>(this T t, Action a) { a(); return t; }
}
public class Manufacturer
{
public Manufacturer(string id, string name) { Id = id; Name = name; }
public string Id { get; set; }
public string Name { get; set; }
}
public class Product
{
public string Sku { get; set; }
public Manufacturer Manufacturer { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
void Main()
{
var dell = new Manufacturer("m001", "DELL");
new ProductBuilder()
.Build()
.Dump();
new ProductBuilder()
.WithSku("001")
.Build()
.Dump();
new ProductBuilder()
.ManufacturedBy(dell)
.Save();
new ProductBuilder()
.ManufacturedBy(dell)
.WithSku("001")
.Named("Xps laptop")
.PricedAt(499.99)
.Save();
new ProductBuilder().Save();
}
public abstract class HasManufacturer { }
public class WithManufacturer : HasManufacturer { }
public class WithoutManufacturer : HasManufacturer { }
public class ProductBuilder<T> where T : HasManufacturer
{
protected Manufacturer manufacturer;
protected string sku;
protected string name;
protected double price;
protected ProductBuilder() { }
private ProductBuilder(Manufacturer aManufacturer, string aSku, string aName, double aPrice)
{
manufacturer = aManufacturer;
sku = aSku;
name = aName;
aPrice = price;
}
public ProductBuilder<WithManufacturer> ManufacturedBy(Manufacturer aManufacturer) => new ProductBuilder<WithManufacturer>(aManufacturer, sku, name, price);
public ProductBuilder<T> WithSku(string aSku) => Fluent(() => sku = aSku);
public ProductBuilder<T> Named(string aName) => Fluent(() => name = aName);
public ProductBuilder<T> PricedAt(double aPrice) => Fluent(() => price = aPrice);
public Product Build() => new Product { Manufacturer = manufacturer, Sku = sku, Name = name, Price = price };
public void Save()
{
if (manufacturer == null) throw new InvalidOperationException("Manufacturer should be set because foreign keys");
DoSave(Build());
}
protected ProductBuilder<T> Fluent(Action a) => this.Fluently(a);
protected void DoSave(Product product) { product.Dump(); }
}
public class ProductBuilder : ProductBuilder<WithoutManufacturer> { }
public static class FluentExtensions
{
public static T Fluently<T>(this T t, Action a) { a(); return t; }
}
public class Manufacturer
{
public Manufacturer(string id, string name) { Id = id; Name = name; }
public string Id { get; set; }
public string Name { get; set; }
}
public class Product
{
public string Sku { get; set; }
public Manufacturer Manufacturer { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
void Main()
{
var dell = new Manufacturer("m001", "DELL");
new ProductBuilder()
.Build()
.Dump();
new ProductBuilder()
.WithSku("001")
.Build()
.Dump();
new ProductBuilder()
.ManufacturedBy(dell)
.Save();
new ProductBuilder()
.ManufacturedBy(dell)
.WithSku("001")
.Named("Xps laptop")
.PricedAt(499.99)
.Save();
//now is invalid
new ProductBuilder().Save();
}
public abstract class HasManufacturer { }
public class WithManufacturer : HasManufacturer { }
public class WithoutManufacturer : HasManufacturer { }
public class ProductBuilder<T> where T : HasManufacturer
{
protected Manufacturer manufacturer;
protected string sku;
protected string name;
protected double price;
protected ProductBuilder() { }
private ProductBuilder(Manufacturer aManufacturer, string aSku, string aName, double aPrice)
{
manufacturer = aManufacturer;
sku = aSku;
name = aName;
aPrice = price;
}
public ProductBuilder<WithManufacturer> ManufacturedBy(Manufacturer aManufacturer) => new ProductBuilder<WithManufacturer>(aManufacturer, sku, name, price);
public ProductBuilder<T> WithSku(string aSku) => Fluent(() => sku = aSku);
public ProductBuilder<T> Named(string aName) => Fluent(() => name = aName);
public ProductBuilder<T> PricedAt(double aPrice) => Fluent(() => price = aPrice);
public Product Build() => new Product { Manufacturer = manufacturer, Sku = sku, Name = name, Price = price };
protected ProductBuilder<T> Fluent(Action a) => this.Fluently(a);
}
public class ProductBuilder : ProductBuilder<WithoutManufacturer> { }
public static class ProductBuilderExtensions
{
public static void Save(this ProductBuilder<WithManufacturer> builder) => DoSave(builder.Build());
private static void DoSave(Product product) { product.Dump(); }
}
public static class FluentExtensions
{
public static T Fluently<T>(this T t, Action a) { a(); return t; }
}
public class Manufacturer
{
public Manufacturer(string id, string name) { Id = id; Name = name; }
public string Id { get; set; }
public string Name { get; set; }
}
public class Product
{
public string Sku { get; set; }
public Manufacturer Manufacturer { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
void Main()
{
var r1= Request
.ForAnalyticsByCountry
.WithCountry("us");
var r2= Request
.ForOmnitureDataByCountryLanguage
.WithCountry("us")
.WithLanguage("en");
var r3 =Request
.ForClickDataByLwp
.WithCountry("us")
.WithLanguage("en")
.WithSegment("gen")
.WithCustomerSet("19");
DummyDataRetriever.RetrieveDataWithFilters(r1);
DummyDataRetriever.RetrieveDataWithFilters(r2);
DummyDataRetriever.RetrieveDataWithFilters(r3);
}
public abstract class Request
{
protected Dictionary<string, object> Filters = new Dictionary<string, object>();
public static AnalyticsByCountry ForAnalyticsByCountry => new AnalyticsByCountry();
public static OmnitureDataByCountryLanguage ForOmnitureDataByCountryLanguage => new OmnitureDataByCountryLanguage();
public static ClickDataByLwp ForClickDataByLwp => new ClickDataByLwp();
public static implicit operator Dictionary<string,object>(Request r) => r.Filters;
}
public abstract class Request<T>:Request where T:Request<T>
{
protected T Fluent(Action a) => This.Fluently(a);
protected abstract T This { get; }
protected T Country (string country ) => Fluent(() => Filters["Country" ] = country );
protected T Language (string language ) => Fluent(() => Filters["Language" ] = language );
protected T Segment (string segment ) => Fluent(() => Filters["Segment" ] = segment );
protected T CustomerSet(string customerSet) => Fluent(() => Filters["CustomerSet"] = customerSet);
}
public class AnalyticsByCountry : Request<AnalyticsByCountry>
{
protected override AnalyticsByCountry This => this;
public AnalyticsByCountry WithCountry(string country) => Country(country);
}
public class OmnitureDataByCountryLanguage : Request<OmnitureDataByCountryLanguage>
{
protected override OmnitureDataByCountryLanguage This => this;
public OmnitureDataByCountryLanguage WithCountry (string country ) => Country (country) ;
public OmnitureDataByCountryLanguage WithLanguage(string language) => Language(language);
}
public class ClickDataByLwp : Request<ClickDataByLwp>
{
protected override ClickDataByLwp This => this;
public ClickDataByLwp WithCountry (string country ) => Country (country) ;
public ClickDataByLwp WithLanguage (string language) => Language (language);
public ClickDataByLwp WithSegment (string segment) => Segment (segment);
public ClickDataByLwp WithCustomerSet(string customerSet) => CustomerSet(customerSet);
}
public class DummyDataRetriever
{
public static object RetrieveDataWithFilters(Dictionary<string, object> filters) => filters.Dump();
}
public static class FluentExtensions
{
public static T Fluently<T>(this T t, Action a) { a(); return t;}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment