Factory Design Pattern

The Factory Design Pattern is a Creational Design Pattern widely used in object-oriented programming to create objects without specifying the exact class of the object that will be created. It is one of the most commonly employed design patterns in real-world applications.

As defined by the Gang of Four (GoF), a factory is an object that creates other objects. In simple words, a factory is a class with a method which generates and returns different and various objects based on the parameters it receives.

With the Factory Design Pattern, object creation details are hidden from the client. The client interacts with the newly created object with the help of a common interface. This pattern delegates the instantiation process to a factory class or method, enabling more flexible and scalable code. We’ll start by implementing a simple example, followed by real-world scenarios.

Example

Let's say you are building a system that deals with different types of shapes (Circle, Rectangle, etc…). You want to use a factory to create these shape objects based on user input or other conditions.

Step 1 - Define an IShape Interface

public interface IShape
{
    void Draw();
}

Step 2 - Implement Concrete Shapes:

public class Circle : IShape
{
    public void Draw()
    {
        Console.WriteLine("Drawing a Circle");
    }
}

public class Rectangle : IShape
{
    public void Draw()
    {
        Console.WriteLine("Drawing a Rectangle");
    }
}

Step 3 - Create a Factory Class:

public class ShapeFactory
{
    public IShape GetShape(string shapeType)
    {
        if (shapeType == null)
        {
            return null;
        }
        if (shapeType.Equals("CIRCLE"))
        {
            return new Circle();
        }
        else if (shapeType.Equals("RECTANGLE"))
        {
            return new Rectangle();
        }
        return null;
    }
}

Step 4 - Use the Factory to Get Shape Objects:

public class Program
{
    public static void Main(string[] args)
    {
        ShapeFactory shapeFactory = new  ShapeFactory();

        // Get a Circle’s object and call its Draw() method.
        IShape shape1 = shapeFactory.GetShape("CIRCLE");
        shape1.Draw();

        // Get an Rectangle’s object and call its Draw() method.
        IShape shape2 = shapeFactory.GetShape("RECTANGLE");
        shape2.Draw();
    }
}


Explanation

  • IShape Interface: This interface declares the Draw method that all shapes must implement.

  • Circle and Rectangle Classes: These are concrete implementations of the IShape interface.

  • ShapeFactory Class: This class has a method GetShape that returns an instance of a shape based on the shapeType string.

  • Program Class: In the Main method, you use the ShapeFactory to get instances of Circle and Rectangle and call their Draw methods.


Benefits

  • Encapsulation: The ShapeFactory encapsulates the object creation logic and keeps it separate from the main program.

  • Flexibility: Adding new shapes is easy. You just need to create a new class that implements IShape and update the ShapeFactory accordingly.

  • Decoupling: The Program class doesn't need to know about the concrete classes of shapes, just the IShape interface.

This simple example demonstrates how the Factory Design Pattern can be used to manage object creation and make code more flexible and maintainable. Now let's see some real-world examples of how the Factory Pattern might be applied.

Examples : Factory Design Pattern in C#

  1. Document Creation

    Consider a scenario where an application can generate different types of documents (PDF, Word, Excel etc….). You don't want to tightly couple the client code to specific document types.

    
    // Document interface
    public interface IDocument
    {
        void Print();
    }
    
    // Concrete Products
    public class PdfDocument : IDocument
    {
        public void Print()
        {
    	Console.WriteLine("Printing PDF Document");
        }
    }
    
    public class WordDocument : IDocument
    {
        public void Print()
        {
    	Console.WriteLine("Printing Word Document");
        }
    }
    
    // Factory interface
    public interface IDocumentFactory
    {
        IDocument CreateDocument();
    }
    
    // Concrete Factories
    public class PdfDocumentFactory : IDocumentFactory
    {
        public IDocument CreateDocument()
        {
    	    return new  PdfDocument(); 
        }
    }
    
    public class WordDocumentFactory : IDocumentFactory
    {
         public IDocument CreateDocument()
         {
    	     return new  WordDocument();
         }
    }
    // Client code
    class Program 
    {
        static void Main(string[] args)
        {
            IDocumentFactory factory = new  PdfDocumentFactory();
            IDocument document = factory.CreateDocument();
            document.Print();  // Output: Printing PDF Document
    
            factory = new  WordDocumentFactory();
            document = factory.CreateDocument();
            document.Print();  // Output: Printing Word Document
        }
    }
    
    
  2. Database Connection

    When an application needs to connect to different types of databases ( SQL Server, MySQL, Oracle etc…), a factory pattern can manage the creation of database connection objects.

    
    // Database Connection interface
    public interface IDatabaseConnection
    {
        void Connect();
    }
    
    // Concrete Class
    public class SqlServerConnection : IDatabaseConnection
    {
        public void Connect() 
        {
               Console.WriteLine("Connecting to SQL Server");
        }
    }
    
    public class MySqlConnection : IDatabaseConnection
    {
       public void Connect() 
       {
               Console.WriteLine("Connecting to MySql");
       }
    }
    public class OracleConnection : IDatabaseConnection
    {
        public void Connect() 
        {
               Console.WriteLine("Connecting to Oracle");
         }
    }
    
    // Factory interface
    public interface IDatabaseConnectionFactory
    {
        IDatabaseConnection CreateConnection();
    }
    
    // Concrete Factories
    public class SqlServerConnectionFactory : IDatabaseConnectionFactory
    {
        public IDatabaseConnection CreateConnection() 
        {
                return new SqlServerConnection();
        }
    }
    
    public class MySqlConnectionFactory : IDatabaseConnectionFactory
    {
         public IDatabaseConnection CreateConnection() 
         {
                return new MySqlConnection();
         }
    }
    
    public class OracleConnectionFactory : IDatabaseConnectionFactory
    {
         public IDatabaseConnection CreateConnection() 
         {
                return new OracleConnection();
         }
    }
    
    // Client code
    class Program
    {
        static void Main(string[] args)
        {
            IDatabaseConnectionFactory factory = new SqlServerConnectionFactory();
            IDatabaseConnection connection = factory.CreateConnection();
            connection.Connect();  // Output: Connecting to SQL Server
    
            factory = new MySqlConnectionFactory();
            connection = factory.CreateConnection();
            connection.Connect();  // Output: Connecting to MySQL
    
            factory = new OracleConnectionFactory();
            connection = factory.CreateConnection();
            connection.Connect();  // Output: Connecting to Oracle
        }
    }
    
    

When and Where to Apply Factory Design Pattern

When to Use:

  • Complex Object Creation: When the creation process of an object is complex or requires multiple steps.

  • Decoupling: When you want to decouple the client code from the concrete implementations of the objects it needs.

  • Scalability: When you anticipate that new types of products might be added, and you want to minimize changes to the existing code.

Where to Use:
  • UI Components: For creating different types of UI controls dynamically.

  • Data Access Layers: When creating different types of data access objects or repositories.

  • Dependency Injection: When integrating with dependency injection frameworks, which often use factories to create and inject dependencies.


Summary

In each of these examples, the is used to handle the creation of objects in a way that allows for easy extension and maintenance. By delegating the creation process to a factory, you decouple the client code from the specifics of the classes it needs, making the system more flexible and easier to adapt to new requirements or changes.