GoF Creational Design Patterns

GoF Creational Design Patterns

Software Architecture Simplified

ยท

13 min read

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?

image.png

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)

image.png

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.

image.png

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 ๐Ÿ“

image.png

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 ๐Ÿ›๏ธ

image.png

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

image.png

Real-world Example ๐Ÿ”ฅ

image.png

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

image.png

2. Builder

Objective ๐ŸŽฏ

Simplify the construction of complex objects by creating it step by step.

UML ๐Ÿ“

image.png

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 ๐Ÿ›๏ธ

image.png

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 image.png

Real-world Example ๐Ÿ”ฅ

image.png

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

image.png

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.

image.png

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 ๐Ÿ“

image.png

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 ๐Ÿ›๏ธ

image.png

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

image.png

Real-world Example ๐Ÿ”ฅ

image.png

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

image.png

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.

image.png

โ€ข Shallow Copy process does not preserve the reference of the original object but keep the reference of the objects inside it.

image.png

โ€ข Deep Copy process does not preserve the reference of the original object or the reference of the objects inside it.

image.png

UML ๐Ÿ“

image.png

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 ๐Ÿ›๏ธ

image.png

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

image.png

Real-world Example ๐Ÿ”ฅ

image.png

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

image.png

5. Singleton

Objective ๐ŸŽฏ

Ensure that a class has just a single instance by providing a global point of access to it.

UML ๐Ÿ“

image.png

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 ๐Ÿ›๏ธ

image.png

 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

image.png

Real-world Example ๐Ÿ”ฅ

image.png

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

image.png

Did you find this article valuable?

Support Victor Lins by becoming a sponsor. Any amount is appreciated!

ย