Introduction ๐
Design Pattern ๐งฑ
A design pattern is a solution that can be repeated/use every time that a specific problem/scenario occurs in the software design. The concept behind these solutions is independent of the programming language.
If we make an analogy with food, there are lots of different ways (recipes) to make French macarons, each one of them will create a different product with a different quality, so in this case, why not get the knowledge of the most experienced chef and follow his/her recipe and steps every time we need to make a French macaron?
This is the idea behind design patterns, to follow the best practices used by โchefsโ (Gang of Four (GoF)) to tackle a specific problem.
Gang of Four (GoF) ๐
Gang of Four is the term that represents the four authors (Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides) of the well-known book in the software industry: โDesign Patterns: Elements of Reusable Object-Oriented Softwareโ. This book was originally published in 1994 and introduced 23 solutions for commonly occurring design problems.
Gang of Four is the best book ever written on object-oriented design - possibly of any style of design. This book has been enormously influential on the software industry - just look at the Java and .NET libraries which are crawling with GOF patterns. Martin Fowler (software developer, author and international public speaker on software development)
Design Categories โ๏ธ
The 23 GoF Design Patterns are broken into three main categories:
โ๏ธBehavioral: Describes how objects interact/communicate between themselves. โ๏ธCreational: Describes how to instantiate an object without large and complex. โ๏ธStructural: Describes how objects/classes are composed to form larger structures.
In this article, letโs analyze the design patterns that are part of the creational category.
Creational Patterns
- Abstract Factory: Creates an instance of several families of classes
- Builder: Separates object construction from its representation
- Factory Method: Creates an instance of several derived classes
- Prototype: Allows to copy/clone a fully initialized instance
- Singleton: A class of which only a single instance can exist
Source Code ๐ฒ
github.com/VictorLins/DesignPatterns
Creational Patterns
1. Abstract Factory
Objective ๐ฏ
Allow the creation of families of related or dependents objects without specifying their concrete classes.
UML ๐
Participants ๐
โข AbstractFactory:
- Declares an interface with methods that create abstract products
โข ConcreteFactory:
- Implements the necessary operations to create product objects
โข AbstractProduct:
- Declares an interface for a specific type of product
โข Product:
- Implements the AbstractProduct interface
โข Client:
- Uses a concrete implementation of AbstractFactory to create concrete implementations of AbstractProducts
Sample Code ๐ฎ
Structural Example ๐๏ธ
public static class AbstractFactoryStructural
{
public static void Execute()
{
AbstractFactory lAbstractFactory = new ConcreteFactory1();
Client lClient = new Client(lAbstractFactory);
lClient.Execute();
lAbstractFactory = new ConcreteFactory2();
lClient = new Client(lAbstractFactory);
lClient.Execute();
}
}
public abstract class AbstractProductA
{
}
public abstract class AbstractProductB
{
public abstract void Interact(AbstractProductA prAbstractProductA);
}
public class ProductA1 : AbstractProductA
{
}
public class ProductA2 : AbstractProductA
{
}
public class ProductB1 : AbstractProductB
{
public override void Interact(AbstractProductA prAbstractProductA)
{
Console.WriteLine(this.GetType().Name + " is interacting with " + prAbstractProductA.GetType().Name);
}
}
public class ProductB2 : AbstractProductB
{
public override void Interact(AbstractProductA prAbstractProductA)
{
Console.WriteLine(this.GetType().Name + " is interacting with " + prAbstractProductA.GetType().Name);
}
}
abstract public class AbstractFactory
{
public abstract AbstractProductA CreateProductA();
public abstract AbstractProductB CreateProductB();
}
public class ConcreteFactory1 : AbstractFactory
{
public override ProductA1 CreateProductA()
{
return new ProductA1();
}
public override ProductB1 CreateProductB()
{
return new ProductB1();
}
}
public class ConcreteFactory2 : AbstractFactory
{
public override ProductA2 CreateProductA()
{
return new ProductA2();
}
public override ProductB2 CreateProductB()
{
return new ProductB2();
}
}
public class Client
{
private AbstractFactory _AbstractFactory;
private AbstractProductA _AbstractProductA;
private AbstractProductB _AbstractProductB;
public Client(AbstractFactory prAbstractFactory)
{
_AbstractFactory = prAbstractFactory;
}
public void Execute()
{
_AbstractProductA = _AbstractFactory.CreateProductA();
Console.WriteLine(_AbstractFactory.GetType().Name + " created Product " + _AbstractProductA.GetType().Name);
_AbstractProductB = _AbstractFactory.CreateProductB();
Console.WriteLine(_AbstractFactory.GetType().Name + " created Product " + _AbstractProductB.GetType().Name);
_AbstractProductB.Interact(_AbstractProductA);
}
}
Output
Real-world Example ๐ฅ
public static class AbstractFactoryPractical
{
public static void Execute()
{
LaptopProducer lLaptopProducer = new LaptopProducer(new DellFactory());
lLaptopProducer.ProduceLaptopParts();
lLaptopProducer = new LaptopProducer(new AppleFactory());
lLaptopProducer.ProduceLaptopParts();
}
}
public abstract class Processor { }
public abstract class Motherboard { }
public abstract class Storage { }
public class DellProcessor : Processor { }
public class AppleProcessor : Processor { }
public class DellMotherboard : Motherboard { }
public class AppleMotherboard : Motherboard { }
public class DellStorage : Storage { }
public class AppleStorage : Storage { }
abstract public class LaptopFactory
{
public abstract Processor CreateProcessor();
public abstract Motherboard CreateMotherboard();
public abstract Storage CreateStorage();
}
public class DellFactory : LaptopFactory
{
public override Processor CreateProcessor()
{
Console.WriteLine(this.GetType().Name + " - Processor created");
return new DellProcessor();
}
public override Motherboard CreateMotherboard()
{
Console.WriteLine(this.GetType().Name + " - Motherboard created");
return new DellMotherboard();
}
public override Storage CreateStorage()
{
Console.WriteLine(this.GetType().Name + " - Storage created");
return new DellStorage();
}
}
public class AppleFactory : LaptopFactory
{
public override Processor CreateProcessor()
{
Console.WriteLine(this.GetType().Name + " - Processor created");
return new DellProcessor();
}
public override Motherboard CreateMotherboard()
{
Console.WriteLine(this.GetType().Name + " - Motherboard created");
return new DellMotherboard();
}
public override Storage CreateStorage()
{
Console.WriteLine(this.GetType().Name + " - Storage created");
return new DellStorage();
}
}
public class LaptopProducer
{
private LaptopFactory _LaptopFactory;
private Processor _Processor;
private Motherboard _Motherboard;
private Storage _Storage;
public LaptopProducer(LaptopFactory prLaptopFactory)
{
_LaptopFactory = prLaptopFactory;
}
public void ProduceLaptopParts()
{
_Processor = _LaptopFactory.CreateProcessor();
_Motherboard = _LaptopFactory.CreateMotherboard();
_Storage = _LaptopFactory.CreateStorage();
}
}
Output
2. Builder
Objective ๐ฏ
Simplify the construction of complex objects by creating it step by step.
UML ๐
Participants ๐
โข Builder:
- Specify an abstract interface for creating parts of a Product object
โข ConcreteBuilder:
- Constructs and assembles parts of the Product by implementing the Builder interface
- Provides an interface for retrieving the product
โข Director:
- Constructs an object using the Builder interface
โข Product:
- Represents the complex object under construction
- May include other classes to represent its parts
Sample Code ๐ฎ
Structural Example ๐๏ธ
public static class BuilderStructural
{
public static void Execute()
{
Director lDirector = new Director();
// Using Builder 1
Builder lConcreteBuilder1 = new ConcreteBuilder1(); // Define Builder
lDirector.Construct(lConcreteBuilder1); // Build Product
Product lProduct1 = lConcreteBuilder1.GetResult(); // Get Product
Console.WriteLine("Builder 1 - Product Result: ");
lProduct1.Show();
// Using Builder 2
Builder lConcreteBuilder2 = new ConcreteBuilder2(); // Define Builder
lDirector.Construct(lConcreteBuilder2); // Build Product
Product lProduct2 = lConcreteBuilder2.GetResult(); // Get Product
Console.WriteLine("\n\rBuilder 2 - Product Result: ");
lProduct2.Show();
}
}
public class Director
{
public void Construct(Builder prBuilder)
{
// Builder can use a complex series of steps
prBuilder.BuildPartOne();
prBuilder.BuildPartTwo();
}
}
public abstract class Builder
{
protected Product _Product = new Product();
public abstract void BuildPartOne();
public abstract void BuildPartTwo();
public abstract Product GetResult();
}
public class ConcreteBuilder1 : Builder
{
public override void BuildPartOne()
{
_Product.Add("Part A");
}
public override void BuildPartTwo()
{
_Product.Add("Part B");
}
public override Product GetResult()
{
return _Product;
}
}
public class ConcreteBuilder2 : Builder
{
public override void BuildPartOne()
{
_Product.Add("Part C");
}
public override void BuildPartTwo()
{
_Product.Add("Part D");
}
public override Product GetResult()
{
return _Product;
}
}
public class Product
{
public List<string> _Parts = new List<string>();
public void Add(string prPart)
{
_Parts.Add(prPart);
}
public void Show()
{
Console.WriteLine("Product created with the following parts: " + String.Join(", ", _Parts));
}
}
Output
Real-world Example ๐ฅ
public static class BuilderPractical
{
public static void Execute()
{
VehicleManifacturer lVehicleManifacturer = new VehicleManifacturer();
// Building MotorCycle
VehicleBuilder lVehicleBuilder = new MotorCycleBuilder();
lVehicleManifacturer.Construct(lVehicleBuilder);
Vehicle lMotorCycle = lVehicleBuilder.GetVehicle();
Console.WriteLine("MotorCycle Builder - Product Result: ");
lMotorCycle.GetDetails();
// Building Car
lVehicleBuilder = new CarBuilder();
lVehicleManifacturer.Construct(lVehicleBuilder);
Vehicle lCar = lVehicleBuilder.GetVehicle();
Console.WriteLine("\n\rCar Builder - Product Result: ");
lCar.GetDetails();
}
}
public class VehicleManifacturer
{
public void Construct(VehicleBuilder prVehicleBuilder)
{
prVehicleBuilder.BuildFrame();
prVehicleBuilder.BuildEngine();
prVehicleBuilder.BuildWheels();
prVehicleBuilder.BuildDoors();
}
}
public abstract class VehicleBuilder
{
protected Vehicle _Vehicle;
public abstract void BuildFrame();
public abstract void BuildEngine();
public abstract void BuildWheels();
public abstract void BuildDoors();
public Vehicle GetVehicle()
{
return _Vehicle;
}
}
public class MotorCycleBuilder : VehicleBuilder
{
public MotorCycleBuilder()
{
_Vehicle = new Vehicle("MotorCycle");
}
public override void BuildFrame()
{
_Vehicle.Frame = "MotorCycle Frame";
}
public override void BuildEngine()
{
_Vehicle.Engine = "500 cc";
}
public override void BuildWheels()
{
_Vehicle.Wheels = 2;
}
public override void BuildDoors()
{
_Vehicle.Doors = 0;
}
}
public class CarBuilder : VehicleBuilder
{
public CarBuilder()
{
_Vehicle = new Vehicle("Car");
}
public override void BuildFrame()
{
_Vehicle.Frame = "Car Frame";
}
public override void BuildEngine()
{
_Vehicle.Engine = "2500 cc";
}
public override void BuildWheels()
{
_Vehicle.Wheels = 4;
}
public override void BuildDoors()
{
_Vehicle.Doors = 4;
}
}
public class Vehicle
{
public string VehicleType { get; set; }
public string Frame { get; set; }
public string Engine { get; set; }
public int Wheels { get; set; }
public int Doors { get; set; }
public Vehicle(string prVehicleType)
{
VehicleType = prVehicleType;
}
public void GetDetails()
{
Console.WriteLine($"Engine: {Engine} | Frame: {Frame} | Wheels: {Wheels} | Doors: {Doors}");
}
}
Output
3. Factory Method
Objective ๐ฏ
Define an interface for creating an object, letting to the subclasses (a.k.a. factory) decide which class (a.k.a. product) will instantiate, by doing this, it will remove the clientโs dependency on concrete classes making it unaware of the object instantiation.
Notes ๐
- Allows to create an instance based on the inputs provided.
- Allows to handle multiple factories.
- Objects returned by a factory method are often referred to as products.
- Products must also implement an abstract class or interface.
UML ๐
Participants ๐
โข IProduct:
- Defines the interface of the objects that will be handled by the factory.
โข ProductA, ProductB:
- Implements the IProduct interface.
โข IFactory:
- Defines the Factory Method that will return a concrete implementation of the interface IProduct.
โข FactoryA, FactoryB:
- Implements the IProduct interface
- Implements the Factory Method to return an instance of a concrete class of IProduct.
Sample Code ๐ฎ
Structural Example ๐๏ธ
public static class FactoryMethodStructural
{
public static void Execute()
{
IFactory lFactory = new FactoryA();
IProduct lProduct = lFactory.FactoryMethod("One");
lProduct.MethodTest();
lProduct = lFactory.FactoryMethod("Two");
lProduct.MethodTest();
lFactory = new FactoryB();
lProduct = lFactory.FactoryMethod("One");
lProduct.MethodTest();
lProduct = lFactory.FactoryMethod("Two");
lProduct.MethodTest();
}
}
public interface IProduct
{
void MethodTest();
}
public class ProductAOne : IProduct
{
public void MethodTest() { Console.WriteLine("Product A One - Executing MethodTest"); }
}
public class ProductATwo : IProduct
{
public void MethodTest() { Console.WriteLine("Product A Two - Executing MethodTest"); }
}
public class ProductBOne : IProduct
{
public void MethodTest() { Console.WriteLine("Product B One - Executing MethodTest"); }
}
public class ProductBTwo : IProduct
{
public void MethodTest() { Console.WriteLine("Product B Two - Executing MethodTest"); }
}
public interface IFactory
{
IProduct FactoryMethod(String prType);
}
public class FactoryA : IFactory
{
public IProduct FactoryMethod(String prType)
{
if (prType == "One")
return new ProductAOne();
else
return new ProductATwo();
}
}
public class FactoryB : IFactory
{
public IProduct FactoryMethod(String prType)
{
if (prType == "One")
return new ProductBOne();
else
return new ProductBTwo();
}
}
Output
Real-world Example ๐ฅ
public static class FactoryMethodPractical
{
public static void Execute()
{
ICellphoneFactory lCellphoneFactory = new SamsungFactory();
ICellphone lCellphone = lCellphoneFactory.GetCellphone("GalaxyS22");
Console.WriteLine(lCellphone.GetReleasedYear());
lCellphone = lCellphoneFactory.GetCellphone("GalaxyS20");
Console.WriteLine(lCellphone.GetReleasedYear());
lCellphoneFactory = new IPhoneFactory();
lCellphone = lCellphoneFactory.GetCellphone("IPhone13");
Console.WriteLine(lCellphone.GetReleasedYear());
lCellphone = lCellphoneFactory.GetCellphone("IPhone12");
Console.WriteLine(lCellphone.GetReleasedYear());
}
}
public interface ICellphone
{
string GetReleasedYear();
}
public class GalaxyS22 : ICellphone
{
public string GetReleasedYear() { return "GalaxyS22 - Released Year: 2022"; }
}
public class GalaxyS20 : ICellphone
{
public string GetReleasedYear() { return "GalaxyS20 - Released Year: 2020"; }
}
public class IPhone13 : ICellphone
{
public string GetReleasedYear() { return "IPhone13 - Released Year: 2021"; }
}
public class IPhone12 : ICellphone
{
public string GetReleasedYear() { return "IPhone12 - Released Year: 2020"; }
}
public interface ICellphoneFactory
{
ICellphone GetCellphone(string prModel);
}
public class SamsungFactory : ICellphoneFactory
{
public ICellphone GetCellphone(string prModel)
{
if (prModel == "GalaxyS22")
return new GalaxyS22();
else
return new GalaxyS20();
}
}
public class IPhoneFactory : ICellphoneFactory
{
public ICellphone GetCellphone(string prModel)
{
if (prModel == "IPhone13")
return new IPhone13();
else
return new IPhone12();
}
}
Output
4. Prototype
Objective ๐ฏ
Allow to create an object from cloning another object (Prototype), to hide the complexity of making new instances and to avoid costly operations.
Notes ๐
โข When 2 objects have the same reference it means that they are sharing the same space in memory and any change in one object will reflect in the other.
โข Normal Copy process preserves the reference of the original object including the objects inside it.
โข Shallow Copy process does not preserve the reference of the original object but keep the reference of the objects inside it.
โข Deep Copy process does not preserve the reference of the original object or the reference of the objects inside it.
UML ๐
Participants ๐
โข Prototype:
- Declares an interface for cloning itself
โข ConcretePrototype:
- Implements an operation for cloning itself
โข Client:
- Creates new object by asking a prototype object to clone itself
Sample Code ๐ฎ
Structural Example ๐๏ธ
public static class PrototypeStructural
{
public static void Execute()
{
ConcretePrototype1 lConcretePrototype1 = new ConcretePrototype1("1");
ConcretePrototype1 lCloneConcretePrototype = (ConcretePrototype1)lConcretePrototype1.Clone();
Console.WriteLine($"Cloned {lConcretePrototype1.GetType().Name}");
ConcretePrototype2 lConcretePrototype2 = new ConcretePrototype2("2");
ConcretePrototype2 lCloneConcretePrototype2 = (ConcretePrototype2)lConcretePrototype2.Clone();
Console.WriteLine($"Cloned {lCloneConcretePrototype2.GetType().Name}");
}
}
public abstract class Prototype
{
public string _Id { get; set; }
public Prototype(string prId)
{
_Id = prId;
}
public abstract Prototype Clone();
}
public class ConcretePrototype1 : Prototype
{
public ConcretePrototype1(string prId)
: base(prId) { }
public override Prototype Clone()
{
return (Prototype)this.MemberwiseClone();
}
}
public class ConcretePrototype2 : Prototype
{
public ConcretePrototype2(string prId)
: base(prId) { }
public override Prototype Clone()
{
return (Prototype)this.MemberwiseClone();
}
}
Output
Real-world Example ๐ฅ
public static class PrototypePractical
{
public static void Execute()
{
ShapeHelper lShapeHelper = new ShapeHelper();
lShapeHelper.CreateShapes("Square", 2);
lShapeHelper.CreateShapes("Circle", 3);
lShapeHelper.CreateShapes("Triangle", 4);
Console.WriteLine($"\n\rCreated {lShapeHelper._Shapes.Count} objects in total");
}
}
public class ShapeHelper
{
public List<Shape> _Shapes = new List<Shape>();
public void CreateShapes(string prType, int prQuantity)
{
Shape lOriginalShape = null;
switch (prType)
{
case ("Circle"): lOriginalShape = new Circle("#1"); break;
case ("Square"): lOriginalShape = new Square("#1"); break;
case ("Triangle"): lOriginalShape = new Triangle("#1"); break;
default: break;
}
if (lOriginalShape != null)
{
Console.WriteLine($"โข Creating {prQuantity} {lOriginalShape.GetType().Name}s...");
Console.WriteLine($" Created object {lOriginalShape.GetType().Name} - Id: {lOriginalShape._Id}");
_Shapes.Add(lOriginalShape);
for (int i = 1; i < prQuantity; i++)
{
_Shapes.Add(lOriginalShape.Clone());
Console.WriteLine($" Clonned object {lOriginalShape.GetType().Name} - Id: {lOriginalShape._Id}");
}
}
}
}
public abstract class Shape
{
public string _Id { get; set; }
public string _Type { get; set; }
public Shape(string prId, string prType)
{
_Id = prId;
_Type = prType;
}
public virtual Shape Clone()
{
// To "Clone" an object can be done either by using Shallow Copy or Deep Copy approach
// In the below example Shallow Copy approach is used
return (Shape)this.MemberwiseClone();
}
}
public class Circle : Shape
{
public Circle(string prId)
: base(prId, "Circle") { }
}
public class Square : Shape
{
public Square(string prId)
: base(prId, "Square") { }
}
public class Triangle : Shape
{
public Triangle(string prId)
: base(prId, "Triangle") { }
}
Output
5. Singleton
Objective ๐ฏ
Ensure that a class has just a single instance by providing a global point of access to it.
UML ๐
Participants ๐
โข Singleton:
- Responsible for creating and maintaining its own unique instance
- Defines a method to let clients to access its unique instance
Sample Code ๐ฎ
Structural Example ๐๏ธ
public static class SingletonStructural
{
public static void Execute()
{
Singleton lSingleton1 = Singleton.GetInstance();
Singleton lSingleton2 = Singleton.GetInstance();
if(lSingleton1 == lSingleton2)
{
Console.WriteLine("Objects are the same instance");
Console.WriteLine("Singleton1 HashCode: " + lSingleton1.GetHashCode());
Console.WriteLine("Singleton2 HashCode: " + lSingleton2.GetHashCode());
}
}
}
public class Singleton
{
private static Singleton _Instance;
protected Singleton()
{
}
public static Singleton GetInstance()
{
if (_Instance == null)
_Instance = new Singleton();
return _Instance;
}
}
Output
Real-world Example ๐ฅ
public static class SingletonPractical
{
public static void Execute()
{
LogManager lLogManager1 = LogManager.GetInstance();
LogManager lLogManager2 = LogManager.GetInstance();
if (lLogManager1 == lLogManager2)
{
Console.WriteLine("Objects are the same instance");
Console.WriteLine("LogManager1 HashCode: " + lLogManager1.GetHashCode());
Console.WriteLine("LogManager2 HashCode: " + lLogManager2.GetHashCode());
}
}
}
public class LogManager
{
private static LogManager _Instance;
protected LogManager()
{
}
public static LogManager GetInstance()
{
if (_Instance == null)
_Instance = new LogManager();
return _Instance;
}
public void WriteLog(string prLogMessage)
{
Console.WriteLine(prLogMessage);
}
}
Output