From 0268776110ea8c0672041e1964f1cf6ce3b9bf37 Mon Sep 17 00:00:00 2001 From: Claudiu Farcas Date: Sat, 17 Apr 2021 10:05:04 +0300 Subject: [PATCH] WIP creational patterns --- Program.cs | 22 +- .../abstract_factory/AbstractFactorySample.cs | 121 ++++++++++ creational/builder/FacadeBuilderSample.cs | 109 +++++++++ creational/builder/FluentBuilderSample.cs | 125 +++++++++++ creational/builder/FunctionalBuilderSample.cs | 80 +++++++ creational/factory/FactorySample.cs | 206 ++++++++++++++++++ 6 files changed, 661 insertions(+), 2 deletions(-) create mode 100644 creational/abstract_factory/AbstractFactorySample.cs create mode 100644 creational/builder/FacadeBuilderSample.cs create mode 100644 creational/builder/FluentBuilderSample.cs create mode 100644 creational/builder/FunctionalBuilderSample.cs create mode 100644 creational/factory/FactorySample.cs diff --git a/Program.cs b/Program.cs index 368b7bc..702a022 100644 --- a/Program.cs +++ b/Program.cs @@ -1,12 +1,30 @@ using System; +using System.Threading.Tasks; +using design_patterns.creational.abstract_factory; +using design_patterns.creational.fluentbuilder; +using design_patterns.creational.functionalbuilder; +using design_patterns.creational.factory; +using design_patterns.creational.facadebuilder; namespace design_patterns { class Program { - static void Main(string[] args) + static async Task Main(string[] args) { - Console.WriteLine("Hello World!"); + try + { + // creational + // await FactorySample.Run(); + // await AbstractFactorySample.Run(); + // await FluentBuilderSample.Run(); + // await FunctionalBuilderSample.Run(); + await FacadeBuilderSample.Run(); + } + catch (System.Exception ex) + { + Console.WriteLine($"EXCEPTION: {ex}"); + } } } } diff --git a/creational/abstract_factory/AbstractFactorySample.cs b/creational/abstract_factory/AbstractFactorySample.cs new file mode 100644 index 0000000..f303cc1 --- /dev/null +++ b/creational/abstract_factory/AbstractFactorySample.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace design_patterns.creational.abstract_factory +{ + /// + /// Abstract Factory is a creational design pattern, + /// which solves the problem of creating entire product families + /// without specifying their concrete classes. + /// Complexity: 2/3 + /// Popularity: 3/3 + /// Usage examples: + /// The Abstract Factory pattern is pretty common in C# code. + /// Many frameworks and libraries use it to provide a way + /// to extend and customize their standard components. + /// Identification: + /// The pattern is easy to recognize by methods, + /// which return a factory object. Then, the factory is used + /// for creating specific sub-components. + /// + public class AbstractFactorySample + { + public static async Task Run() + { + Console.WriteLine("Creational - Abstract Factory"); + + var abstFactory = new WorkshopFactory(); + var p1 = abstFactory.GetProduct("a"); + Console.WriteLine($"product is of type {p1.GetType()}"); + p1.ShowDetails(); + var p2 = abstFactory.GetProduct("b"); + Console.WriteLine($"product is of type {p2.GetType()}"); + p2.ShowDetails(); + try + { + var p3 = abstFactory.GetProduct("c"); + p3.ShowDetails(); + } + catch (System.Exception ex) + { + if(ex is ArgumentException exa && exa.Message.Contains("not available in existing factories")){ + Console.WriteLine($"p3 cannot be create because there is no factory of this type."); + }else{ + throw; + } + } + + } + } + + public interface IProduct + { + string Details {get;set;} + void ShowDetails(); + } + + public class ProductA : IProduct + { + public string Details { get; set; } + + public void ShowDetails() + { + Console.WriteLine($"ProductA details: {this.Details}"); + } + } + + public class ProductB : IProduct + { + public string Details { get; set; } + + public void ShowDetails() + { + Console.WriteLine($"ProductB details: {this.Details}"); + } + } + + public interface IProductFactory { + IProduct GetProduct(); + } + + public class ProductAFactory : IProductFactory + { + public IProduct GetProduct() + { + return new ProductA() { + Details = "ProductA " + Guid.NewGuid() + }; + } + } + + public class ProductBFactory : IProductFactory + { + public IProduct GetProduct() + { + return new ProductB() { + Details = "ProductB " + Guid.NewGuid() + }; + } + } + + public class WorkshopFactory + { + private Dictionary factories = new Dictionary() { + {"a", new ProductAFactory()}, + {"b", new ProductBFactory()} + }; + public IProduct GetProduct(string productType) + { + if(factories.TryGetValue(productType, out var factory)){ + var concrete = factory.GetProduct(); + // can do extra + concrete.Details += " " + Guid.NewGuid(); + return concrete; + } + else { + throw new ArgumentException($"product type {productType} not available in existing factories."); + } + } + } +} \ No newline at end of file diff --git a/creational/builder/FacadeBuilderSample.cs b/creational/builder/FacadeBuilderSample.cs new file mode 100644 index 0000000..f8aa5f6 --- /dev/null +++ b/creational/builder/FacadeBuilderSample.cs @@ -0,0 +1,109 @@ +using System; +using System.Threading.Tasks; + +namespace design_patterns.creational.facadebuilder +{ + /// + /// this sample covers the case when we have multiple builders + /// for the same class, that we want to chain + /// + public class FacadeBuilderSample + { + public static async Task Run() { + Console.WriteLine("Creational - Facade Builder"); + + var product = new ProductBuilder() + .Info + .WithName("New Product") + .WithDetails("product details") + .Price + .WithPrice(67.12m) + .WithDiscountLevel1(4) + .WithDiscountLevel2(12) + .Build(); + + Console.WriteLine("product: " + product.ToString()); + + // sample with implicit operator casting + Product product2 = new ProductBuilder() + .Info + .WithName("New Product 2") + .WithDetails("product details 2") + .Price + .WithPrice(23.11m) + .WithDiscountLevel1(5) + .WithDiscountLevel2(10); + + Console.WriteLine("product: " + product2.ToString()); + } + } + + public class Product { + // first section handled by a builder + public string Name { get; set; } + public string Details { get; set; } + + // second section handled by another builder + public decimal Price { get; set; } + public int DiscountPercentLevel1 { get; set; } + public int DiscountPercentLevel2 { get; set; } + + + public override string ToString() + { + return $"{Name} : {Details} : {Price} : {DiscountPercentLevel1} : {DiscountPercentLevel2}"; + } + } + + // this acts as a facade + public class ProductBuilder + { + protected Product instance = new Product(); + + public ProductInfoBuilder Info => new ProductInfoBuilder(instance); + public ProductPriceBuilder Price => new ProductPriceBuilder(instance); + + public Product Build() { + return instance; + } + + public static implicit operator Product(ProductBuilder pb) => pb.instance; + } + + public class ProductInfoBuilder : ProductBuilder { + public ProductInfoBuilder(Product instance) { + this.instance = instance; + } + + public ProductInfoBuilder WithName(string name) { + this.instance.Name = name; + return this; + } + + public ProductInfoBuilder WithDetails(string details) { + this.instance.Details = details; + return this; + } + } + + public class ProductPriceBuilder : ProductBuilder { + public ProductPriceBuilder(Product instance) { + this.instance = instance; + } + + public ProductPriceBuilder WithPrice(decimal price) { + this.instance.Price = price; + return this; + } + + public ProductPriceBuilder WithDiscountLevel1(int discount) { + this.instance.DiscountPercentLevel1 = discount; + return this; + } + + public ProductPriceBuilder WithDiscountLevel2(int discount) { + this.instance.DiscountPercentLevel2 = discount; + return this; + } + } +} \ No newline at end of file diff --git a/creational/builder/FluentBuilderSample.cs b/creational/builder/FluentBuilderSample.cs new file mode 100644 index 0000000..a9357d9 --- /dev/null +++ b/creational/builder/FluentBuilderSample.cs @@ -0,0 +1,125 @@ +using System; +using System.Threading.Tasks; + +namespace design_patterns.creational.fluentbuilder +{ + /// + /// Builder is a creational design pattern that + /// lets you construct complex objects step by step. + /// The pattern allows you to produce different types + /// and representations of an object using the same construction code. + /// + /// Fluent builder - limited as it cannot be extended via inheritance + /// + public class FluentBuilderSample + { + public static async Task Run() { + Console.WriteLine("Creational - Fluent Builder"); + + var product = ProductBuilder + .CreateProduct() + .WithName("New Product") + .WithCategory("Best category") + .WithAge(30) + .WithPrice(45.22m) + .Build(); + + Console.WriteLine("product: " + product.ToString()); + } + } + + public class Product { + public int Age { get; set; } + public decimal Price { get; set; } + public string ProductName { get; set; } + public string Category { get; set; } + public string Info { get; internal set; } + + public override string ToString() + { + return $"{ProductName} : {Category} : {Age} : {Price}"; + } + } + + public interface IProductBuilder : + IProductName, + IProductAge, + IProductPrice, + IProductCategory { + Product Build(); + } + public interface IProductName { + IProductBuilder WithName(string productName); + } + + public interface IProductAge { + IProductBuilder WithAge(int productAge); + } + + public interface IProductPrice { + IProductBuilder WithPrice(decimal productPrice); + } + + public interface IProductCategory { + IProductBuilder WithCategory(string productCategory); + } + + public class ProductBuilderWithInfo : ProductBuilder { + + IProductBuilder WithInfo(string info){ + this.instance.Info = info; + return this; + } + + } + + + public abstract class ProductBuilderBase { + + } + + public class ProductBuilder : + IProductBuilder + { + // set as protected to favor inheritance for extensibility of builder class + protected Product instance; + + protected ProductBuilder() {} + public static ProductBuilder CreateProduct() + { + var pb = new ProductBuilder() { + instance = new Product() + }; + return pb; + } + + public Product Build() + { + return instance; + } + + public IProductBuilder WithAge(int productAge) + { + this.instance.Age = productAge; + return this; + } + + public IProductBuilder WithCategory(string productCategory) + { + this.instance.Category = productCategory; + return this; + } + + public IProductBuilder WithName(string productName) + { + this.instance.ProductName = productName; + return this; + } + + public IProductBuilder WithPrice(decimal productPrice) + { + this.instance.Price = productPrice; + return this; + } + } +} \ No newline at end of file diff --git a/creational/builder/FunctionalBuilderSample.cs b/creational/builder/FunctionalBuilderSample.cs new file mode 100644 index 0000000..0bf398e --- /dev/null +++ b/creational/builder/FunctionalBuilderSample.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace design_patterns.creational.functionalbuilder +{ + /// + /// Builder is a creational design pattern that + /// lets you construct complex objects step by step. + /// The pattern allows you to produce different types + /// and representations of an object using the same construction code. + /// + /// Functional builder - allow builder extension via extended methods + /// + public class FunctionalBuilderSample + { + public static async Task Run() { + Console.WriteLine("Creational - Functional Builder"); + + var product = new ProductBuilder() + .WithName("New Product") + .WithCategory("Best category") + .WithAge(30) + .WithPrice(45.22m) + .Build(); + + Console.WriteLine("product: " + product.ToString()); + } + } + + public abstract class FunctionalBuilderBase + where SELF : FunctionalBuilderBase + where T : new() + { + private readonly List> actions = new List>(); + + public SELF Apply(Action action) { + actions.Add(t => { + action(t); + return t; + }); + return this as SELF; + } + + public T Build() { + return actions.Aggregate(new T(), (t, f) => f(t)); + } + + } + public sealed class ProductBuilder : FunctionalBuilderBase { + public ProductBuilder WithName(string name) => + Apply(p => p.ProductName = name); + } + + /// + /// allow extending the builder + /// + public static class ProductBuilderExtensions { + public static ProductBuilder WithAge(this ProductBuilder builder, int age) => + builder.Apply(p => p.Age = age); + public static ProductBuilder WithCategory(this ProductBuilder builder, string category) => + builder.Apply(p => p.Category = category); + public static ProductBuilder WithPrice(this ProductBuilder builder, decimal price) => + builder.Apply(p => p.Price = price); + } + + public class Product { + public int Age { get; set; } + public decimal Price { get; set; } + public string ProductName { get; set; } + public string Category { get; set; } + public string Info { get; internal set; } + + public override string ToString() + { + return $"{ProductName} : {Category} : {Age} : {Price}"; + } + } +} \ No newline at end of file diff --git a/creational/factory/FactorySample.cs b/creational/factory/FactorySample.cs new file mode 100644 index 0000000..f631ce3 --- /dev/null +++ b/creational/factory/FactorySample.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading.Tasks; + +namespace design_patterns.creational.factory +{ + /// + /// Factory method + /// provides an interface for creating objects in a superclass, + /// but allows subclasses to alter the type of objects + /// that will be created. + /// + public class FactorySample + { + public static async Task Run() + { + Console.WriteLine("Creational - Factory"); + + // not allowed to create new Product + // var pA = new ProductA(); + + var productFactory = new ProductFactory(); + + var productA = productFactory.CreateProduct(); + Console.WriteLine($"productA is of type { productA.GetType()}"); + productA.ShowDetails(); + + var productB = productFactory.CreateProduct(); + Console.WriteLine($"productB is of type { productB.GetType()}"); + productB.ShowDetails(); + + var productAasync = await productFactory.CreateProductAsync(); + Console.WriteLine($"productAasync is of type { productAasync.GetType()}"); + productAasync.ShowDetails(); + + var productBasync = await productFactory.CreateProductAsync(); + Console.WriteLine($"productBasync is of type { productBasync.GetType()}"); + productBasync.ShowDetails(); + + var productC = productFactory.CreateProduct(); + productC.ShowDetails(); + var newProductC = new ProductCFactory().CreateProductC("my extra C product"); + newProductC.ShowDetails(); + } + } + + public interface IProduct + { + void Init(); + Task InitAsync(); + + void ShowDetails(); + string Details {get;set;} + } + + public abstract class ProductBase : IProduct + { + public string Details { get ; set; } + + public virtual void Init() + { + // default implementation + this.Details = $"Default product detail {Guid.NewGuid()}"; + } + + public virtual Task InitAsync() + { + // default implementation + return Task.FromResult(true); + } + + public virtual void ShowDetails() + { + // default implementation + Console.WriteLine($"Default show details for {this.GetType()}: {this.Details}"); + } + } + public class ProductA : ProductBase { + // protect constructor and prevent user to create it + private ProductA() {} + + public override void Init() + { + base.Init(); + // fill details from some other source + this.Details = $"Details {Guid.NewGuid()}"; + } + + public override async Task InitAsync() + { + this.Init(); + // do some other async initialization + // var x = await Do Some stuff + await Task.Delay(10); + } + + public override void ShowDetails() + { + Console.WriteLine("ProductA detail: " + this.Details); + } + } + + public class ProductB : ProductBase { + // protect constructor and prevent user to create it + private ProductB() {} + + public override void Init() + { + base.Init(); + // fill details from some other source + this.Details = $"Details {Guid.NewGuid()}"; + } + + public override async Task InitAsync() + { + this.Init(); + // do some other async initialization + // var x = await Do Some stuff + await Task.Delay(10); + } + public override void ShowDetails() + { + Console.WriteLine("ProductB detail: " + this.Details); + } + } + + public class ProductC : ProductBase + { + // protect constructor and prevent user to create it + private ProductC() {} + } + + /// + /// Custom factory implemented just for ProductC + /// + public class ProductCFactory : ProductFactory { + + public IProduct CreateProductC(string extraDetail) + { + var concreteC = base.CreateProduct(); + concreteC.Details = "Product from custom factory " + extraDetail; + return concreteC; + } + } + + public class ProductFactory : FactoryBase + { + public virtual T CreateProduct() where T : class, IProduct { + var concrete = CreateInstance(); + concrete?.Init(); + return concrete; + } + + public virtual async Task CreateProductAsync() where T : class, IProduct { + var concrete = CreateInstance(); + await concrete?.InitAsync(); + return concrete; + } + } + + public class FactoryBase + { + protected T CreateInstance() where T : class { + // use of the following approach: activator of compiled expression + // var concrete = CreateInstanceWithActivator(); + var concrete = CreateInstanceWithLambda(); + return concrete; + } + + /// + /// Create object instance with Activator + /// + /// type of created instance + private T CreateInstanceWithActivator() where T : class + { + var concrete = Activator.CreateInstance(typeof(T), true) as T; + return concrete; + } + + // used for caching lambda compilation expressions to reduce reflection overhead + private static Dictionary> activators = new Dictionary>(); + /// + /// Create object instance with lambda compiled expression + /// + /// type of created instance + private T CreateInstanceWithLambda() where T : class + { + var type = typeof(T); + Func activator; + if(!activators.TryGetValue(type, out activator)){ + var ctor = type.GetConstructor( + BindingFlags.Instance | BindingFlags.CreateInstance | + BindingFlags.NonPublic, + null, new Type[] { }, null); + + var ctorExpression = Expression.New(ctor); + activator = Expression.Lambda>(ctorExpression).Compile(); + activators.Add(type, activator); + } + var concrete = activator() as T; + return concrete; + } + } +} \ No newline at end of file