-
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(); }
public class Circle : IShape { public void Draw() { Console.WriteLine("Drawing a Circle"); } } public class Rectangle : IShape { public void Draw() { Console.WriteLine("Drawing a Rectangle"); } }
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; } }
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#
- 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 } }
- 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.
- 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.